From: Armaan Bhojwani <3fb650a9-b47e-4604-a282-1dd91953b2ee@anonaddy.me> Date: Mon, 26 Oct 2020 17:07:58 +0000 (-0400) Subject: first push X-Git-Url: https://git.armaanb.net/?p=gen-shell.git;a=commitdiff_plain;h=a5d9cd320e9ed83e36de2b5326f1201ea16b697f first push --- a5d9cd320e9ed83e36de2b5326f1201ea16b697f diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..675b3d4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/libshared"] + path = src/libshared + url = https://git.tasktools.org/TM/libshared.git diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..9617cca --- /dev/null +++ b/AUTHORS @@ -0,0 +1,28 @@ +The development of tasksh was made possible by the significant contributions of +the following people: + + Paul Beckingham (Principal Author) + Federico Hernandez (Principal Author) + Dirk Deimeke (Technical Advisor & Evangelist) + +The following submitted code, packages or analysis, and deserve special thanks: + + Jörg Krause + Ben Boeckel + ilove zfs + Paul Fenwick + +Thanks to the following, who submitted detailed bug reports and excellent +suggestions: + + Kevin Gunn + Fidel Mato + David Stahl + David Patrick + jonbobbly + hosaka + Lars Kumbier + Iain R. Learmonth + Eric Hymowitz + bjonnh + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..205cf7b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,79 @@ +cmake_minimum_required (VERSION 2.8) +set (CMAKE_LEGACY_CYGWIN_WIN32 0) # Remove when CMake >= 2.8.4 is required +set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") +set (HAVE_CMAKE true) + +project (tasksh) +include (CXXSniffer) + +set (PROJECT_VERSION "1.2.0") + +include (CheckFunctionExists) +include (CheckStructHasMember) +include (CheckCXXCompilerFlag) + +message ("-- Looking for SHA1 references") +if (EXISTS ${CMAKE_SOURCE_DIR}/.git/index) + set (HAVE_COMMIT true) + execute_process (COMMAND git log -1 --pretty=format:%h + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE COMMIT) + configure_file ( ${CMAKE_SOURCE_DIR}/commit.h.in + ${CMAKE_SOURCE_DIR}/commit.h) + message ("-- Found SHA1 reference: ${COMMIT}") +endif (EXISTS ${CMAKE_SOURCE_DIR}/.git/index) + +set (PACKAGE "${PROJECT_NAME}") +set (VERSION "${PROJECT_VERSION}") +set (PACKAGE_BUGREPORT "support@taskwarrior.org") +set (PACKAGE_NAME "${PACKAGE}") +set (PACKAGE_TARNAME "${PACKAGE}") +set (PACKAGE_VERSION "${VERSION}") +set (PACKAGE_STRING "${PACKAGE} ${VERSION}") + +if (FREEBSD) +SET (TASKSH_MAN1DIR man/man1 CACHE STRING "Installation directory for man pages, section 1") +else (FREEBSD) +SET (TASKSH_MAN1DIR share/man/man1 CACHE STRING "Installation directory for man pages, section 1") +endif (FREEBSD) +SET (TASKSH_DOCDIR share/doc/tasksh CACHE STRING "Installation directory for doc files") +SET (TASKSH_RCDIR "${TASKSH_DOCDIR}/rc" CACHE STRING "Installation directory for configuration files") +SET (TASKSH_BINDIR bin CACHE STRING "Installation directory for the binary") + +# include the readline library finder module +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules") + +# find readline +message ("-- Looking for GNU Readline") +find_package (Readline REQUIRED) +if (READLINE_FOUND) + set (HAVE_READLINE true) + set (TASKSH_INCLUDE_DIRS ${TASKSH_INCLUDE_DIRS} ${READLINE_INCLUDE_DIR}) + set (TASKSH_LIBRARIES ${TASKSH_LIBRARIES} ${READLINE_LIBRARIES}) +endif (READLINE_FOUND) + +message ("-- Configuring cmake.h") +configure_file ( + ${CMAKE_SOURCE_DIR}/cmake.h.in + ${CMAKE_SOURCE_DIR}/cmake.h) + +add_subdirectory (src) +add_subdirectory (doc) +if (EXISTS ${CMAKE_SOURCE_DIR}/test) + add_subdirectory (test EXCLUDE_FROM_ALL) +endif (EXISTS ${CMAKE_SOURCE_DIR}/test) + +set (doc_FILES NEWS ChangeLog INSTALL AUTHORS COPYING) +foreach (doc_FILE ${doc_FILES}) + install (FILES ${doc_FILE} DESTINATION ${TASKSH_DOCDIR}) +endforeach (doc_FILE) + +# --- + +set (CPACK_SOURCE_GENERATOR "TGZ") +set (CPACK_SOURCE_PACKAGE_FILE_NAME ${PACKAGE_NAME}-${PACKAGE_VERSION}) +set (CPACK_SOURCE_IGNORE_FILES "CMakeCache" "CMakeFiles" "CPackConfig" "CPackSourceConfig" + "_CPack_Packages" "cmake_install" "install_manifest" "Makefile$" + "test" "package-config" "misc/*" "src/tasksh$" "README.md" + "/\\\\.gitignore" "/\\\\.git/" "swp$") +include (CPack) diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..9295662 --- /dev/null +++ b/COPYING @@ -0,0 +1,23 @@ +tasksh - a shell/frontend for the command line task list manager taskwarrior. + +Copyright 2006 - 2017, Paul Beckingham, Federico Hernandez. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +http://www.opensource.org/licenses/mit-license.php diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..733d5fa --- /dev/null +++ b/ChangeLog @@ -0,0 +1,50 @@ +1.2.0 (2017-05-10) - + +- TS-29 tasksh hangs trying to read task from stdin + (thanks to ilove zfs). +- TS-32 control-d to exit + (thanks to Eric Hymowitz, Paul Fenwick). +- TS-34 Tasksh throw a warning at the end of a review command + (thanks to bjonnh). +- Review report now defaults to 6 days instead of 1 weeķ, which is more + convenient for those who review weekly + (thanks to Dirk Deimeke). + +------ current release --------------------------- + +1.1.0 (2016-09-06) 464f5ae19f853911e739c2489897aef64345c388 + +- TD-120 Missing cmakedefine for HAVE_GET_CURRENT_DIR_NAME + (Thanks to Jörg Krause, Ben Boeckel). +- TW-1845 Cygwin build fails, missing get_current_dir_name + (thanks to hosaka). +- TS-11 Autoclear in the Task Shell + (thanks to Lars Kumbier). +- TS-24 add review option (m)odify + (thanks to David Patrick). +- TS-28 Please add a (m)odify feature for review + (thanks to Iain R. Learmonth). +- Implemented 'review' command. +- Implemented 'diag' command. +- Added 'review N' option, to specify the number of tasks you would like to + review. +- Integrated libshared.git. + +------ old releases ------------------------------ + +1.0.0 (2014-12-21) 5934dfcefac6d037a359bc733a8382e42e32552e + +- TS-1 Apostrophe inside tasksh 'log' causes segmentation fault + (thanks to David Stahl). +- TS-2 tasksh segfaults if quotes not closed + (thanks to Fidel Mato). +- TS-5 tasksh segfaults + (thanks to David Patrick). +- TS-13 Quotes included when using task shell + (thanks to Kevin Gunn). +- libreadline support added for line editing and command history. + +------ start ----------------------------------- + +Project started 2014-06-08 + diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..95f9120 --- /dev/null +++ b/INSTALL @@ -0,0 +1,131 @@ +Installation Instructions +------------------------- + +Please follow the instructions below to build and install tasksh from source. + + +Dependencies +------------ + +You will need the CMake build system installed in order to build tasksh from +source. Information on cmake can be obtained at http://cmake.org + +Additionally, you will need: + + libreadline + + +Basic Installation +------------------ + +Briefly, these shell commands will unpack, build and install Tasksh: + + $ tar xzf tasksh-X.Y.Z.tar.gz [1] + $ cd tasksh-X.Y.Z [2] + $ cmake -DCMAKE_BUILD_TYPE=release . [3] + $ make [4] + $ sudo make install [5] + $ cd .. ; rm -r tasksh-X.Y.Z [6] + +These commands are explained below: + + 1. Unpacks the source tarball. This creates the directory tasksh-X.Y.Z, + containing all the code. + + 2. Change directory to the root of the distribution. + + 3. Invokes CMake to scan for dependencies and machine-specific details, then + generate the makefiles. Requests an optimized build, which will run faster + and be more compact. This may take a minute. + + 4. Builds tasksh. This may take a minute. + + 5. Installs the program, documentation and other data files. + + 6. Removes the temporary directory. + + +Build and configurations options +-------------------------------- + +You can customize the configuration run with cmake variables. This will modify +the installation process: + +To change the installation directory you use the following configuration +variable: + + $ cmake -DCMAKE_INSTALL_PREFIX= . + +cmake configuration variables are applied with the -D option and consist of a + and a : + + $ cmake -D= . + +Four more variables can customize the installation process. The following table +lists them and their defaults plus the CMAKE_INSTALL_PREFIX: + + CMAKE_INSTALL_PREFIX /usr/local + TASKSH_BINDIR bin + TASKSH_DOCDIR share/doc/tasksh + TASKSH_MAN1DIR share/man/man1 + +The corresponding TASKSH_* variables will be combined with CMAKE_INSTALL_PREFIX to +get absolute installation directories: + + CMAKE_INSTALL_PREFIX/TASKSH_BINDIR /usr/local/bin + CMAKE_INSTALL_PREFIX/TASKSH_DOCDIR /usr/local/share/doc/tasksh + CMAKE_INSTALL_PREFIX/TASKSH_MAN1DIR /usr/local/share/man/man1 + + +Uninstallation +-------------- + +There is no uninstall option in CMake makefiles. This is a manual process. + +To uninstall Tasksh, remove the files listed in the install_manifest.txt file +that was generated when you built Tasksh. + + +Tasksh Build Notes +----------------------- + +Tasksh has dependencies that are detected by CMake in almost all cases, but +there are situations and operating systems that mean you will need to offer a +little help. + +If Tasksh will not build on your system, first take a look at the Operating +System notes below. If this doesn't help, then go to the Troubleshooting +section, which includes instructions on how to contact us for help. + + +Operating System Notes +---------------------- + +Cygwin + If 'make install' fails when writing to the /usr/local/share/ directory, + this may be because your current login doesn't have permission to write + to the Windows directory containing your Cygwin installation. Either + login to Windows as an Administrator and try the 'make install' process + again, or reinstall Cygwin under your normal Windows login. + + +Troubleshooting +--------------- + +If you've recently made changes to dependencies (by reinstalling them, for +example) be sure to rerun 'cmake .' before trying to execute 'make' again. + +CMake will run and locate all the necessary pieces for the build, and create +a Makefile. There may be errors and warnings when running CMake, or there +may be compiler errors and warnings when running 'make'. Sometimes you will run +CMake with no reported problems, and the build will fail later. This is +almost always because CMake is mistaken about some assumption. + +If a build does not succeed, please send the contents of the 'CMakeCache.txt' +and 'CMakeFiles/CMakeOutput.log' files to support@taskwarrior.org. + +If CMake runs but tasksh does not build, please send the contents of the above +files as well as a transcript from the build, which is not written to a file +and must be captured from the terminal. + +--- diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f9ab300 --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +tasksh - a shell/frontend for ithe command line task list manager taskwarrior. + +Copyright 2006 - 2017, Paul Beckingham, Federico Hernandez. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +http://www.opensource.org/licenses/mit-license.php diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..4f9546e --- /dev/null +++ b/NEWS @@ -0,0 +1,43 @@ + +New Features in tasksh 1.2.0 + + - Responds to Ctrl-D by exiting. + +New commands in tasksh 1.2.0 + + - + +New configuration options in tasksh 1.2.0 + + - + +Known Issues + + - + +Tasksh has been built and tested on the following configurations: + + * macOS + * Fedora + * Ubuntu + * Debian + * Arch + * FreeBSD + * Cygwin + +--- + +While Tasksh has undergone testing, bugs are sure to remain. If you +encounter a bug, please enter a new issue at: + + http://bug.tasktools.org + +Or you can also report the issue in the forums at: + + http://answers.tasktools.org + +Or just send a message to: + + support@taskwarrior.org + +Thank you. diff --git a/README.md b/README.md new file mode 100644 index 0000000..953d7c7 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# gen-shell +A work in progress generic shell forked form [taskshell](https://github.com/GothenburgBitFactory/taskshell) diff --git a/cmake.h b/cmake.h new file mode 100644 index 0000000..6a8947b --- /dev/null +++ b/cmake.h @@ -0,0 +1,60 @@ +/* cmake.h.in. Creates cmake.h during a cmake run */ + +/* Product identification */ +#define PRODUCT_TASKSH 1 + +/* Package information */ +#define PACKAGE "tasksh" +#define VERSION "1.2.0" +#define PACKAGE_BUGREPORT "support@taskwarrior.org" +#define PACKAGE_NAME "tasksh" +#define PACKAGE_TARNAME "tasksh" +#define PACKAGE_VERSION "1.2.0" +#define PACKAGE_STRING "tasksh 1.2.0" + +#define CMAKE_BUILD_TYPE "" + +/* Localization */ +#define PACKAGE_LANGUAGE +#define LANGUAGE_ENG_USA + +/* git information */ +#define HAVE_COMMIT + +/* cmake information */ +#define HAVE_CMAKE +#define CMAKE_VERSION "3.8.1" + +/* Compiling platform */ +/* #undef LINUX */ +#define DARWIN +/* #undef CYGWIN */ +/* #undef FREEBSD */ +/* #undef OPENBSD */ +/* #undef NETBSD */ +/* #undef HAIKU */ +/* #undef SOLARIS */ +/* #undef KFREEBSD */ +/* #undef GNUHURD */ +/* #undef UNKNOWN */ + +/* Found the Readline library */ +#define HAVE_READLINE + +/* Found the pthread library */ +/* #undef HAVE_LIBPTHREAD */ + +/* Found wordexp.h */ +/* #undef HAVE_WORDEXP */ + +/* Found tm.tm_gmtoff struct member */ +/* #undef HAVE_TM_GMTOFF */ + +/* Found st.st_birthtime struct member */ +/* #undef HAVE_ST_BIRTHTIME */ + +/* Functions */ +/* #undef HAVE_GET_CURRENT_DIR_NAME */ +/* #undef HAVE_TIMEGM */ +/* #undef HAVE_UUID_UNPARSE_LOWER */ + diff --git a/cmake.h.in b/cmake.h.in new file mode 100644 index 0000000..e905598 --- /dev/null +++ b/cmake.h.in @@ -0,0 +1,60 @@ +/* cmake.h.in. Creates cmake.h during a cmake run */ + +/* Product identification */ +#define PRODUCT_TASKSH 1 + +/* Package information */ +#define PACKAGE "${PACKAGE}" +#define VERSION "${VERSION}" +#define PACKAGE_BUGREPORT "${PACKAGE_BUGREPORT}" +#define PACKAGE_NAME "${PACKAGE_NAME}" +#define PACKAGE_TARNAME "${PACKAGE_TARNAME}" +#define PACKAGE_VERSION "${PACKAGE_VERSION}" +#define PACKAGE_STRING "${PACKAGE_STRING}" + +#define CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" + +/* Localization */ +#define PACKAGE_LANGUAGE ${PACKAGE_LANGUAGE} +#define LANGUAGE_ENG_USA ${LANGUAGE_ENG_USA} + +/* git information */ +#cmakedefine HAVE_COMMIT + +/* cmake information */ +#cmakedefine HAVE_CMAKE +#define CMAKE_VERSION "${CMAKE_VERSION}" + +/* Compiling platform */ +#cmakedefine LINUX +#cmakedefine DARWIN +#cmakedefine CYGWIN +#cmakedefine FREEBSD +#cmakedefine OPENBSD +#cmakedefine NETBSD +#cmakedefine HAIKU +#cmakedefine SOLARIS +#cmakedefine KFREEBSD +#cmakedefine GNUHURD +#cmakedefine UNKNOWN + +/* Found the Readline library */ +#cmakedefine HAVE_READLINE + +/* Found the pthread library */ +#cmakedefine HAVE_LIBPTHREAD + +/* Found wordexp.h */ +#cmakedefine HAVE_WORDEXP + +/* Found tm.tm_gmtoff struct member */ +#cmakedefine HAVE_TM_GMTOFF + +/* Found st.st_birthtime struct member */ +#cmakedefine HAVE_ST_BIRTHTIME + +/* Functions */ +#cmakedefine HAVE_GET_CURRENT_DIR_NAME +#cmakedefine HAVE_TIMEGM +#cmakedefine HAVE_UUID_UNPARSE_LOWER + diff --git a/cmake/CXXSniffer.cmake b/cmake/CXXSniffer.cmake new file mode 100644 index 0000000..9606226 --- /dev/null +++ b/cmake/CXXSniffer.cmake @@ -0,0 +1,51 @@ +message ("-- Configuring C++11") +message ("-- System: ${CMAKE_SYSTEM_NAME}") + +include (CheckCXXCompilerFlag) + +# NOTE: Phase out -std=gnu++0x and --std=c++0x as soon as realistically possible. +CHECK_CXX_COMPILER_FLAG("-std=c++11" _HAS_CXX11) +CHECK_CXX_COMPILER_FLAG("-std=c++0x" _HAS_CXX0X) +CHECK_CXX_COMPILER_FLAG("-std=gnu++0x" _HAS_GNU0X) + +if (_HAS_CXX11) + set (_CXX11_FLAGS "-std=c++11") +elseif (_HAS_CXX0X) + message (WARNING "Enabling -std=c++0x draft compile flag. Your compiler does not support the standard '-std=c++11' option. Consider upgrading.") + set (_CXX11_FLAGS "-std=c++0x") +elseif (_HAS_GNU0X) + message (WARNING "Enabling -std=gnu++0x draft compile flag. Your compiler does not support the standard '-std=c++11' option. Consider upgrading.") + set (_CXX11_FLAGS "-std=gnu++0x") +else (_HAS_CXX11) + message (FATAL_ERROR "C++11 support missing. Try upgrading your C++ compiler. If you have a good reason for using an outdated compiler, please let us know at support@taskwarrior.org.") +endif (_HAS_CXX11) + +if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + set (LINUX true) +elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set (DARWIN true) + set (_CXX11_FLAGS "${_CXX11_FLAGS} -stdlib=libc++") +elseif (${CMAKE_SYSTEM_NAME} MATCHES "kFreeBSD") + set (KFREEBSD true) +elseif (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") + set (FREEBSD true) +elseif (${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD") + set (OPENBSD true) +elseif (${CMAKE_SYSTEM_NAME} MATCHES "NetBSD") + set (NETBSD true) +elseif (${CMAKE_SYSTEM_NAME} MATCHES "SunOS") + set (SOLARIS true) +elseif (${CMAKE_SYSTEM_NAME} STREQUAL "GNU") + set (GNUHURD true) +elseif (${CMAKE_SYSTEM_NAME} STREQUAL "CYGWIN") + set (CYGWIN true) + # NOTE: Not setting -std=gnu++0x leads to compile errors even with + # GCC 4.8.3, and debugging those leads to insanity. Adding this + # workaround instead of fixing Cygwin. + set (_CXX11_FLAGS "-std=gnu++0x") +else (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + set (UNKNOWN true) +endif (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + +set (CMAKE_CXX_FLAGS "${_CXX11_FLAGS} ${CMAKE_CXX_FLAGS}") +set (CMAKE_CXX_FLAGS "-Wall -Wextra -Wsign-compare -Wreturn-type ${CMAKE_CXX_FLAGS}") diff --git a/cmake/Modules/FindReadline.cmake b/cmake/Modules/FindReadline.cmake new file mode 100644 index 0000000..8fdaec1 --- /dev/null +++ b/cmake/Modules/FindReadline.cmake @@ -0,0 +1,81 @@ +# - Find the readline library +# This module defines +# READLINE_INCLUDE_DIR, path to readline/readline.h, etc. +# READLINE_LIBRARIES, the libraries required to use READLINE. +# READLINE_FOUND, If false, do not try to use READLINE. +# also defined, but not for general use are +# READLINE_readline_LIBRARY, where to find the READLINE library. +# READLINE_ncurses_LIBRARY, where to find the ncurses library [might not be defined] + +# Apple readline does not support readline hooks +# So we look for another one by default +IF (APPLE OR FREEBSD) + FIND_PATH (READLINE_INCLUDE_DIR NAMES readline/readline.h PATHS + /usr/include/ + /sw/include + /opt/local/include + /opt/include + /usr/local/include + NO_DEFAULT_PATH + ) +ENDIF (APPLE OR FREEBSD) +FIND_PATH (READLINE_INCLUDE_DIR NAMES readline/readline.h) + + +# Apple readline does not support readline hooks +# So we look for another one by default +IF (APPLE OR FREEBSD) + FIND_LIBRARY (READLINE_readline_LIBRARY NAMES readline PATHS + /usr/lib + /sw/lib + /opt/local/lib + /opt/lib + /usr/local/lib + NO_DEFAULT_PATH + ) +ENDIF (APPLE OR FREEBSD) +FIND_LIBRARY (READLINE_readline_LIBRARY NAMES readline) + +# Sometimes readline really needs ncurses +IF (APPLE OR FREEBSD) + FIND_LIBRARY (READLINE_ncurses_LIBRARY NAMES ncurses PATHS + /usr/lib + /sw/lib + /opt/local/lib + /opt/lib + /usr/local/lib + /usr/lib + NO_DEFAULT_PATH + ) +ENDIF (APPLE OR FREEBSD) +FIND_LIBRARY (READLINE_ncurses_LIBRARY NAMES ncurses) + +MARK_AS_ADVANCED ( + READLINE_INCLUDE_DIR + READLINE_readline_LIBRARY + READLINE_ncurses_LIBRARY + ) + +SET (READLINE_FOUND "NO" ) +IF (READLINE_INCLUDE_DIR) + IF (READLINE_readline_LIBRARY) + SET (READLINE_FOUND "YES" ) + SET (READLINE_LIBRARIES + ${READLINE_readline_LIBRARY} + ) + + # some readline libraries depend on ncurses + IF (READLINE_ncurses_LIBRARY) + SET (READLINE_LIBRARIES ${READLINE_LIBRARIES} ${READLINE_ncurses_LIBRARY}) + ENDIF (READLINE_ncurses_LIBRARY) + + ENDIF (READLINE_readline_LIBRARY) +ENDIF (READLINE_INCLUDE_DIR) + +IF (READLINE_FOUND) + MESSAGE (STATUS "Found readline library") +ELSE (READLINE_FOUND) + IF (READLINE_FIND_REQUIRED) + MESSAGE (FATAL_ERROR "Could not find readline -- please give some paths to CMake") + ENDIF (READLINE_FIND_REQUIRED) +ENDIF (READLINE_FOUND) diff --git a/commit.h b/commit.h new file mode 100644 index 0000000..a968aef --- /dev/null +++ b/commit.h @@ -0,0 +1,4 @@ +/* commit.h.in. Creates commit.h during a cmake run */ + +/* git information */ +#define COMMIT "e6d0532" diff --git a/commit.h.in b/commit.h.in new file mode 100644 index 0000000..3a6fe82 --- /dev/null +++ b/commit.h.in @@ -0,0 +1,4 @@ +/* commit.h.in. Creates commit.h during a cmake run */ + +/* git information */ +#define COMMIT "${COMMIT}" diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 0000000..180543d --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required (VERSION 2.8) +message ("-- Configuring man pages") +set (man_FILES tasksh.1) +foreach (man_FILE ${man_FILES}) + configure_file ( + man/${man_FILE}.in + man/${man_FILE}) +endforeach (man_FILE) + +install (DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/man/ DESTINATION ${TASKSH_MAN1DIR} + FILES_MATCHING PATTERN "*.1") + diff --git a/doc/man/tasksh.1 b/doc/man/tasksh.1 new file mode 100644 index 0000000..ec53447 --- /dev/null +++ b/doc/man/tasksh.1 @@ -0,0 +1,175 @@ +.TH tasksh 1 2017-05-10 "tasksh 1.2.0" "User Manuals" + +.SH NAME +tasksh \- Interactive taskwarrior shell + +.SH SYNOPSIS +.B tasksh +.br +.B tasksh --version + +.SH DESCRIPTION +Tasksh can be used to create a more immersive taskwarrior environment. +Any task command you run outside the shell can also be run inside +the shell, without the need to prefix every command with "task". + +When built with libreadline, tasksh provides command editing and history. + +Tasksh has an integrated 'review' command that leads you through an interactive +review session. + +Tasksh supports all recent versions of Taskwarrior. + +.SH COMMANDS +Tasksh supports the following commands. All other commands are passed intact to +Taskwarrior. + +.TP +.B diagnostics +Displays settings pertinent to tasksh, for diagnosing problems. + +.TP +.B exec +This command allows you to run shell commands from within Tasksh. This is ideal +for accessing man pages such as this. The '!' command can be used in place of +the 'exec' keyword. Once the command is run, control returns to Tasksh. + +.TP +.B exit/quit +These commands cause tasksh to terminate, returning you to your system shell. + +.TP +.B help +Shows a summary of commands, and how to obtain help. + +.TP +.B review [N] +Begins an interactive review session, where you can mark tasks as reviewed, +edit them using your text editor, provide modification commands, or skip them. +You can terminate a review session at any time, and the next review session +will resume at the right place. + +To find tasks needing review, the '_reviewed' custom report is created and run, +which filters tasks that have a missing 'reviewed' UDA date, or have not been +reviewed for a week. + +This means that if you run a review session to completion, there will be no +need to review again for a week, and the review command will simply do nothing +until then. + +The one week review cycle is defined by the '_reviewed' custom report, which +can be modified if you prefer a monthly review cycle. + +If 'N' is provided, the session is limited to reviewing only N tasks. + +Note: requires Taskwarrior 2.5.0 or later. +For full details, see: + + +.SH USAGE +Here is an example tasksh session. + +$ tasksh +.br +task> projects +.br + +.br +Project Tasks Pri:None Pri:L Pri:M Pri:H +.br +------- ----- -------- ----- ----- ----- +.br + 7 7 0 0 0 +.br +home 2 2 0 0 0 +.br +party 6 3 0 0 3 +.br + +.br +3 projects (15 tasks) +.br +task> tags +.br + +.br +Tag Count +.br +mall 2 +.br + +.br +1 tag (15 tasks) +.br +task> list +.br + +.br +ID Project Pri Due Active Age Description +.br +--------------------------------------------------------------------- +.br + 2 party H 10/17/2015 2 hrs Select and book a venue +.br + 5 party H 10/22/2015 2 hrs Design invitations +.br + 9 home 10/31/2015 1 hr Pay rent +.br + 3 party 2 hrs Mail invitations +.br + 4 party 2 hrs Select a caterer +.br + 6 party 2 hrs Print invitations +.br + +.br + 8 tasks +.br + task> quit +.br + $ +.br + +.SH CONFIGURATION +Tasksh piggybacks on Taskwarrior's .taskrc configuration file, and refers +to settings there. If you use a non-standard location for your .task database +, and .taskrc file, Tasksh will not find them unless you set the TASKDATA and +TASKRC environment variables. See 'man taskrc' for more details. + +The review command storeѕ a UDA ('reviewed') and report definition ('_reviewed'). + +.TP +.B tasksh.autoclear=1 +If set to "1", causes each tasksh command to be preceded by a 'clear screen' and +cursor reset. Default is "0". + +.SH "CREDITS & COPYRIGHTS" +Copyright (C) 2006 \- 2017 P. Beckingham, F. Hernandez. + +This man page was originally written by Federico Hernandez. + +Tasksh is distributed under the MIT license. See +http://www.opensource.org/licenses/mit-license.php for more information. + +.SH SEE ALSO +.BR task(1), + +For more information regarding tasksh, see the following: + +.TP +The official site at + + +.TP +The official code repository at + + +.TP +You can contact the project by emailing + + +.SH REPORTING BUGS +.TP +Bugs in tasksh may be reported to the issue-tracker at + + diff --git a/doc/man/tasksh.1.in b/doc/man/tasksh.1.in new file mode 100644 index 0000000..0b0354c --- /dev/null +++ b/doc/man/tasksh.1.in @@ -0,0 +1,175 @@ +.TH tasksh 1 2017-05-10 "${PACKAGE_STRING}" "User Manuals" + +.SH NAME +tasksh \- Interactive taskwarrior shell + +.SH SYNOPSIS +.B tasksh +.br +.B tasksh --version + +.SH DESCRIPTION +Tasksh can be used to create a more immersive taskwarrior environment. +Any task command you run outside the shell can also be run inside +the shell, without the need to prefix every command with "task". + +When built with libreadline, tasksh provides command editing and history. + +Tasksh has an integrated 'review' command that leads you through an interactive +review session. + +Tasksh supports all recent versions of Taskwarrior. + +.SH COMMANDS +Tasksh supports the following commands. All other commands are passed intact to +Taskwarrior. + +.TP +.B diagnostics +Displays settings pertinent to tasksh, for diagnosing problems. + +.TP +.B exec +This command allows you to run shell commands from within Tasksh. This is ideal +for accessing man pages such as this. The '!' command can be used in place of +the 'exec' keyword. Once the command is run, control returns to Tasksh. + +.TP +.B exit/quit +These commands cause tasksh to terminate, returning you to your system shell. + +.TP +.B help +Shows a summary of commands, and how to obtain help. + +.TP +.B review [N] +Begins an interactive review session, where you can mark tasks as reviewed, +edit them using your text editor, provide modification commands, or skip them. +You can terminate a review session at any time, and the next review session +will resume at the right place. + +To find tasks needing review, the '_reviewed' custom report is created and run, +which filters tasks that have a missing 'reviewed' UDA date, or have not been +reviewed for a week. + +This means that if you run a review session to completion, there will be no +need to review again for a week, and the review command will simply do nothing +until then. + +The one week review cycle is defined by the '_reviewed' custom report, which +can be modified if you prefer a monthly review cycle. + +If 'N' is provided, the session is limited to reviewing only N tasks. + +Note: requires Taskwarrior 2.5.0 or later. +For full details, see: + + +.SH USAGE +Here is an example tasksh session. + +$ tasksh +.br +task> projects +.br + +.br +Project Tasks Pri:None Pri:L Pri:M Pri:H +.br +------- ----- -------- ----- ----- ----- +.br + 7 7 0 0 0 +.br +home 2 2 0 0 0 +.br +party 6 3 0 0 3 +.br + +.br +3 projects (15 tasks) +.br +task> tags +.br + +.br +Tag Count +.br +mall 2 +.br + +.br +1 tag (15 tasks) +.br +task> list +.br + +.br +ID Project Pri Due Active Age Description +.br +--------------------------------------------------------------------- +.br + 2 party H 10/17/2015 2 hrs Select and book a venue +.br + 5 party H 10/22/2015 2 hrs Design invitations +.br + 9 home 10/31/2015 1 hr Pay rent +.br + 3 party 2 hrs Mail invitations +.br + 4 party 2 hrs Select a caterer +.br + 6 party 2 hrs Print invitations +.br + +.br + 8 tasks +.br + task> quit +.br + $ +.br + +.SH CONFIGURATION +Tasksh piggybacks on Taskwarrior's .taskrc configuration file, and refers +to settings there. If you use a non-standard location for your .task database +, and .taskrc file, Tasksh will not find them unless you set the TASKDATA and +TASKRC environment variables. See 'man taskrc' for more details. + +The review command storeѕ a UDA ('reviewed') and report definition ('_reviewed'). + +.TP +.B tasksh.autoclear=1 +If set to "1", causes each tasksh command to be preceded by a 'clear screen' and +cursor reset. Default is "0". + +.SH "CREDITS & COPYRIGHTS" +Copyright (C) 2006 \- 2017 P. Beckingham, F. Hernandez. + +This man page was originally written by Federico Hernandez. + +Tasksh is distributed under the MIT license. See +http://www.opensource.org/licenses/mit-license.php for more information. + +.SH SEE ALSO +.BR task(1), + +For more information regarding tasksh, see the following: + +.TP +The official site at + + +.TP +The official code repository at + + +.TP +You can contact the project by emailing + + +.SH REPORTING BUGS +.TP +Bugs in tasksh may be reported to the issue-tracker at + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..787640d --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required (VERSION 2.8) +include_directories (${CMAKE_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/src/libshared/src + ${TASKSH_INCLUDE_DIRS}) + +set (tasksh_SRCS diag.cpp + help.cpp + prompt.cpp + review.cpp + shell.cpp) + +set (libshared_SRCS libshared/src/Color.cpp libshared/src/Color.h + libshared/src/Datetime.cpp libshared/src/Datetime.h + libshared/src/Duration.cpp libshared/src/Duration.h + libshared/src/FS.cpp libshared/src/FS.h + libshared/src/Lexer.cpp libshared/src/Lexer.h + libshared/src/Pig.cpp libshared/src/Pig.h + libshared/src/shared.cpp libshared/src/shared.h + libshared/src/format.cpp libshared/src/format.h + libshared/src/unicode.cpp libshared/src/unicode.h + libshared/src/utf8.cpp libshared/src/utf8.h + libshared/src/wcwidth6.cpp) + +add_library (tasksh STATIC ${tasksh_SRCS}) +add_library (libshared STATIC ${libshared_SRCS}) +add_executable (tasksh_executable main.cpp) + +target_link_libraries (tasksh_executable tasksh libshared ${TASKSH_LIBRARIES}) + +set_property (TARGET tasksh_executable PROPERTY OUTPUT_NAME "tasksh") + +install (TARGETS tasksh_executable DESTINATION ${TASKSH_BINDIR}) + diff --git a/src/diag.cpp b/src/diag.cpp new file mode 100644 index 0000000..8478fba --- /dev/null +++ b/src/diag.cpp @@ -0,0 +1,163 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2017, Paul Beckingham, Federico Hernandez. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// http://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_READLINE +#include +#include +#endif + +//////////////////////////////////////////////////////////////////////////////// +int cmdDiagnostics () +{ + Color bold ("bold"); + + std::cout << "\n" + << bold.colorize (PACKAGE_STRING) + << "\n" + << " " << "Platform: " << osName () + << "\n\n"; + + // Compiler. + std::cout << bold.colorize ("Compiler") + << "\n" +#ifdef __VERSION__ + << " " << "Version: " + << __VERSION__ << "\n" +#endif + << " " << "Caps:" +#ifdef __STDC__ + << " +stdc" +#endif +#ifdef __STDC_HOSTED__ + << " +stdc_hosted" +#endif +#ifdef __STDC_VERSION__ + << " +" << __STDC_VERSION__ +#endif +#ifdef _POSIX_VERSION + << " +" << _POSIX_VERSION +#endif +#ifdef _POSIX2_C_VERSION + << " +" << _POSIX2_C_VERSION +#endif +#ifdef _ILP32 + << " +ILP32" +#endif +#ifdef _LP64 + << " +LP64" +#endif + << " +c" << 8 * sizeof (char) + << " +i" << 8 * sizeof (int) + << " +l" << 8 * sizeof (long) + << " +vp" << 8 * sizeof (void*) + << " +time_t" << 8 * sizeof (time_t) + << "\n"; + + // Compiler compliance level. + std::cout << " Compliance: " + << cppCompliance () + << "\n\n"; + + std::cout << bold.colorize ("Build Features") + << "\n" + + // Build date. + << " " << "Built: " << __DATE__ << " " << __TIME__ << "\n" +#ifdef HAVE_COMMIT + << " " << "Commit: " << COMMIT << "\n" +#endif + << " CMake: " << CMAKE_VERSION << "\n"; + + std::cout << "libreadline: " +#ifdef HAVE_READLINE +#ifdef RL_VERSION_MAJOR + << RL_VERSION_MAJOR << "." << RL_VERSION_MINOR +#elif defined RL_READLINE_VERSION + << "0x" << std::hex << RL_READLINE_VERSION +#endif +#else + << "n/a" +#endif + << "\n"; + + std::cout << " Build type: " +#ifdef CMAKE_BUILD_TYPE + << CMAKE_BUILD_TYPE +#else + << "-" +#endif + << "\n\n"; + + std::cout << bold.colorize ("Configuration") + << "\n"; + + auto env = getenv ("TASKRC"); + std::cout << " TASKRC: " + << (env ? env : "") + << "\n"; + + env = getenv ("TASKDATA"); + std::cout << " TASKDATA: " + << (env ? env : "") + << "\n"; + + // Taskwarrior version + location + std::string path (getenv ("PATH")); + std::cout << " PATH: " << path << "\n"; + + for (const auto& i : split (path, ':')) + { + File task (i + "/task"); + if (task.exists ()) + { + std::string input; + std::string output; + execute ("task", {"--version"}, input, output); + + std::cout << "Taskwarrior: " + << i + << "/task " + << output; // Still has \n + } + } + + std::cout << "\n"; + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/help.cpp b/src/help.cpp new file mode 100644 index 0000000..2af1a01 --- /dev/null +++ b/src/help.cpp @@ -0,0 +1,48 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2017, Paul Beckingham, Federico Hernandez. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// http://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +int cmdHelp () +{ + std::cout << '\n' + << " Commands:\n" + << " tasksh> list Or any other Taskwarrior command\n" + << " tasksh> review [N] Task review session, with optional cutoff after N tasks\n" + << " tasksh> exec ls -al Any shell command. May also use '!ls -al'\n" + << " tasksh> help Tasksh help\n" + << " tasksh> diagnostics Tasksh diagnostics\n" + << " tasksh> quit End of session. May also use 'exit'\n" + << '\n' + << "Run 'man tasksh' from your shell prompt.\n" + << "Run '! man tasksh' from inside tasksh.\n" + << '\n'; + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/liblibshared.a b/src/liblibshared.a new file mode 100644 index 0000000..fe9bdc8 Binary files /dev/null and b/src/liblibshared.a differ diff --git a/src/libshared/AUTHORS b/src/libshared/AUTHORS new file mode 100644 index 0000000..8aab4e2 --- /dev/null +++ b/src/libshared/AUTHORS @@ -0,0 +1,21 @@ +The development of libshared was made possible by the significant contributions +of the following people: + + Paul Beckingham (Principal Author) + Federico Hernandez (Principal Author) + +The following submitted code, packages or analysis, and deserve special thanks: + + Lynoure Braakman + Jörg Krause + Ben Boeckel + Iain R. Learmonth + Toyam Cox + +Thanks to the following, who submitted detailed bug reports and excellent +suggestions: + + Sunil Joshi + Ellington Santos + Yury Vidineev + hosaka diff --git a/src/libshared/CMakeLists.txt b/src/libshared/CMakeLists.txt new file mode 100644 index 0000000..c1fccb2 --- /dev/null +++ b/src/libshared/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required (VERSION 2.8) +set (CMAKE_LEGACY_CYGWIN_WIN32 0) # Remove when CMake >= 2.8.4 is required +set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") +set (HAVE_CMAKE true) + +project (shared) +include (CXXSniffer) + +set (PROJECT_VERSION "1.0.0") + +set (PACKAGE "${PROJECT_NAME}") +set (VERSION "${PROJECT_VERSION}") +set (PACKAGE_BUGREPORT "support@taskwarrior.org") +set (PACKAGE_NAME "${PACKAGE}") +set (PACKAGE_TARNAME "${PACKAGE}") +set (PACKAGE_VERSION "${VERSION}") +set (PACKAGE_STRING "${PACKAGE} ${VERSION}") + +message ("-- Configuring cmake.h") +configure_file ( + ${CMAKE_SOURCE_DIR}/cmake.h.in + ${CMAKE_SOURCE_DIR}/cmake.h) + +add_subdirectory (src) +if (EXISTS ${CMAKE_SOURCE_DIR}/test) + add_subdirectory (test EXCLUDE_FROM_ALL) +endif (EXISTS ${CMAKE_SOURCE_DIR}/test) + diff --git a/src/libshared/ChangeLog b/src/libshared/ChangeLog new file mode 100644 index 0000000..12ab900 --- /dev/null +++ b/src/libshared/ChangeLog @@ -0,0 +1,89 @@ +master/HEAD + +- TI-53 Fix musl-libc compatibility + (thanks to Toyam Cox). +- Define PATH_MAX if it's not defined + (thanks to Iain R. Learmonth). +- Removed 'std::' from stdtoimax call. + (thanks to fornwall). +- When Lexer::noOperator () is called, prevent ::isWord boundaries from being + comprised of operators. +- Added Pig::getCharacter, which was oddly missing. +- Added 'Tree' class. +- Added FS error handling for POSIX call failure. +- Updated Timer class to use std::chrono. +- Args now tolerates undeclared option queries. +- Added case (in)sentitive find functions. +- Pig no longer makes a copy of the input string. +- Fixed bug where Pig::getUntil included the terminator if it was the last + character. +- Table now uses the correct include latch. +- Migrated obfuscateText from Taskwarrior. +- Added unicodeHorizontalWhitespace and unicodeVerticalWhitespace. +- Added unicodePunctuation. +- Added unicodeAlpha. +- Added osName. +- Duration::formatVague can now pad all values to the same length. +- Combined JSON.h, JSON2.h, eliminated duplicate encode/decode implementations. +- Table::addRow{,Odd,Even} allows a user-specified notion of 'odd' row. +- Added isIPv4Address and isIPv6Address. +- Added PEG parser. +- Added Packrat parser. +- Datetime/Duration can now parse dates from an embedded string, with negative + lookahead. +- Table supports unwrapped columns. +- Table supports colored columns. + +tasksh-1.1.0 (2016-09-05) +anomaly-1.1.0 (2016-09-04) + +- TD-120 Missing cmakedefine for HAVE_GET_CURRENT_DIR_NAME + (Thanks to Jörg Krause, Ben Boeckel). +- TW-1845 Cygwin build fails, missing get_current_dir_name + (thanks to hosaka). +- Lexer can now disable individual token types. +- Pig is more careful about string bounds in ::peek. +- Pig can extract substrings. +- FS now has strict error handling, requiring that file existence is checked before + readability. + +timew-1.0.0 (2016-08-17) + +- TI-30 10:00am isn't recognized as date + (thanks to Yurї Videneev). +- Datetime::weekStart set to 1 (Monday), per ISO-8601. +- Datetime no longer users 23:59:59 as EOD, but 24:00:00. All date ranges should + therefore be [...) instead of [...]. +- Datetime now uses whole days, not 86400 seconds for calculating date offsets. +- Datetime now properly calculates day names when looking backwards. +- Datetime considerѕ forwards/backwards when calculating informal time. + +clog-1.3.0 (2016-06-27) + +- TW-1741 Warning "ignoring return value of ‘int ftruncate" while doing make on + xubuntu15.10 + (thanks to Sunil Joshi). +- TW-1807 dateformat lacks a flag to display day of week + (thanks to Ellington Santos). +- Bug '12pm' was getting 12 hours added because of the 'pm', which is wrong. +- Added 'juhannus' as a synonym for 'midsommarafton' + (thanks to Lynoure Braakman). +- Added 'join' function. +- Added 'str_replace' function. +- Added Datetime support for informal time, '8am', '2:30p'. +- Added 'JSON2' SAX parser. +- Introduced the new shared submodule. +- Added Datetime support for 'socq', 'eocq', 'socy', 'eocy'. +- Added Composite object. +- Added Palette object. +- Added Lexer object. +- Added Msg::set overload. + +Design completed 2015-12-XX +Project started 2015-11-29 + +------ current release --------------------------- + +Note: There are no releases. There are tags applied when a project is released. + +------ start ----------------------------------- diff --git a/src/libshared/LICENSE b/src/libshared/LICENSE new file mode 100644 index 0000000..4cd3104 --- /dev/null +++ b/src/libshared/LICENSE @@ -0,0 +1,23 @@ +libshared + +Copyright 2015 - 2017, Paul Beckingham, Federico Hernandez. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +http://www.opensource.org/licenses/mit-license.php diff --git a/src/libshared/cmake.h.in b/src/libshared/cmake.h.in new file mode 100644 index 0000000..92d4b9b --- /dev/null +++ b/src/libshared/cmake.h.in @@ -0,0 +1,36 @@ +/* cmake.h.in. Creates cmake.h during a cmake run */ + +/* Package information */ +#define PACKAGE "${PACKAGE}" +#define VERSION "${VERSION}" +#define PACKAGE_BUGREPORT "${PACKAGE_BUGREPORT}" +#define PACKAGE_NAME "${PACKAGE_NAME}" +#define PACKAGE_TARNAME "${PACKAGE_TARNAME}" +#define PACKAGE_VERSION "${PACKAGE_VERSION}" +#define PACKAGE_STRING "${PACKAGE_STRING}" + +#define CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" + +/* Compiling platform */ +#cmakedefine LINUX +#cmakedefine DARWIN +#cmakedefine CYGWIN +#cmakedefine FREEBSD +#cmakedefine OPENBSD +#cmakedefine NETBSD +#cmakedefine SOLARIS +#cmakedefine KFREEBSD +#cmakedefine GNUHURD +#cmakedefine UNKNOWN + +/* Found tm.tm_gmtoff struct member */ +#cmakedefine HAVE_TM_GMTOFF + +/* Found st.st_birthtime struct member */ +#cmakedefine HAVE_ST_BIRTHTIME + +/* Functions */ +#cmakedefine HAVE_GET_CURRENT_DIR_NAME +#cmakedefine HAVE_TIMEGM +#cmakedefine HAVE_UUID_UNPARSE_LOWER + diff --git a/src/libshared/cmake/CXXSniffer.cmake b/src/libshared/cmake/CXXSniffer.cmake new file mode 100644 index 0000000..9606226 --- /dev/null +++ b/src/libshared/cmake/CXXSniffer.cmake @@ -0,0 +1,51 @@ +message ("-- Configuring C++11") +message ("-- System: ${CMAKE_SYSTEM_NAME}") + +include (CheckCXXCompilerFlag) + +# NOTE: Phase out -std=gnu++0x and --std=c++0x as soon as realistically possible. +CHECK_CXX_COMPILER_FLAG("-std=c++11" _HAS_CXX11) +CHECK_CXX_COMPILER_FLAG("-std=c++0x" _HAS_CXX0X) +CHECK_CXX_COMPILER_FLAG("-std=gnu++0x" _HAS_GNU0X) + +if (_HAS_CXX11) + set (_CXX11_FLAGS "-std=c++11") +elseif (_HAS_CXX0X) + message (WARNING "Enabling -std=c++0x draft compile flag. Your compiler does not support the standard '-std=c++11' option. Consider upgrading.") + set (_CXX11_FLAGS "-std=c++0x") +elseif (_HAS_GNU0X) + message (WARNING "Enabling -std=gnu++0x draft compile flag. Your compiler does not support the standard '-std=c++11' option. Consider upgrading.") + set (_CXX11_FLAGS "-std=gnu++0x") +else (_HAS_CXX11) + message (FATAL_ERROR "C++11 support missing. Try upgrading your C++ compiler. If you have a good reason for using an outdated compiler, please let us know at support@taskwarrior.org.") +endif (_HAS_CXX11) + +if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + set (LINUX true) +elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set (DARWIN true) + set (_CXX11_FLAGS "${_CXX11_FLAGS} -stdlib=libc++") +elseif (${CMAKE_SYSTEM_NAME} MATCHES "kFreeBSD") + set (KFREEBSD true) +elseif (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") + set (FREEBSD true) +elseif (${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD") + set (OPENBSD true) +elseif (${CMAKE_SYSTEM_NAME} MATCHES "NetBSD") + set (NETBSD true) +elseif (${CMAKE_SYSTEM_NAME} MATCHES "SunOS") + set (SOLARIS true) +elseif (${CMAKE_SYSTEM_NAME} STREQUAL "GNU") + set (GNUHURD true) +elseif (${CMAKE_SYSTEM_NAME} STREQUAL "CYGWIN") + set (CYGWIN true) + # NOTE: Not setting -std=gnu++0x leads to compile errors even with + # GCC 4.8.3, and debugging those leads to insanity. Adding this + # workaround instead of fixing Cygwin. + set (_CXX11_FLAGS "-std=gnu++0x") +else (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + set (UNKNOWN true) +endif (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + +set (CMAKE_CXX_FLAGS "${_CXX11_FLAGS} ${CMAKE_CXX_FLAGS}") +set (CMAKE_CXX_FLAGS "-Wall -Wextra -Wsign-compare -Wreturn-type ${CMAKE_CXX_FLAGS}") diff --git a/src/libshared/src/Args.cpp b/src/libshared/src/Args.cpp new file mode 100644 index 0000000..0c396f0 --- /dev/null +++ b/src/libshared/src/Args.cpp @@ -0,0 +1,246 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2012 - 2017, Paul Beckingham, Federico Hernandez. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// http://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +void Args::addOption (const std::string& name, bool defaultValue) +{ + _options[name] = defaultValue; + _optionCount[name] = 0; +} + +//////////////////////////////////////////////////////////////////////////////// +void Args::addNamed (const std::string& name, const std::string& defaultValue) +{ + _named[name] = defaultValue; +} + +//////////////////////////////////////////////////////////////////////////////// +void Args::limitPositionals (int limit) +{ + _limit = limit; +} + +//////////////////////////////////////////////////////////////////////////////// +void Args::enableNegatives () +{ + _negatives = true; +} + +//////////////////////////////////////////////////////////////////////////////// +void Args::scan (int argc, const char** argv) +{ + for (int i = 1; i < argc; ++i) + { + // Is an option or named arg. + if (argv[i][0] == '-' && strlen (argv[i]) > 1) + { + auto name = ltrim (argv[i], "-"); + + std::string canonical; + if (canonicalizeOption (name, canonical)) + { + bool negated = _negatives && name.find ("no") == 0; + _options[canonical] = ! negated; + _optionCount[canonical]++; + } + + else if (canonicalizeNamed (name, canonical)) + { + if (i >= argc) + throw std::string ("Argument '" + canonical + "' has no value."); + + ++i; + _named[canonical] = argv[i]; + } + + else + throw std::string ("Unrecognized argument '" + name + "'."); + } + + // Or a positional. + else + { + _positionals.push_back (argv[i]); + if (_limit != -1 && + static_cast (_positionals.size ()) > _limit) + throw std::string ("Too many positional arguments."); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +bool Args::getOption (const std::string& name) const +{ + if (_options.find (name) == _options.end ()) + return false; + + return _options.at (name); +} + +//////////////////////////////////////////////////////////////////////////////// +int Args::getOptionCount (const std::string& name) const +{ + if (_optionCount.find (name) == _optionCount.end ()) + return false; + + return _optionCount.at (name); +} + +//////////////////////////////////////////////////////////////////////////////// +std::string Args::getNamed (const std::string& name) const +{ + if (_named.find (name) == _named.end ()) + return ""; + + return _named.at (name); +} + +//////////////////////////////////////////////////////////////////////////////// +int Args::getPositionalCount () const +{ + return static_cast (_positionals.size ()); +} + +//////////////////////////////////////////////////////////////////////////////// +std::string Args::getPositional (int n) const +{ + return _positionals.at (n); +} + +//////////////////////////////////////////////////////////////////////////////// +// Assuming "abc" is a declared option, support the following canonicalization: +// +// abc --> abc (exact match always canonicalizes) +// ab --> abc (if unique) +// a --> abc (if unique) +// noabc --> abc (exact negation match always canonicalizes) +// noab --> abc (if unique) +// noa --> abc (if unique) +// +bool Args::canonicalizeOption (const std::string& partial, std::string& canonical) const +{ + bool negated = _negatives && partial.find ("no") == 0; + + // Look for exact positive or negative matches first, which should succeed + // regardless of any longer partial matches. + if (_options.find (partial) != _options.end ()) + { + canonical = partial; + return true; + } + + if (negated && + _options.find (partial.substr (2)) != _options.end ()) + { + canonical = partial.substr (2); + return true; + } + + // Iterate over all options, and look for partial matches. If there is only + // one, we have canonicalization. + std::vector candidates; + for (const auto& option : _options) + { + if (option.first.find (partial) == 0 || + (negated && option.first.find (partial, 2) == 2)) + { + candidates.push_back (option.first); + } + } + + if (candidates.size () == 1) + { + canonical = candidates[0]; + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// Assuming "abc" is a declared name, support the following canonicalization: +// +// abc --> abc (exact match always canonicalizes) +// ab --> abc (if unique) +// a --> abc (if unique) +// +bool Args::canonicalizeNamed (const std::string& partial, std::string& canonical) const +{ + // Look for exact positive or negative matches first, which should succeed + // regardless of longer partial matches. + if (_named.find (partial) != _named.end ()) + { + canonical = partial; + return true; + } + + // Iterate over all options, and look for partial matches. If there is only + // one, we have canonicalization. + std::vector candidates; + for (const auto& name : _named) + if (name.first.find (partial) == 0) + candidates.push_back (name.first); + + if (candidates.size () == 1) + { + canonical = candidates[0]; + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string Args::dump () const +{ + std::stringstream out; + out << "Args\n" + << " Options\n"; + for (const auto& arg : _options) + out << " " << arg.first << " = " << arg.second << " (" << _optionCount.at (arg.first) << ")\n"; + + out << " Named\n"; + for (const auto& arg : _named) + out << " " << arg.first << " = " << arg.second << '\n'; + + out << " Positionals\n" + << " limit = " << _limit << '\n'; + for (const auto& arg : _positionals) + out << " " << arg << '\n'; + + out << " Negatives\n" + << " enabled = " << _negatives << '\n'; + + return out.str (); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/libshared/src/Args.h b/src/libshared/src/Args.h new file mode 100644 index 0000000..84a0070 --- /dev/null +++ b/src/libshared/src/Args.h @@ -0,0 +1,68 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2012 - 2017, Paul Beckingham, Federico Hernandez. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// http://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDED_ARGS +#define INCLUDED_ARGS + +#include +#include +#include + +class Args +{ +public: + Args () = default; + + void addOption (const std::string&, bool defaultValue = true); + void addNamed (const std::string&, const std::string& defaultValue = ""); + void limitPositionals (int); + void enableNegatives (); + + void scan (int, const char**); + + bool getOption (const std::string&) const; + int getOptionCount (const std::string&) const; + std::string getNamed (const std::string&) const; + int getPositionalCount () const; + std::string getPositional (int) const; + + std::string dump () const; + +private: + bool canonicalizeOption (const std::string&, std::string&) const; + bool canonicalizeNamed (const std::string&, std::string&) const; + +private: + std::map _options {}; + std::map _optionCount {}; + std::map _named {}; + std::vector _positionals {}; + int _limit {-1}; + bool _negatives {false}; +}; + +#endif + diff --git a/src/libshared/src/CMakeLists.txt b/src/libshared/src/CMakeLists.txt new file mode 100644 index 0000000..8b3ec70 --- /dev/null +++ b/src/libshared/src/CMakeLists.txt @@ -0,0 +1,65 @@ +cmake_minimum_required (VERSION 2.8) +include_directories (${CMAKE_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/src) + +set (shared_HEADERS Args.h + Color.h + Composite.h + Configuration.h + Datetime.h + Duration.h + FS.h + JSON.h + Lexer.h + Log.h + Msg.h + Packrat.h + Palette.h + PEG.h + Pig.h + RX.h + Table.h + Timer.h + Tree.h + shared.h + format.h + unicode.h + utf8.h) + +set (shared_SRCS Args.cpp + Color.cpp + Composite.cpp + Configuration.cpp + Datetime.cpp + Duration.cpp + FS.cpp + JSON.cpp + Lexer.cpp + Log.cpp + Msg.cpp + Packrat.cpp + Palette.cpp + PEG.cpp + Pig.cpp + RX.cpp + SAX.cpp + Table.cpp + Timer.cpp + Tree.cpp + format.cpp + ip.cpp + shared.cpp + unicode.cpp + utf8.cpp + wcwidth6.cpp + ${shared_HEADERS}) + +add_library (shared STATIC ${shared_SRCS}) + +set (CMAKE_INSTALL_LIBDIR lib CACHE PATH "Output directory for libraries") +install (TARGETS shared DESTINATION lib) +install (FILES ${shared_HEADERS} DESTINATION include) + +add_executable (lex_executable lex.cpp) +target_link_libraries (lex_executable shared) +set_property (TARGET lex_executable PROPERTY OUTPUT_NAME "lex") diff --git a/src/libshared/src/Color.cpp b/src/libshared/src/Color.cpp new file mode 100644 index 0000000..0673d81 --- /dev/null +++ b/src/libshared/src/Color.cpp @@ -0,0 +1,682 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2017, Paul Beckingham, Federico Hernandez. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// http://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include + +// uint to string lookup table for Color::_colorize() +// _colorize() gets called _a lot_, having this lookup table is a cheap +// performance optimization. +const char *colorstring[] = { + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", + "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", + "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", + "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", + "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", + "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", + "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", + "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", + "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", + "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", + "100", "101", "102", "103", "104", "105", "106", "107", "108", "109", + "110", "111", "112", "113", "114", "115", "116", "117", "118", "119", + "120", "121", "122", "123", "124", "125", "126", "127", "128", "129", + "130", "131", "132", "133", "134", "135", "136", "137", "138", "139", + "140", "141", "142", "143", "144", "145", "146", "147", "148", "149", + "150", "151", "152", "153", "154", "155", "156", "157", "158", "159", + "160", "161", "162", "163", "164", "165", "166", "167", "168", "169", + "170", "171", "172", "173", "174", "175", "176", "177", "178", "179", + "180", "181", "182", "183", "184", "185", "186", "187", "188", "189", + "190", "191", "192", "193", "194", "195", "196", "197", "198", "199", + "200", "201", "202", "203", "204", "205", "206", "207", "208", "209", + "210", "211", "212", "213", "214", "215", "216", "217", "218", "219", + "220", "221", "222", "223", "224", "225", "226", "227", "228", "229", + "230", "231", "232", "233", "234", "235", "236", "237", "238", "239", + "240", "241", "242", "243", "244", "245", "246", "247", "248", "249", + "250", "251", "252", "253", "254", "255" +}; + +//////////////////////////////////////////////////////////////////////////////// +static struct +{ + Color::color_id id; + std::string english_name; + int index; // offset red=3 (therefore fg=33, bg=43) +} allColors[] = +{ + // Color.h enum English Index + { Color::nocolor, "none", 0}, + { Color::black, "black", 1}, // fg 29+0 bg 39+0 + { Color::red, "red", 2}, + { Color::green, "green", 3}, + { Color::yellow, "yellow", 4}, + { Color::blue, "blue", 5}, + { Color::magenta, "magenta", 6}, + { Color::cyan, "cyan", 7}, + { Color::white, "white", 8}, + +}; + +#define NUM_COLORS (sizeof (allColors) / sizeof (allColors[0])) + +//////////////////////////////////////////////////////////////////////////////// +Color::Color () +: _value (0) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +Color::Color (const Color& other) +{ + _value = other._value; +} + +//////////////////////////////////////////////////////////////////////////////// +Color::Color (unsigned int c) +: _value (0) +{ + if (!(c & _COLOR_HASFG)) _value &= ~_COLOR_FG; + if (!(c & _COLOR_HASBG)) _value &= ~_COLOR_BG; + + _value = c & (_COLOR_256 | _COLOR_HASBG | _COLOR_HASFG |_COLOR_UNDERLINE | + _COLOR_INVERSE | _COLOR_BOLD | _COLOR_BRIGHT | _COLOR_BG | + _COLOR_FG); +} + +//////////////////////////////////////////////////////////////////////////////// +// Supports the following constructs: +// [bright] [color] [on color] [bright] [underline] +// +// Where [color] is one of: +// black +// red +// ... +// grayN 0 <= N <= 23 fg 38;5;232 + N bg 48;5;232 + N +// greyN 0 <= N <= 23 fg 38;5;232 + N bg 48;5;232 + N +// colorN 0 <= N <= 255 fg 38;5;N bg 48;5;N +// rgbRGB 0 <= R,G,B <= 5 fg 38;5;16 + R*36 + G*6 + B bg 48;5;16 + R*36 + G*6 + B +Color::Color (const std::string& spec) +: _value (0) +{ + // Split spec into words. + auto words = split (spec, ' '); + + // Construct the color as two separate colors, then blend them later. This + // make it possible to declare a color such as "color1 on black", and have + // the upgrade work properly. + unsigned int fg_value = 0; + unsigned int bg_value = 0; + + bool bg = false; + int index; + for (auto& word : words) + { + word = lowerCase (trim (word)); + + if (word == "bold") fg_value |= _COLOR_BOLD; + else if (word == "bright") bg_value |= _COLOR_BRIGHT; + else if (word == "underline") fg_value |= _COLOR_UNDERLINE; + else if (word == "inverse") fg_value |= _COLOR_INVERSE; + else if (word == "on") bg = true; + + // X where X is one of black, red, blue ... + else if ((index = find (word)) != -1) + { + if (index) + { + if (bg) + { + bg_value |= _COLOR_HASBG; + bg_value |= index << 8; + } + else + { + fg_value |= _COLOR_HASFG; + fg_value |= index; + } + } + } + + // greyN/grayN, where 0 <= N <= 23. + else if (! word.compare (0, 4, "grey", 4) || + ! word.compare (0, 4, "gray", 4)) + { + index = strtol (word.substr (4).c_str (), nullptr, 10); + if (index < 0 || index > 23) + throw format ("The color '{1}' is not recognized.", word); + + if (bg) + { + bg_value |= _COLOR_HASBG; + bg_value |= (index + 232) << 8; + bg_value |= _COLOR_256; + } + else + { + fg_value |= _COLOR_HASFG; + fg_value |= index + 232; + fg_value |= _COLOR_256; + } + } + + // rgbRGB, where 0 <= R,G,B <= 5. + else if (! word.compare (0, 3, "rgb", 3)) + { + index = strtol (word.substr (3).c_str (), nullptr, 10); + if (word.length () != 6 || + index < 0 || index > 555) + throw format ("The color '{1}' is not recognized.", word); + + int r = strtol (word.substr (3, 1).c_str (), nullptr, 10); + int g = strtol (word.substr (4, 1).c_str (), nullptr, 10); + int b = strtol (word.substr (5, 1).c_str (), nullptr, 10); + if (r < 0 || r > 5 || + g < 0 || g > 5 || + b < 0 || b > 5) + throw format ("The color '{1}' is not recognized.", word); + + index = 16 + r*36 + g*6 + b; + + if (bg) + { + bg_value |= _COLOR_HASBG; + bg_value |= index << 8; + bg_value |= _COLOR_256; + } + else + { + fg_value |= _COLOR_HASFG; + fg_value |= index; + fg_value |= _COLOR_256; + } + } + + // colorN, where 0 <= N <= 255. + else if (! word.compare (0, 5, "color", 5)) + { + index = strtol (word.substr (5).c_str (), nullptr, 10); + if (index < 0 || index > 255) + throw format ("The color '{1}' is not recognized.", word); + + upgrade (); + + if (bg) + { + bg_value |= _COLOR_HASBG; + bg_value |= index << 8; + bg_value |= _COLOR_256; + } + else + { + fg_value |= _COLOR_HASFG; + fg_value |= index; + fg_value |= _COLOR_256; + } + } + else if (word != "") + throw format ("The color '{1}' is not recognized.", word); + } + + // Now combine the fg and bg into a single color. + _value = fg_value; + blend (Color (bg_value)); +} + +//////////////////////////////////////////////////////////////////////////////// +Color::Color (color_id fg) +: _value (0) +{ + if (fg != Color::nocolor) + { + _value |= _COLOR_HASFG; + _value |= fg; + } +} + +//////////////////////////////////////////////////////////////////////////////// +Color::Color (color_id fg, color_id bg, bool underline, bool bold, bool bright) +: _value (0) +{ + _value |= ((underline ? 1 : 0) << 18) + | ((bold ? 1 : 0) << 17) + | ((bright ? 1 : 0) << 16); + + if (bg != Color::nocolor) + { + _value |= _COLOR_HASBG; + _value |= (bg << 8); + } + + if (fg != Color::nocolor) + { + _value |= _COLOR_HASFG; + _value |= fg; + } +} + +//////////////////////////////////////////////////////////////////////////////// +Color::operator std::string () const +{ + std::string description; + if (_value & _COLOR_BOLD) description += "bold"; + + if (_value & _COLOR_UNDERLINE) + description += std::string (description.length () ? " " : "") + "underline"; + + if (_value & _COLOR_INVERSE) + description += std::string (description.length () ? " " : "") + "inverse"; + + if (_value & _COLOR_HASFG) + description += std::string (description.length () ? " " : "") + fg (); + + if (_value & _COLOR_HASBG) + { + description += std::string (description.length () ? " " : "") + "on"; + + if (_value & _COLOR_BRIGHT) + description += std::string (description.length () ? " " : "") + "bright"; + + description += " " + bg (); + } + + return description; +} + +//////////////////////////////////////////////////////////////////////////////// +Color::operator int () const +{ + return (int) _value; +} + +//////////////////////////////////////////////////////////////////////////////// +// If 'other' has styles that are compatible, merge them into this. Colors in +// other take precedence. +void Color::blend (const Color& other) +{ + if (!other.nontrivial ()) + return; + + Color c (other); + _value |= (c._value & _COLOR_UNDERLINE); // Always inherit underline. + _value |= (c._value & _COLOR_INVERSE); // Always inherit inverse. + + // 16 <-- 16. + if (!(_value & _COLOR_256) && + !(c._value & _COLOR_256)) + { + _value |= (c._value & _COLOR_BOLD); // Inherit bold. + _value |= (c._value & _COLOR_BRIGHT); // Inherit bright. + + if (c._value & _COLOR_HASFG) + { + _value |= _COLOR_HASFG; // There is now a color. + _value &= ~_COLOR_FG; // Remove previous color. + _value |= (c._value & _COLOR_FG); // Apply other color. + } + + if (c._value & _COLOR_HASBG) + { + _value |= _COLOR_HASBG; // There is now a color. + _value &= ~_COLOR_BG; // Remove previous color. + _value |= (c._value & _COLOR_BG); // Apply other color. + } + + return; + } + else + { + // Upgrade either color, if necessary. + if (!(_value & _COLOR_256)) upgrade (); + if (!(c._value & _COLOR_256)) c.upgrade (); + + // 256 <-- 256. + if (c._value & _COLOR_HASFG) + { + _value |= _COLOR_HASFG; // There is now a color. + _value &= ~_COLOR_FG; // Remove previous color. + _value |= (c._value & _COLOR_FG); // Apply other color. + } + + if (c._value & _COLOR_HASBG) + { + _value |= _COLOR_HASBG; // There is now a color. + _value &= ~_COLOR_BG; // Remove previous color. + _value |= (c._value & _COLOR_BG); // Apply other color. + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +void Color::upgrade () +{ + if (!(_value & _COLOR_256)) + { + if (_value & _COLOR_HASFG) + { + bool bold = _value & _COLOR_BOLD; + unsigned int fg = _value & _COLOR_FG; + _value &= ~_COLOR_FG; + _value &= ~_COLOR_BOLD; + _value |= (bold ? fg + 7 : fg - 1); + } + + if (_value & _COLOR_HASBG) + { + bool bright = _value & _COLOR_BRIGHT; + unsigned int bg = (_value & _COLOR_BG) >> 8; + _value &= ~_COLOR_BG; + _value &= ~_COLOR_BRIGHT; + _value |= (bright ? bg + 7 : bg - 1) << 8; + } + + _value |= _COLOR_256; + } +} + +//////////////////////////////////////////////////////////////////////////////// +std::string Color::colorize (const std::string& input) const +{ + std::string result; + _colorize (result, input); + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// Sample color codes: +// red \033[31m +// bold red \033[91m +// underline red \033[4;31m +// bold underline red \033[1;4;31m +// +// on red \033[41m +// on bright red \033[101m +// +// 256 fg \033[38;5;Nm +// 256 bg \033[48;5;Nm +void Color::_colorize (std::string &result, const std::string& input) const +{ + if (!nontrivial ()) + { + result += input; + return; + } + + int count = 0; + + // 256 color + if (_value & _COLOR_256) + { + if (_value & _COLOR_UNDERLINE) + result += "\033[4m"; + + if (_value & _COLOR_INVERSE) + result += "\033[7m"; + + if (_value & _COLOR_HASFG) + { + result += "\033[38;5;"; + result += colorstring[(_value & _COLOR_FG)]; + result += 'm'; + } + + if (_value & _COLOR_HASBG) + { + result += "\033[48;5;"; + result += colorstring[((_value & _COLOR_BG) >> 8)]; + result += 'm'; + } + + result += input; + result += "\033[0m"; + } + + // 16 color + else + { + result += "\033["; + + if (_value & _COLOR_BOLD) + { + if (count++) result += ';'; + result += '1'; + } + + if (_value & _COLOR_UNDERLINE) + { + if (count++) result += ';'; + result += '4'; + } + + if (_value & _COLOR_INVERSE) + { + if (count++) result += ';'; + result += '7'; + } + + if (_value & _COLOR_HASFG) + { + if (count++) result += ';'; + result += colorstring[(29 + (_value & _COLOR_FG))]; + } + + if (_value & _COLOR_HASBG) + { + if (count++) result += ';'; + result += colorstring[((_value & _COLOR_BRIGHT ? 99 : 39) + ((_value & _COLOR_BG) >> 8))]; + } + + result += 'm'; + result += input; + result += "\033[0m"; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Remove color codes from a string. +std::string Color::strip (const std::string& input) +{ + int length = input.length (); + bool inside = false; + std::string output; + for (int i = 0; i < length; ++i) + { + if (inside) + { + if (input[i] == 'm') + inside = false; + } + else + { + if (input[i] == 033) + inside = true; + else + output += input[i]; + } + } + + return output; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string Color::colorize (const std::string& input, const std::string& spec) +{ + Color c (spec); + return c.colorize (input); +} + +//////////////////////////////////////////////////////////////////////////////// +std::string Color::code () const +{ + if (! nontrivial ()) + return ""; + + std::string result; + + // 256 color + if (_value & _COLOR_256) + { + if (_value & _COLOR_UNDERLINE) + result += "\033[4m"; + + if (_value & _COLOR_INVERSE) + result += "\033[7m"; + + if (_value & _COLOR_HASFG) + { + result += "\033[38;5;"; + result += colorstring[(_value & _COLOR_FG)]; + result += 'm'; + } + + if (_value & _COLOR_HASBG) + { + result += "\033[48;5;"; + result += colorstring[((_value & _COLOR_BG) >> 8)]; + result += 'm'; + } + } + + // 16 color + else + { + int count = 0; + result += "\033["; + + if (_value & _COLOR_BOLD) + { + if (count++) result += ';'; + result += '1'; + } + + if (_value & _COLOR_UNDERLINE) + { + if (count++) result += ';'; + result += '4'; + } + + if (_value & _COLOR_INVERSE) + { + if (count++) result += ';'; + result += '7'; + } + + if (_value & _COLOR_HASFG) + { + if (count++) result += ';'; + result += colorstring[(29 + (_value & _COLOR_FG))]; + } + + if (_value & _COLOR_HASBG) + { + if (count++) result += ';'; + result += colorstring[((_value & _COLOR_BRIGHT ? 99 : 39) + ((_value & _COLOR_BG) >> 8))]; + } + + result += 'm'; + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string Color::end () const +{ + if (nontrivial ()) + return "\033[0m"; + + return ""; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Color::nontrivial () const +{ + return _value != 0 ? true : false; +} + +//////////////////////////////////////////////////////////////////////////////// +int Color::find (const std::string& input) +{ + for (unsigned int i = 0; i < NUM_COLORS; ++i) + if (allColors[i].english_name == input) + return (int) i; + + return -1; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string Color::fg () const +{ + int index = _value & _COLOR_FG; + + if (_value & _COLOR_256) + { + if (_value & _COLOR_HASFG) + { + std::stringstream s; + s << "color" << (_value & _COLOR_FG); + return s.str (); + } + } + else + { + for (unsigned int i = 0; i < NUM_COLORS; ++i) + if (allColors[i].index == index) + return allColors[i].english_name; + } + + return ""; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string Color::bg () const +{ + int index = (_value & _COLOR_BG) >> 8; + + if (_value & _COLOR_256) + { + if (_value & _COLOR_HASBG) + { + std::stringstream s; + s << "color" << ((_value & _COLOR_BG) >> 8); + return s.str (); + } + } + else + { + for (unsigned int i = 0; i < NUM_COLORS; ++i) + if (allColors[i].index == index) + return allColors[i].english_name; + } + + return ""; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/libshared/src/Color.h b/src/libshared/src/Color.h new file mode 100644 index 0000000..d8393c2 --- /dev/null +++ b/src/libshared/src/Color.h @@ -0,0 +1,78 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2017, Paul Beckingham, Federico Hernandez. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// http://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDED_COLOR +#define INCLUDED_COLOR + +#include + +#define _COLOR_INVERSE 0x00400000 // Inverse attribute. +#define _COLOR_256 0x00200000 // 256-color mode. +#define _COLOR_HASBG 0x00100000 // Has background color (all values taken). +#define _COLOR_HASFG 0x00080000 // Has foreground color (all values taken). +#define _COLOR_UNDERLINE 0x00040000 // General underline attribute. +#define _COLOR_BOLD 0x00020000 // 16-color bold attribute. +#define _COLOR_BRIGHT 0x00010000 // 16-color bright background attribute. +#define _COLOR_BG 0x0000FF00 // 8-bit background color index. +#define _COLOR_FG 0x000000FF // 8-bit foreground color index. + +class Color +{ +public: + enum color_id {nocolor = 0, black, red, green, yellow, blue, magenta, cyan, white}; + + Color (); + Color (const Color&); + Color (unsigned int); // 256 | INVERSE | UNDERLINE | BOLD | BRIGHT | (BG << 8) | FG + Color (const std::string&); // "red on bright black" + Color (color_id); // fg. + Color (color_id, color_id, bool, bool, bool); // fg, bg, underline, bold, bright + operator std::string () const; + operator int () const; + + void upgrade (); + void blend (const Color&); + + std::string colorize (const std::string&) const; + static std::string colorize (const std::string&, const std::string&); + void _colorize (std::string&, const std::string&) const; + static std::string strip (const std::string&); + + std::string code () const; + std::string end () const; + + bool nontrivial () const; + +private: + int find (const std::string&); + std::string fg () const; + std::string bg () const; + +private: + unsigned int _value; +}; + +#endif diff --git a/src/libshared/src/Composite.cpp b/src/libshared/src/Composite.cpp new file mode 100644 index 0000000..98bd49c --- /dev/null +++ b/src/libshared/src/Composite.cpp @@ -0,0 +1,148 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 - 2017, Paul Beckingham, Federico Hernandez. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// http://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +// Initially assume no text, but infinite virtual space. +// +// Ållow overlay placement of arbitrary text at any offset, real or virtual, and +// using a specific color. +// +// For example: +// Composite c; +// c.add ("aaaaaaaaaa", 2, Color ("...")); // Layer 1 +// c.add ("bbbbb", 5, Color ("...")); // Layer 2 +// c.add ("c", 15, Color ("...")); // Layer 3 +// +// _layers = { std::make_tuple ("aaaaaaaaaa", 2, Color ("...")), +// std::make_tuple ("bbbbb", 5, Color ("...")), +// std::make_tuple ("c", 15, Color ("..."))}; +// +void Composite::add ( + const std::string& text, + std::string::size_type offset, + const Color& color) +{ + _layers.push_back (std::make_tuple (text, offset, color)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Merge the layers of text and color into one string. +// +// For example: +// Composite c; +// c.add ("aaaaaaaaaa", 2, Color ("...")); // Layer 1 +// c.add ("bbbbb", 5, Color ("...")); // Layer 2 +// c.add ("c", 15, Color ("...")); // Layer 3 +// +// _layers = { std::make_tuple ("aaaaaaaaaa", 2, Color ("...")), +// std::make_tuple ("bbbbb", 5, Color ("...")), +// std::make_tuple ("c", 15, Color ("..."))}; +// +// Arrange strings conceptually: +// 111111 +// 0123456789012345 // Position +// +// aaaaaaaaaa // Layer 1 +// bbbbb // Layer 2 +// c // Layer 3 +// +// Walk all strings left to right, selecting the character and color from the +// highest numbered layer. Emit color codes only on edge detection. +// +std::string Composite::str () const +{ + // The strings are broken into a vector of int, for UTF8 support. + std::vector characters; + std::vector colors; + for (unsigned int layer = 0; layer < _layers.size (); ++layer) + { + auto text = std::get <0> (_layers[layer]); + auto offset = std::get <1> (_layers[layer]); + auto len = utf8_text_length (text); + + // Make sure the vectors are large enough to support a write operator[]. + if (characters.size () < offset + len) + { + characters.resize (offset + len, 32); + colors.resize (offset + len, 0); + } + + // Copy in the layer characters and color indexes. + std::string::size_type cursor = 0; + int character; + int count = 0; + while ((character = utf8_next_char (text, cursor))) + { + characters[offset + count] = character; + colors [offset + count] = layer + 1; + ++count; + } + } + + // Now walk the character and color vector, emitting every character and + // every detected color change. + std::stringstream out; + int prev_color = 0; + for (unsigned int i = 0; i < characters.size (); ++i) + { + // A change in color triggers a code emit. + if (prev_color != colors[i]) + { + if (prev_color) + out << std::get <2> (_layers[prev_color - 1]).end (); + + if (colors[i]) + out << std::get <2> (_layers[colors[i] - 1]).code (); + else + out << std::get <2> (_layers[prev_color - 1]).end (); + + prev_color = colors[i]; + } + + out << utf8_character (characters[i]); + } + + // Terminate the color codes, if necessary. + if (prev_color) + out << std::get <2> (_layers[prev_color - 1]).end (); + + return out.str (); +} + +//////////////////////////////////////////////////////////////////////////////// +// So the same instance can be reused. +void Composite::clear () +{ + _layers.clear (); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/libshared/src/Composite.h b/src/libshared/src/Composite.h new file mode 100644 index 0000000..7309c51 --- /dev/null +++ b/src/libshared/src/Composite.h @@ -0,0 +1,47 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 - 2017, Paul Beckingham, Federico Hernandez. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// http://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDED_COMPOSITE +#define INCLUDED_COMPOSITE + +#include +#include +#include +#include + +class Composite +{ +public: + Composite () = default; + void add (const std::string&, std::string::size_type, const Color&); + std::string str () const; + void clear (); + +private: + std::vector > _layers; +}; + +#endif diff --git a/src/libshared/src/Configuration.cpp b/src/libshared/src/Configuration.cpp new file mode 100644 index 0000000..fc9331c --- /dev/null +++ b/src/libshared/src/Configuration.cpp @@ -0,0 +1,323 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2017, Paul Beckingham, Federico Hernandez. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// http://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +bool setVariableInFile ( + const std::string& file, + const std::string& name, + const std::string& value) +{ + // Read the file contents. + std::vector contents; + File::read (file, contents); + + bool found = false; + bool change = false; + + for (auto& line : contents) + { + // If there is a comment on the line, it must follow the pattern. + auto comment = line.find ('#'); + auto pos = line.find (name + '='); + + if (pos != std::string::npos && + (comment == std::string::npos || + comment > pos)) + { + found = true; + if (comment != std::string::npos) + line = name + '=' + value + ' ' + line.substr (comment); + else + line = name + '=' + value; + + change = true; + } + } + + // Not found, so append instead. + if (! found) + { + contents.push_back (name + '=' + value); + change = true; + } + + if (change) + File::write (file, contents); + + return change; +} + +//////////////////////////////////////////////////////////////////////////////// +bool unsetVariableInFile ( + const std::string& file, + const std::string& name) +{ + // Read configuration file. + std::vector contents; + File::read (file, contents); + + bool change = false; + + for (auto line = contents.begin (); line != contents.end (); ) + { + bool lineDeleted = false; + + // If there is a comment on the line, it must follow the pattern. + auto comment = line->find ('#'); + auto pos = line->find (name + '='); + + if (pos != std::string::npos && + (comment == std::string::npos || + comment > pos)) + { + // vector::erase method returns a valid iterator to the next object + line = contents.erase (line); + lineDeleted = true; + change = true; + } + + if (! lineDeleted) + line++; + } + + if (change) + File::write (file, contents); + + return change; +} + +//////////////////////////////////////////////////////////////////////////////// +// Read the Configuration file and populate the *this map. The file format is +// simply lines with name=value pairs. Whitespace between name, = and value is +// not tolerated, but blank lines and comments starting with # are allowed. +// +// Nested files are now supported, with the following construct: +// include /absolute/path/to/file +// +void Configuration::load (const std::string& file, int nest /* = 1 */) +{ + if (nest > 10) + throw std::string ("Configuration files may only be nested to 10 levels."); + + // Read the file, then parse the contents. + File config (file); + + if (nest == 1) + _original_file = config; + + if (config.exists () && + config.readable ()) + { + std::string contents; + if (File::read (file, contents) && contents.length ()) + parse (contents, nest); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Write the Configuration file. +void Configuration::save () +{ + std::string contents; + for (const auto& i : *this) + contents += i.first + "=" + i.second + '\n'; + + File::write (_original_file, contents); + _dirty = false; +} + +//////////////////////////////////////////////////////////////////////////////// +void Configuration::parse (const std::string& input, int nest /* = 1 */) +{ + // Shortcut case for default constructor. + if (input.length () == 0) + return; + + // Parse each line. + for (auto& line : split (input, '\n')) + { + // Remove comments. + auto pound = line.find ('#'); + if (pound != std::string::npos) + line = line.substr (0, pound); + + // Skip empty lines. + line = trim (line); + if (line.length () > 0) + { + auto equal = line.find ('='); + if (equal != std::string::npos) + { + std::string key = trim (line.substr (0, equal)); + std::string value = trim (line.substr (equal+1, line.length () - equal)); + + (*this)[key] = json::decode (value); + } + else + { + auto include = line.find ("include"); + if (include != std::string::npos) + { + Path included (trim (line.substr (include + 7))); + if (included.is_absolute ()) + { + if (included.readable ()) + load (included, nest + 1); + else + throw format ("Could not read include file '{1}'.", included._data); + } + else + throw format ("Can only include files with absolute paths, not '{1}'", included._data); + } + else + throw format ("Malformed entry '{1}' in config file.", line); + } + } + } + + _dirty = true; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Configuration::has (const std::string& key) const +{ + return (*this).find (key) != (*this).end (); +} + +//////////////////////////////////////////////////////////////////////////////// +// Return the configuration value given the specified key. +std::string Configuration::get (const std::string& key) const +{ + auto found = find (key); + if (found != end ()) + return found->second; + + return ""; +} + +//////////////////////////////////////////////////////////////////////////////// +int Configuration::getInteger (const std::string& key) const +{ + auto found = find (key); + if (found != end ()) + return strtoimax (found->second.c_str (), nullptr, 10); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +double Configuration::getReal (const std::string& key) const +{ + auto found = find (key); + if (found != end ()) + return strtod (found->second.c_str (), nullptr); + + return 0.0; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Configuration::getBoolean (const std::string& key) const +{ + auto found = find (key); + if (found != end ()) + { + auto value = lowerCase (found->second); + if (value == "true" || + value == "1" || + value == "y" || + value == "yes" || + value == "on") + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +void Configuration::set (const std::string& key, const int value) +{ + (*this)[key] = format (value); + _dirty = true; +} + +//////////////////////////////////////////////////////////////////////////////// +void Configuration::set (const std::string& key, const double value) +{ + (*this)[key] = format (value, 1, 8); + _dirty = true; +} + +//////////////////////////////////////////////////////////////////////////////// +void Configuration::set (const std::string& key, const std::string& value) +{ + (*this)[key] = value; + _dirty = true; +} + +//////////////////////////////////////////////////////////////////////////////// +// Autovivification is ok here. +void Configuration::setIfBlank (const std::string& key, const std::string& value) +{ + if ((*this)[key] == "") + { + (*this)[key] = value; + _dirty = true; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Provide a vector of all configuration keys. +std::vector Configuration::all () const +{ + std::vector items; + for (const auto& it : *this) + items.push_back (it.first); + + return items; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string Configuration::file () const +{ + return _original_file._data; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Configuration::dirty () +{ + return _dirty; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/libshared/src/Configuration.h b/src/libshared/src/Configuration.h new file mode 100644 index 0000000..067affe --- /dev/null +++ b/src/libshared/src/Configuration.h @@ -0,0 +1,66 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2017, Paul Beckingham, Federico Hernandez. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// http://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDED_CONFIGURATION +#define INCLUDED_CONFIGURATION + +#include +#include +#include +#include + +bool setVariableInFile (const std::string&, const std::string&, const std::string&); +bool unsetVariableInFile (const std::string&, const std::string&); + +class Configuration : public std::map +{ +public: + void load (const std::string&, int nest = 1); + void save (); + void parse (const std::string&, int nest = 1); + + bool has (const std::string&) const; + std::string get (const std::string&) const; + int getInteger (const std::string&) const; + double getReal (const std::string&) const; + bool getBoolean (const std::string&) const; + + void set (const std::string&, const int); + void set (const std::string&, const double); + void set (const std::string&, const std::string&); + void setIfBlank (const std::string&, const std::string&); + std::vector all () const; + + std::string file () const; + + bool dirty (); + +private: + File _original_file {}; + bool _dirty {false}; +}; + +#endif diff --git a/src/libshared/src/Datetime.cpp b/src/libshared/src/Datetime.cpp new file mode 100644 index 0000000..726d9dc --- /dev/null +++ b/src/libshared/src/Datetime.cpp @@ -0,0 +1,3787 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2017, Paul Beckingham, Federico Hernandez. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// http://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static std::vector dayNames { + "sunday", + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday"}; + +static std::vector monthNames { + "january", + "february", + "march", + "april", + "may", + "june", + "july", + "august", + "september", + "october", + "november", + "december"}; + +int Datetime::weekstart = 1; // Monday, per ISO-8601. +int Datetime::minimumMatchLength = 3; +bool Datetime::isoEnabled = true; +bool Datetime::standaloneDateEnabled = true; +bool Datetime::standaloneTimeEnabled = true; + +//////////////////////////////////////////////////////////////////////////////// +Datetime::Datetime () +{ + clear (); + _date = time (nullptr); +} + +//////////////////////////////////////////////////////////////////////////////// +Datetime::Datetime (const std::string& input, const std::string& format) +{ + clear (); + std::string::size_type start = 0; + if (! parse (input, start, format)) + throw ::format ("'{1}' is not a valid date in the '{2}' format.", input, format); +} + +//////////////////////////////////////////////////////////////////////////////// +Datetime::Datetime (const time_t t) +{ + clear (); + _date = t; +} + +//////////////////////////////////////////////////////////////////////////////// +Datetime::Datetime (const int y, const int m, const int d) +{ + // Protect against arguments being passed in the wrong order. + assert (y >= 1969 && y < 2100); + assert (m >= 1 && m <= 12); + assert (d >= 1 && d <= 31); + + clear (); + + // Error if not valid. + struct tm t {}; + t.tm_isdst = -1; // Requests that mktime determine summer time effect. + t.tm_mday = d; + t.tm_mon = m - 1; + t.tm_year = y - 1900; + + _date = mktime (&t); +} + +//////////////////////////////////////////////////////////////////////////////// +Datetime::Datetime (const int y, const int m, const int d, + const int hr, const int mi, const int se) +{ + // Protect against arguments being passed in the wrong order. + assert (y >= 1969 && y < 2100); + assert (m >= 1 && m <= 12); + assert (d >= 1 && d <= 31); + assert (hr >= 0 && hr <= 24); + assert (mi >= 0 && mi < 60); + assert (se >= 0 && se < 60); + + clear (); + + // Error if not valid. + struct tm t {}; + t.tm_isdst = -1; // Requests that mktime determine summer time effect. + t.tm_mday = d; + t.tm_mon = m - 1; + t.tm_year = y - 1900; + t.tm_hour = hr; + t.tm_min = mi; + t.tm_sec = se; + + _date = mktime (&t); +} + +//////////////////////////////////////////////////////////////////////////////// +bool Datetime::parse ( + const std::string& input, + std::string::size_type& start, + const std::string& format) +{ + auto i = start; + Pig pig (input); + if (i) + pig.skipN (static_cast (i)); + + auto checkpoint = pig.cursor (); + + // Parse epoch first, as it's the most common scenario. + if (parse_epoch (pig)) + { + // ::validate and ::resolve are not needed in this case. + start = pig.cursor (); + return true; + } + + if (parse_formatted (pig, format)) + { + // Check the values and determine time_t. + if (validate ()) + { + start = pig.cursor (); + resolve (); + return true; + } + } + + // Allow parse_date_time and parse_date_time_ext regardless of + // Datetime::isoEnabled setting, because these formats are relied upon by + // the 'import' command, JSON parser and hook system. + if (parse_date_time_ext (pig) || // Strictest first. + parse_date_time (pig) || + (Datetime::isoEnabled && + ( parse_date_ext (pig) || + (Datetime::standaloneDateEnabled && parse_date (pig)) || + parse_time_utc_ext (pig) || + parse_time_utc (pig) || + parse_time_off_ext (pig) || + parse_time_off (pig) || + parse_time_ext (pig) || + (Datetime::standaloneTimeEnabled && parse_time (pig)) // Time last, as it is the most permissive. + ) + ) + ) + { + // Check the values and determine time_t. + if (validate ()) + { + start = pig.cursor (); + resolve (); + return true; + } + } + + pig.restoreTo (checkpoint); + + if (parse_named (pig)) + { + // ::validate and ::resolve are not needed in this case. + start = pig.cursor (); + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +void Datetime::clear () +{ + _year = 0; + _month = 0; + _week = 0; + _weekday = 0; + _julian = 0; + _day = 0; + _seconds = 0; + _offset = 0; + _utc = false; + _date = 0; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Datetime::parse_formatted (Pig& pig, const std::string& format) +{ + // Short-circuit on missing format. + if (format == "") + return false; + + auto checkpoint = pig.cursor (); + + int month {-1}; // So we can check later. + int day {-1}; + int year {-1}; + int hour {-1}; + int minute {-1}; + int second {-1}; + + // For parsing, unused. + int wday {-1}; + int week {-1}; + + for (unsigned int f = 0; f < format.length (); ++f) + { + switch (format[f]) + { + case 'm': + if (pig.getDigit (month)) + { + if (month == 0) + pig.getDigit (month); + + if (month == 1) + if (pig.getDigit (month)) + month += 10; + } + else + { + pig.restoreTo (checkpoint); + return false; + } + break; + + case 'M': + if (! pig.getDigit2 (month)) + { + pig.restoreTo (checkpoint); + return false; + } + break; + + case 'd': + if (pig.getDigit (day)) + { + if (day == 0) + pig.getDigit (day); + + if (day == 1 || day == 2 || day == 3) + { + int tens = day; + if (pig.getDigit (day)) + day += 10 * tens; + } + } + else + { + pig.restoreTo (checkpoint); + return false; + } + break; + + case 'D': + if (! pig.getDigit2 (day)) + { + pig.restoreTo (checkpoint); + return false; + } + break; + + case 'y': + if (! pig.getDigit2 (year)) + { + pig.restoreTo (checkpoint); + return false; + } + year += 2000; + break; + + case 'Y': + if (! pig.getDigit4 (year)) + { + pig.restoreTo (checkpoint); + return false; + } + break; + + case 'h': + if (pig.getDigit (hour)) + { + if (hour == 0) + pig.getDigit (hour); + + if (hour == 1 || hour == 2) + { + int tens = hour; + if (pig.getDigit (hour)) + hour += 10 * tens; + } + } + else + { + pig.restoreTo (checkpoint); + return false; + } + break; + + case 'H': + if (! pig.getDigit2 (hour)) + { + pig.restoreTo (checkpoint); + return false; + } + break; + + case 'n': + if (pig.getDigit (minute)) + { + if (minute == 0) + pig.getDigit (minute); + + if (minute < 6) + { + int tens = minute; + if (pig.getDigit (minute)) + minute += 10 * tens; + } + } + else + { + pig.restoreTo (checkpoint); + return false; + } + break; + + case 'N': + if (! pig.getDigit2 (minute)) + { + pig.restoreTo (checkpoint); + return false; + } + break; + + case 's': + if (pig.getDigit (second)) + { + if (second == 0) + pig.getDigit (second); + + if (second < 6) + { + int tens = second; + if (pig.getDigit (second)) + second += 10 * tens; + } + } + else + { + pig.restoreTo (checkpoint); + return false; + } + break; + + case 'S': + if (! pig.getDigit2 (second)) + { + pig.restoreTo (checkpoint); + return false; + } + break; + + case 'v': + if (pig.getDigit (week)) + { + if (week == 0) + pig.getDigit (week); + + if (week < 6) + { + int tens = week; + if (pig.getDigit (week)) + week += 10 * tens; + } + } + else + { + pig.restoreTo (checkpoint); + return false; + } + break; + + case 'V': + if (! pig.getDigit2 (week)) + { + pig.restoreTo (checkpoint); + return false; + } + break; + + case 'a': + wday = Datetime::dayOfWeek (pig.str ().substr (0, 3)); + if (wday == -1) + { + pig.restoreTo (checkpoint); + return false; + } + + pig.skipN (3); + break; + + case 'A': + { + std::string dayName; + if (pig.getUntil (format[f + 1], dayName)) + { + wday = Datetime::dayOfWeek (dayName); + if (wday == -1) + { + pig.restoreTo (checkpoint); + return false; + } + } + } + break; + + case 'b': + month = Datetime::monthOfYear (pig.str ().substr (0, 3)); + if (month == -1) + { + pig.restoreTo (checkpoint); + return false; + } + + pig.skipN (3); + break; + + case 'B': + { + std::string monthName; + if (pig.getUntil (format[f + 1], monthName)) + { + month = Datetime::monthOfYear (monthName); + if (month == -1) + { + pig.restoreTo (checkpoint); + return false; + } + } + } + break; + + default: + if (! pig.skip (format[f])) + { + pig.restoreTo (checkpoint); + return false; + } + break; + } + } + + // It is possible that the format='Y-M-D', and the input is Y-M-DTH:N:SZ, and + // this should not be considered a match. + if (! pig.eos () && ! unicodeWhitespace (pig.peek ())) + { + pig.restoreTo (checkpoint); + return false; + } + + // Missing values are filled in from the current date. + if (year == -1) + { + Datetime now; + year = now.year (); + if (month == -1) + { + month = now.month (); + if (day == -1) + { + day = now.day (); + if (hour == -1) + { + hour = now.hour (); + if (minute == -1) + { + minute = now.minute (); + if (second == -1) + second = now.second (); + } + } + } + } + } + + // Any remaining undefined values are assigned defaults. + if (month == -1) month = 1; + if (day == -1) day = 1; + if (hour == -1) hour = 0; + if (minute == -1) minute = 0; + if (second == -1) second = 0; + + _year = year; + _month = month; + _day = day; + _seconds = (hour * 3600) + (minute * 60) + second; + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// Note how these are all single words. +// +// Examples and descriptions, assuming now == 2017-03-05T12:34:56. +// +// Example Notes +// ------------------- ------------------ +// now 2017-03-05T12:34:56 Unaffected +// yesterday 2017-03-04T00:00:00 Unaffected +// today 2017-03-05T00:00:00 Unaffected +// tomorrow 2017-03-06T00:00:00 Unaffected +// 12th 2017-03-12T00:00:00 +// monday 2017-03-06T00:00:00 +// april 2017-04-01T00:00:00 +// later 2038-01-18T00:00:00 Unaffected +// someday 2038-01-18T00:00:00 Unaffected +// sopd 2017-03-04T00:00:00 Unaffected +// sod 2017-03-05T00:00:00 Unaffected +// sond 2017-03-06T00:00:00 Unaffected +// eopd 2017-03-05T00:00:00 Unaffected +// eod 2017-03-06T00:00:00 Unaffected +// eond 2017-03-07T00:00:00 Unaffected +// sopw 2017-02-26T00:00:00 Unaffected +// sow 2017-03-05T00:00:00 Unaffected +// sonw 2017-03-12T00:00:00 Unaffected +// eopw 2017-03-05T00:00:00 Unaffected +// eow 2017-03-12T00:00:00 Unaffected +// eonw 2017-03-19T00:00:00 Unaffected +// sopww 2017-02-27T00:00:00 Unaffected +// soww 2017-03-06T00:00:00 +// sonww 2017-03-06T00:00:00 Unaffected +// eopww 2017-03-03T00:00:00 Unaffected +// eoww 2017-03-10T00:00:00 +// eonww 2017-03-17T00:00:00 Unaffected +// sopm 2017-02-01T00:00:00 Unaffected +// som 2017-03-01T00:00:00 Unaffected +// sonm 2017-04-01T00:00:00 Unaffected +// eopm 2017-03-01T00:00:00 Unaffected +// eom 2017-04-01T00:00:00 Unaffected +// eonm 2017-05-01T00:00:00 Unaffected +// sopq 2017-10-01T00:00:00 Unaffected +// soq 2017-01-01T00:00:00 Unaffected +// sonq 2017-04-01T00:00:00 Unaffected +// eopq 2017-01-01T00:00:00 Unaffected +// eoq 2017-04-01T00:00:00 Unaffected +// eonq 2017-07-01T00:00:00 Unaffected +// sopy 2016-01-01T00:00:00 Unaffected +// soy 2017-01-01T00:00:00 Unaffected +// sony 2018-01-01T00:00:00 Unaffected +// eopy 2017-01-01T00:00:00 Unaffected +// eoy 2018-01-01T00:00:00 Unaffected +// eony 2019-01-01T00:00:00 Unaffected +// easter 2017-04-16T00:00:00 +// eastermonday 2017-04-16T00:00:00 +// ascension 2017-05-25T00:00:00 +// pentecost 2017-06-04T00:00:00 +// goodfriday 2017-04-14T00:00:00 +// midsommar 2017-06-24T00:00:00 midnight, 1st Saturday after 20th June +// midsommarafton 2017-06-23T00:00:00 midnight, 1st Friday after 19th June +// juhannus 2017-06-23T00:00:00 midnight, 1st Friday after 19th June +// +bool Datetime::parse_named (Pig& pig) +{ + auto checkpoint = pig.cursor (); + + // Experimental handling of date phrases, such as "first monday in march". + // Note that this requires that phrases are deliminted by EOS or WS. + std::string token; + std::vector tokens; + while (pig.getUntilWS (token)) + { + tokens.push_back (token); + if (! pig.skipWS ()) + break; + } + +/* + // This grpoup contains "1st monday ..." which must be processed before + // initializeOrdinal below. + if (initializeNthDayInMonth (tokens)) + { + return true; + } +*/ + + // Restoration necessary because of the tokenization. + pig.restoreTo (checkpoint); + + if (initializeNow (pig) || + initializeYesterday (pig) || + initializeToday (pig) || + initializeTomorrow (pig) || + initializeOrdinal (pig) || + initializeDayName (pig) || + initializeMonthName (pig) || + initializeLater (pig) || + initializeSopd (pig) || + initializeSod (pig) || + initializeSond (pig) || + initializeEopd (pig) || + initializeEod (pig) || + initializeEond (pig) || + initializeSopw (pig) || + initializeSow (pig) || + initializeSonw (pig) || + initializeEopw (pig) || + initializeEow (pig) || + initializeEonw (pig) || + initializeSopww (pig) || // Must appear after sopw + initializeSonww (pig) || // Must appear after sonw + initializeSoww (pig) || // Must appear after sow + initializeEopww (pig) || // Must appear after eopw + initializeEonww (pig) || // Must appear after eonw + initializeEoww (pig) || // Must appear after eow + initializeSopm (pig) || + initializeSom (pig) || + initializeSonm (pig) || + initializeEopm (pig) || + initializeEom (pig) || + initializeEonm (pig) || + initializeSopq (pig) || + initializeSoq (pig) || + initializeSonq (pig) || + initializeEopq (pig) || + initializeEoq (pig) || + initializeEonq (pig) || + initializeSopy (pig) || + initializeSoy (pig) || + initializeSony (pig) || + initializeEopy (pig) || + initializeEoy (pig) || + initializeEony (pig) || + initializeEaster (pig) || + initializeMidsommar (pig) || + initializeMidsommarafton (pig) || + initializeInformalTime (pig)) + { + return true; + } + + pig.restoreTo (checkpoint); + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// Valid epoch values are unsigned integers after 1980-01-01T00:00:00Z. This +// restriction means that '12' will not be identified as an epoch date. +bool Datetime::parse_epoch (Pig& pig) +{ + auto checkpoint = pig.cursor (); + + int epoch {}; + if (pig.getDigits (epoch) && + ! unicodeLatinAlpha (pig.peek ()) && + epoch >= 315532800) + { + _date = static_cast (epoch); + //std::cout << "# parse_epoch succeed " << pig.dump () << "\n"; + return true; + } + + //std::cout << "# parse_epoch fail\n"; + pig.restoreTo (checkpoint); + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// date_ext 'T' time_utc_ext 'Z' +// date_ext 'T' time_off_ext +// date_ext 'T' time_ext +bool Datetime::parse_date_time_ext (Pig& pig) +{ + auto checkpoint = pig.cursor (); + + if (parse_date_ext (pig) && + pig.skip ('T') && + (parse_time_utc_ext (pig) || + parse_time_off_ext (pig) || + parse_time_ext (pig))) + { + return true; + } + + pig.restoreTo (checkpoint); + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// YYYY-MM-DD +// YYYY-MM +// YYYY-DDD +// YYYY-Www-D +// YYYY-Www +bool Datetime::parse_date_ext (Pig& pig) +{ + auto checkpoint = pig.cursor (); + + int year {}; + if (parse_year (pig, year) && + pig.skip ('-')) + { + auto checkpointYear = pig.cursor (); + + int month {}; + int day {}; + int julian {}; + + if (pig.skip ('W') && + parse_week (pig, _week)) + { + if (pig.skip ('-') && + pig.getDigit (_weekday)) + { + // What is happening here - must be something to do? + } + + if (! unicodeLatinDigit (pig.peek ())) + { + _year = year; + return true; + } + } + + pig.restoreTo (checkpointYear); + + if (parse_month (pig, month) && + pig.skip ('-') && + parse_day (pig, day) && + ! unicodeLatinDigit (pig.peek ())) + { + _year = year; + _month = month; + _day = day; + return true; + } + + pig.restoreTo (checkpointYear); + + if (parse_julian (pig, julian) && + ! unicodeLatinDigit (pig.peek ())) + { + _year = year; + _julian = julian; + return true; + } + + pig.restoreTo (checkpointYear); + + if (parse_month (pig, month) && + pig.peek () != '-' && + ! unicodeLatinDigit (pig.peek ())) + { + _year = year; + _month = month; + _day = 1; + return true; + } + } + + pig.restoreTo (checkpoint); + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// ±hh[:mm] +bool Datetime::parse_off_ext (Pig& pig) +{ + auto checkpoint = pig.cursor (); + + int sign = pig.peek (); + if (sign == '+' || sign == '-') + { + pig.skipN (1); + + int hour {0}; + int minute {0}; + + if (parse_off_hour (pig, hour)) + { + if (pig.skip (':')) + { + if (! parse_off_minute (pig, minute)) + { + pig.restoreTo (checkpoint); + return false; + } + } + + _offset = (hour * 3600) + (minute * 60); + if (sign == '-') + _offset = - _offset; + + if (! unicodeLatinDigit (pig.peek ())) + return true; + } + } + + pig.restoreTo (checkpoint); + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// hh:mm[:ss] +bool Datetime::parse_time_ext (Pig& pig, bool terminated) +{ + auto checkpoint = pig.cursor (); + + int hour {}; + int minute {}; + if (parse_hour (pig, hour) && + pig.skip (':') && + parse_minute (pig, minute)) + { + if (pig.skip (':')) + { + int second {}; + if (parse_second (pig, second) && + ! unicodeLatinDigit (pig.peek ()) && + (! terminated || (pig.peek () != '-' && pig.peek () != '+'))) + { + _seconds = (hour * 3600) + (minute * 60) + second; + return true; + } + + pig.restoreTo (checkpoint); + return false; + } + + auto following = pig.peek (); + if (! unicodeLatinDigit (following) && + (! terminated || (following != '+' && following != '-')) && + following != 'A' && + following != 'a' && + following != 'P' && + following != 'p') + { + _seconds = (hour * 3600) + (minute * 60); + return true; + } + } + + pig.restoreTo (checkpoint); + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// time-ext 'Z' +bool Datetime::parse_time_utc_ext (Pig& pig) +{ + auto checkpoint = pig.cursor (); + + if (parse_time_ext (pig, false) && + pig.skip ('Z')) + { + if (! unicodeLatinDigit (pig.peek ())) + { + _utc = true; + return true; + } + } + + pig.restoreTo (checkpoint); + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// time-ext off-ext +bool Datetime::parse_time_off_ext (Pig& pig) +{ + auto checkpoint = pig.cursor (); + + if (parse_time_ext (pig, false) && + parse_off_ext (pig)) + { + return true; + } + + pig.restoreTo (checkpoint); + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// YYYYMMDDTHHMMSSZ +// YYYYMMDDTHHMMSS +bool Datetime::parse_date_time (Pig& pig) +{ + auto checkpoint = pig.cursor (); + + if (parse_date (pig) && + pig.skip ('T') && + (parse_time_utc (pig) || + parse_time_off (pig) || + parse_time (pig))) + { + return true; + } + + pig.restoreTo (checkpoint); + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// YYYYWww +// YYYYDDD +// YYYYMMDD +// YYYYMM +bool Datetime::parse_date (Pig& pig) +{ + auto checkpoint = pig.cursor (); + + int year {}; + int month {}; + int julian {}; + int week {}; + int weekday {}; + int day {}; + if (parse_year (pig, year)) + { + auto checkpointYear = pig.cursor (); + + if (pig.skip ('W') && + parse_week (pig, week)) + { + if (pig.getDigit (weekday)) + _weekday = weekday; + + if (! unicodeLatinDigit (pig.peek ())) + { + _year = year; + _week = week; + return true; + } + } + + pig.restoreTo (checkpointYear); + + if (parse_julian (pig, julian) && + ! unicodeLatinDigit (pig.peek ())) + { + _year = year; + _julian = julian; + return true; + } + + pig.restoreTo (checkpointYear); + + if (parse_month (pig, month)) + { + if (parse_day (pig, day)) + { + if (! unicodeLatinDigit (pig.peek ())) + { + _year = year; + _month = month; + _day = day; + return true; + } + } + else + { + if (! unicodeLatinDigit (pig.peek ())) + { + _year = year; + _month = month; + _day = 1; + return true; + } + } + } + } + + pig.restoreTo (checkpoint); + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +//