--- /dev/null
+[submodule "src/libshared"]
+ path = src/libshared
+ url = https://git.tasktools.org/TM/libshared.git
--- /dev/null
+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
+
--- /dev/null
+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)
--- /dev/null
+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
--- /dev/null
+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
+
--- /dev/null
+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=<path-to-installation-dir> .
+
+cmake configuration variables are applied with the -D option and consist of a
+<name> and a <value>:
+
+ $ cmake -D<name>=<value> .
+
+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.
+
+---
--- /dev/null
+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
--- /dev/null
+
+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.
--- /dev/null
+# gen-shell
+A work in progress generic shell forked form [taskshell](https://github.com/GothenburgBitFactory/taskshell)
--- /dev/null
+/* 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 */
+
--- /dev/null
+/* 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
+
--- /dev/null
+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}")
--- /dev/null
+# - 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)
--- /dev/null
+/* commit.h.in. Creates commit.h during a cmake run */
+
+/* git information */
+#define COMMIT "e6d0532"
--- /dev/null
+/* commit.h.in. Creates commit.h during a cmake run */
+
+/* git information */
+#define COMMIT "${COMMIT}"
--- /dev/null
+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")
+
--- /dev/null
+.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 <commands>
+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:
+<https://taskwarrior.org/docs/review.html>
+
+.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
+<http://taskwarrior.org/tools>
+
+.TP
+The official code repository at
+<https://git.tasktools.org/scm/ex/tasksh.git>
+
+.TP
+You can contact the project by emailing
+<support@tasktools.org>
+
+.SH REPORTING BUGS
+.TP
+Bugs in tasksh may be reported to the issue-tracker at
+<http://bug.tasktools.org>
+
--- /dev/null
+.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 <commands>
+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:
+<https://taskwarrior.org/docs/review.html>
+
+.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
+<http://taskwarrior.org/tools>
+
+.TP
+The official code repository at
+<https://git.tasktools.org/scm/ex/tasksh.git>
+
+.TP
+You can contact the project by emailing
+<support@tasktools.org>
+
+.SH REPORTING BUGS
+.TP
+Bugs in tasksh may be reported to the issue-tracker at
+<http://bug.tasktools.org>
+
--- /dev/null
+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})
+
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <commit.h>
+#include <iostream>
+#include <string>
+#include <vector>
+#include <cstring>
+#include <stdlib.h>
+#include <FS.h>
+#include <Color.h>
+#include <shared.h>
+#include <format.h>
+
+#ifdef HAVE_READLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+#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;
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <iostream>
+
+////////////////////////////////////////////////////////////////////////////////
+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;
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+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
--- /dev/null
+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)
+
--- /dev/null
+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 -----------------------------------
--- /dev/null
+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
--- /dev/null
+/* 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
+
--- /dev/null
+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}")
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <Args.h>
+#include <shared.h>
+#include <sstream>
+#include <string.h>
+
+////////////////////////////////////////////////////////////////////////////////
+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 <int> (_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 <int> (_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 <std::string> 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 <std::string> 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 ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <string>
+#include <vector>
+#include <map>
+
+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 <std::string, bool> _options {};
+ std::map <std::string, int> _optionCount {};
+ std::map <std::string, std::string> _named {};
+ std::vector <std::string> _positionals {};
+ int _limit {-1};
+ bool _negatives {false};
+};
+
+#endif
+
--- /dev/null
+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")
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <Color.h>
+#include <sstream>
+#include <vector>
+#include <cstdlib>
+#include <shared.h>
+#include <format.h>
+
+// 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 "";
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <string>
+
+#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
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <Composite.h>
+#include <utf8.h>
+#include <sstream>
+#include <stack>
+
+////////////////////////////////////////////////////////////////////////////////
+// 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 <int> characters;
+ std::vector <int> 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 ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <Color.h>
+#include <vector>
+#include <string>
+#include <tuple>
+
+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 <std::tuple <std::string, std::string::size_type, Color>> _layers;
+};
+
+#endif
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <Configuration.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <FS.h>
+#include <JSON.h>
+#include <shared.h>
+#include <format.h>
+
+////////////////////////////////////////////////////////////////////////////////
+bool setVariableInFile (
+ const std::string& file,
+ const std::string& name,
+ const std::string& value)
+{
+ // Read the file contents.
+ std::vector <std::string> 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 <std::string> 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 <std::string> Configuration::all () const
+{
+ std::vector <std::string> 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;
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <map>
+#include <vector>
+#include <string>
+#include <FS.h>
+
+bool setVariableInFile (const std::string&, const std::string&, const std::string&);
+bool unsetVariableInFile (const std::string&, const std::string&);
+
+class Configuration : public std::map <std::string, std::string>
+{
+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 <std::string> all () const;
+
+ std::string file () const;
+
+ bool dirty ();
+
+private:
+ File _original_file {};
+ bool _dirty {false};
+};
+
+#endif
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <Datetime.h>
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include <iomanip>
+#include <cassert>
+#include <stdlib.h>
+#include <shared.h>
+#include <format.h>
+#include <unicode.h>
+#include <utf8.h>
+
+static std::vector <std::string> dayNames {
+ "sunday",
+ "monday",
+ "tuesday",
+ "wednesday",
+ "thursday",
+ "friday",
+ "saturday"};
+
+static std::vector <std::string> 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 <int> (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
+// <ordinal> 12th 2017-03-12T00:00:00
+// <day> monday 2017-03-06T00:00:00
+// <month> 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 <std::string> 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 <time_t> (epoch);
+ //std::cout << "# parse_epoch \e[33msucceed\e[0m " << 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;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// <time> Z
+bool Datetime::parse_time_utc (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (parse_time (pig, false) &&
+ pig.skip ('Z'))
+ {
+ _utc = true;
+ if (! unicodeLatinDigit (pig.peek ()))
+ return true;
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// <time> <off>
+bool Datetime::parse_time_off (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (parse_time (pig, false) &&
+ parse_off (pig))
+ {
+ auto terminator = pig.peek ();
+ if (terminator != '-' && ! unicodeLatinDigit (terminator))
+ {
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// hhmmss
+// hhmm
+bool Datetime::parse_time (Pig& pig, bool terminated)
+{
+ auto checkpoint = pig.cursor ();
+
+ int hour {};
+ int minute {};
+ if (parse_hour (pig, hour) &&
+ parse_minute (pig, minute))
+ {
+ int second {};
+ parse_second (pig, second);
+
+ auto terminator = pig.peek ();
+ if (! terminated ||
+ (! unicodeLatinDigit (terminator) && terminator != '-' && terminator != '+'))
+ {
+ _seconds = (hour * 3600) + (minute * 60) + second;
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ±hhmm
+// ±hh
+bool Datetime::parse_off (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ int sign = pig.peek ();
+ if (sign == '+' || sign == '-')
+ {
+ pig.skipN (1);
+
+ int hour {};
+ if (parse_off_hour (pig, hour))
+ {
+ int minute {};
+ parse_off_minute (pig, minute);
+
+ if (! unicodeLatinDigit (pig.peek ()))
+ {
+ _offset = (hour * 3600) + (minute * 60);
+ if (sign == '-')
+ _offset = - _offset;
+
+ return true;
+ }
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::parse_year (Pig& pig, int& value)
+{
+ auto checkpoint = pig.cursor ();
+
+ int year;
+ if (pig.getDigit4 (year) &&
+ year > 1969)
+ {
+ value = year;
+ return true;
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::parse_month (Pig& pig, int& value)
+{
+ auto checkpoint = pig.cursor ();
+
+ int month;
+ if (pig.getDigit2 (month) &&
+ month > 0 &&
+ month <= 12)
+ {
+ value = month;
+ return true;
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::parse_week (Pig& pig, int& value)
+{
+ auto checkpoint = pig.cursor ();
+
+ int week;
+ if (pig.getDigit2 (week) &&
+ week > 0 &&
+ week <= 53)
+ {
+ value = week;
+ return true;
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::parse_julian (Pig& pig, int& value)
+{
+ auto checkpoint = pig.cursor ();
+
+ int julian;
+ if (pig.getDigit3 (julian) &&
+ julian > 0 &&
+ julian <= 366)
+ {
+ value = julian;
+ return true;
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::parse_day (Pig& pig, int& value)
+{
+ auto checkpoint = pig.cursor ();
+
+ int day;
+ if (pig.getDigit2 (day) &&
+ day > 0 &&
+ day <= 31)
+ {
+ value = day;
+ return true;
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::parse_weekday (Pig& pig, int& value)
+{
+ auto checkpoint = pig.cursor ();
+
+ int weekday;
+ if (pig.getDigit (weekday) &&
+ weekday >= 1 &&
+ weekday <= 7)
+ {
+ value = weekday;
+ return true;
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::parse_hour (Pig& pig, int& value)
+{
+ auto checkpoint = pig.cursor ();
+
+ int hour;
+ if (pig.getDigit2 (hour) &&
+ hour >= 0 &&
+ hour < 24)
+ {
+ value = hour;
+ return true;
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::parse_minute (Pig& pig, int& value)
+{
+ auto checkpoint = pig.cursor ();
+
+ int minute;
+ if (pig.getDigit2 (minute) &&
+ minute >= 0 &&
+ minute < 60)
+ {
+ value = minute;
+ return true;
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::parse_second (Pig& pig, int& value)
+{
+ auto checkpoint = pig.cursor ();
+
+ int second;
+ if (pig.getDigit2 (second) &&
+ second >= 0 &&
+ second < 60)
+ {
+ value = second;
+ return true;
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::parse_off_hour (Pig& pig, int& value)
+{
+ auto checkpoint = pig.cursor ();
+
+ int hour;
+ if (pig.getDigit2 (hour) &&
+ hour >= 0 &&
+ hour <= 12)
+ {
+ value = hour;
+ return true;
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::parse_off_minute (Pig& pig, int& value)
+{
+ auto checkpoint = pig.cursor ();
+
+ int minute;
+ if (pig.getDigit2 (minute) &&
+ minute >= 0 &&
+ minute < 60)
+ {
+ value = minute;
+ return true;
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// now [ !<alpha> && !<digit> ]
+bool Datetime::initializeNow (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("now"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ _date = time (nullptr);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// yesterday/abbrev [ !<alpha> && !<digit> ]
+bool Datetime::initializeYesterday (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ std::string token;
+ if (pig.skipPartial ("yesterday", token) &&
+ token.length () >= static_cast <std::string::size_type> (Datetime::minimumMatchLength))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_isdst = -1;
+ t->tm_mday -= 1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// today/abbrev [ !<alpha> && !<digit> ]
+bool Datetime::initializeToday (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ std::string token;
+ if (pig.skipPartial ("today", token) &&
+ token.length () >= static_cast <std::string::size_type> (Datetime::minimumMatchLength))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// tomcorrow/abbrev [ !<alpha> && !<digit> ]
+bool Datetime::initializeTomorrow (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ std::string token;
+ if (pig.skipPartial ("tomorrow", token) &&
+ token.length () >= static_cast <std::string::size_type> (Datetime::minimumMatchLength))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_mday++;
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// <digit>+ [ "st" | "nd" | "rd" | "th" ] [ !<alpha> && !<digit> ]
+bool Datetime::initializeOrdinal (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ int number = 0;
+ if (pig.getDigits (number) &&
+ number > 0 &&
+ number <= 31)
+ {
+ int character1;
+ int character2;
+ if (pig.getCharacter (character1) &&
+ pig.getCharacter (character2) &&
+ ! unicodeLatinAlpha (pig.peek ()) &&
+ ! unicodeLatinDigit (pig.peek ()))
+ {
+ int remainder1 = number % 10;
+ int remainder2 = number % 100;
+ if ((remainder2 != 11 && remainder1 == 1 && character1 == 's' && character2 == 't') ||
+ (remainder2 != 12 && remainder1 == 2 && character1 == 'n' && character2 == 'd') ||
+ (remainder2 != 13 && remainder1 == 3 && character1 == 'r' && character2 == 'd') ||
+ ((remainder2 == 11 ||
+ remainder2 == 12 ||
+ remainder2 == 13 ||
+ remainder1 == 0 ||
+ remainder1 > 3) && character1 == 't' && character2 == 'h'))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ int y = t->tm_year + 1900;
+ int m = t->tm_mon + 1;
+ int d = t->tm_mday;
+
+ // If it is this month.
+ if (d < number &&
+ number <= daysInMonth (y, m))
+ {
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_mon = m - 1;
+ t->tm_mday = number;
+ t->tm_year = y - 1900;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ }
+ else
+ {
+ if (++m > 12)
+ {
+ m = 1;
+ y++;
+ }
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_mon = m - 1;
+ t->tm_mday = number;
+ t->tm_year = y - 1900;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ }
+
+ return true;
+ }
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sunday/abbrev [ !<alpha> && !<digit> && !: && != ]
+bool Datetime::initializeDayName (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ std::string token;
+ for (int day = 0; day <= 7; ++day) // Deliberate <= so that 'sunday' is either 0 or 7.
+ {
+ if (pig.skipPartial (dayNames[day % 7], token) &&
+ token.length () >= static_cast <std::string::size_type> (Datetime::minimumMatchLength))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following) &&
+ following != ':' &&
+ following != '=')
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ if (t->tm_wday >= day)
+ t->tm_mday += day - t->tm_wday + 7;
+ else
+ t->tm_mday += day - t->tm_wday;
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// january/abbrev [ !<alpha> && !<digit> && !: && != ]
+bool Datetime::initializeMonthName (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ std::string token;
+ for (int month = 0; month < 12; ++month)
+ {
+ if (pig.skipPartial (monthNames[month], token) &&
+ token.length () >= static_cast <std::string::size_type> (Datetime::minimumMatchLength))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following) &&
+ following != ':' &&
+ following != '=')
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ if (t->tm_mon >= month)
+ t->tm_year++;
+
+ t->tm_mon = month;
+ t->tm_mday = 1;
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// later/abbrev [ !<alpha> && !<digit> ]
+// someday/abbrev [ !<alpha> && !<digit> ]
+bool Datetime::initializeLater (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ std::string token;
+ if ((pig.skipPartial ("later", token) &&
+ token.length () >= static_cast <std::string::size_type> (Datetime::minimumMatchLength))
+
+ ||
+
+ (pig.skipPartial ("someday", token) &&
+ token.length () >= static_cast <std::string::size_type> (std::max (Datetime::minimumMatchLength, 4))))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_year = 138;
+ t->tm_mon = 0;
+ t->tm_mday = 18;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sopd [ !<alpha> && !<digit> ]
+bool Datetime::initializeSopd (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("sopd"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_isdst = -1;
+ t->tm_mday -= 1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sod [ !<alpha> && !<digit> ]
+bool Datetime::initializeSod (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("sod"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sond [ !<alpha> && !<digit> ]
+bool Datetime::initializeSond (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("sond"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_mday++;
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// eopd [ !<alpha> && !<digit> ]
+bool Datetime::initializeEopd (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("eopd"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// eod [ !<alpha> && !<digit> ]
+bool Datetime::initializeEod (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("eod"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_mday++;
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// eond [ !<alpha> && !<digit> ]
+bool Datetime::initializeEond (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("eond"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_mday += 2;
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sopw [ !<alpha> && !<digit> ]
+bool Datetime::initializeSopw (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("sopw"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+
+ int extra = (t->tm_wday + 6) % 7;
+ t->tm_mday -= extra;
+ t->tm_mday -= 7;
+
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sow [ !<alpha> && !<digit> ]
+bool Datetime::initializeSow (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("sow"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+
+ int extra = (t->tm_wday + 6) % 7;
+ t->tm_mday -= extra;
+
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sonw [ !<alpha> && !<digit> ]
+bool Datetime::initializeSonw (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("sonw"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+
+ int extra = (t->tm_wday + 6) % 7;
+ t->tm_mday -= extra;
+ t->tm_mday += 7;
+
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// eopw [ !<alpha> && !<digit> ]
+bool Datetime::initializeEopw (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("eopw"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+
+ int extra = (t->tm_wday + 6) % 7;
+ t->tm_mday -= extra;
+
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// eow [ !<alpha> && !<digit> ]
+bool Datetime::initializeEow (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("eow"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+
+ int extra = (t->tm_wday + 6) % 7;
+ t->tm_mday -= extra;
+ t->tm_mday += 7;
+
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// eonw [ !<alpha> && !<digit> ]
+bool Datetime::initializeEonw (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("eonw"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_mday += 15 - t->tm_wday;
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sopww [ !<alpha> && !<digit> ]
+bool Datetime::initializeSopww (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("sopww"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_mday += -6 - t->tm_wday;
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// soww [ !<alpha> && !<digit> ]
+bool Datetime::initializeSoww (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("soww"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_mday += 8 - t->tm_wday;
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sonww [ !<alpha> && !<digit> ]
+bool Datetime::initializeSonww (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("sonww"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_mday += 8 - t->tm_wday;
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// eopww [ !<alpha> && !<digit> ]
+bool Datetime::initializeEopww (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("eopww"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_mday -= (t->tm_wday + 1) % 7;
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// eoww [ !<alpha> && !<digit> ]
+bool Datetime::initializeEoww (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("eoww"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_mday += 6 - t->tm_wday;
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// eonww [ !<alpha> && !<digit> ]
+bool Datetime::initializeEonww (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("eonww"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_mday += 13 - t->tm_wday;
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sopm [ !<alpha> && !<digit> ]
+bool Datetime::initializeSopm (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("sopm"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+
+ if (t->tm_mon == 0)
+ {
+ t->tm_year--;
+ t->tm_mon = 11;
+ }
+ else
+ t->tm_mon--;
+
+ t->tm_mday = 1;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// som [ !<alpha> && !<digit> ]
+bool Datetime::initializeSom (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("som"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_mday = 1;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sonm [ !<alpha> && !<digit> ]
+bool Datetime::initializeSonm (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("sonm"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+
+ t->tm_mon++;
+ if (t->tm_mon > 11)
+ {
+ t->tm_year++;
+ t->tm_mon = 0;
+ }
+
+ t->tm_mday = 1;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// eopm [ !<alpha> && !<digit> ]
+bool Datetime::initializeEopm (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("eopm"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_mday = 1;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// eom [ !<alpha> && !<digit> ]
+bool Datetime::initializeEom (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("eom"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+
+ t->tm_mon++;
+ if (t->tm_mon > 11)
+ {
+ t->tm_year++;
+ t->tm_mon = 0;
+ }
+
+ t->tm_mday = 1;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// eonm [ !<alpha> && !<digit> ]
+bool Datetime::initializeEonm (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("eonm"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_mday = 1;
+ t->tm_mon += 2;
+ if (t->tm_mon > 11)
+ {
+ t->tm_year++;
+ t->tm_mon -= 12;
+ }
+
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sopq [ !<alpha> && !<digit> ]
+bool Datetime::initializeSopq (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("sopq"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_mon -= t->tm_mon % 3;
+ t->tm_mon -= 3;
+ if (t->tm_mon < 0)
+ {
+ t->tm_mon += 12;
+ t->tm_year--;
+ }
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_mday = 1;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// soq [ !<alpha> && !<digit> ]
+bool Datetime::initializeSoq (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("soq"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_mon -= t->tm_mon % 3;
+ t->tm_mday = 1;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sonq [ !<alpha> && !<digit> ]
+bool Datetime::initializeSonq (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("sonq"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_mon += 3 - (t->tm_mon % 3);
+ if (t->tm_mon > 11)
+ {
+ t->tm_mon -= 12;
+ ++t->tm_year;
+ }
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_mday = 1;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// eopq [ !<alpha> && !<digit> ]
+bool Datetime::initializeEopq (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("eopq"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_mon -= t->tm_mon % 3;
+ t->tm_mday = 1;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// eoq [ !<alpha> && !<digit> ]
+bool Datetime::initializeEoq (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("eoq"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_mon += 3 - (t->tm_mon % 3);
+ if (t->tm_mon > 11)
+ {
+ t->tm_mon -= 12;
+ ++t->tm_year;
+ }
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_mday = 1;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// eonq [ !<alpha> && !<digit> ]
+bool Datetime::initializeEonq (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("eonq"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_mon += 6 - (t->tm_mon % 3);
+ if (t->tm_mon > 11)
+ {
+ t->tm_mon -= 12;
+ ++t->tm_year;
+ }
+
+ t->tm_mday = 1;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sopy [ !<alpha> && !<digit> ]
+bool Datetime::initializeSopy (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("sopy"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_mon = 0;
+ t->tm_mday = 1;
+ t->tm_year--;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// soy [ !<alpha> && !<digit> ]
+bool Datetime::initializeSoy (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("soy"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_mon = 0;
+ t->tm_mday = 1;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sony [ !<alpha> && !<digit> ]
+bool Datetime::initializeSony (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("sony"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_mon = 0;
+ t->tm_mday = 1;
+ t->tm_year++;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// eopy [ !<alpha> && !<digit> ]
+bool Datetime::initializeEopy (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("eopy"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_mon = 0;
+ t->tm_mday = 1;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// eoy [ !<alpha> && !<digit> ]
+bool Datetime::initializeEoy (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("eoy"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_mon = 0;
+ t->tm_mday = 1;
+ t->tm_year++;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// eony [ !<alpha> && !<digit> ]
+bool Datetime::initializeEony (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("eony"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+ t->tm_mon = 0;
+ t->tm_mday = 1;
+ t->tm_year += 2;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// easter [ !<alpha> && !<digit> ]
+// eastermonday [ !<alpha> && !<digit> ]
+// ascension [ !<alpha> && !<digit> ]
+// pentecost [ !<alpha> && !<digit> ]
+// goodfriday [ !<alpha> && !<digit> ]
+bool Datetime::initializeEaster (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ std::vector <std::string> holidays = {"eastermonday", "easter", "ascension", "pentecost", "goodfriday"};
+ std::vector <int> offsets = { 1, 0, 39, 49, -2};
+
+ std::string token;
+ for (int holiday = 0; holiday < 5; ++holiday)
+ {
+ if (pig.skipLiteral (holidays[holiday]) &&
+ ! unicodeLatinAlpha (pig.peek ()) &&
+ ! unicodeLatinDigit (pig.peek ()))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ easter (t);
+ _date = mktime (t);
+
+ // If the result is earlier this year, then recalc for next year.
+ if (_date < now)
+ {
+ t = localtime (&now);
+ t->tm_year++;
+ easter (t);
+ }
+
+ // Adjust according to holiday-specific offsets.
+ t->tm_mday += offsets[holiday];
+
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// midsommar [ !<alpha> && !<digit> ]
+bool Datetime::initializeMidsommar (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("midsommar"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+ midsommar (t);
+ _date = mktime (t);
+
+ // If the result is earlier this year, then recalc for next year.
+ if (_date < now)
+ {
+ t = localtime (&now);
+ t->tm_year++;
+ midsommar (t);
+ }
+
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// midsommarafton [ !<alpha> && !<digit> ]
+// juhannus [ !<alpha> && !<digit> ]
+bool Datetime::initializeMidsommarafton (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skipLiteral ("midsommarafton") ||
+ pig.skipLiteral ("juhannus"))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+ midsommarafton (t);
+ _date = mktime (t);
+
+ // If the result is earlier this year, then recalc for next year.
+ if (_date < now)
+ {
+ t = localtime (&now);
+ t->tm_year++;
+ midsommarafton (t);
+ }
+
+ _date = mktime (t);
+ return true;
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 8am
+// 8a
+// 8:30am
+// 8:30a
+// 8:30
+//
+// \d+ [ : \d{2} ] [ am | a | pm | p ] [ !<alpha> && !<digit> && !: && !+ && !- ]
+//
+bool Datetime::initializeInformalTime (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ int digit = 0;
+ bool needDesignator = true; // Require am/pm.
+ bool haveDesignator = false; // Provided am/pm.
+ if (pig.getDigit (digit))
+ {
+ int hours = digit;
+ if (pig.getDigit (digit))
+ hours = 10 * hours + digit;
+
+ int minutes = 0;
+ int seconds = 0;
+ if (pig.skip (':'))
+ {
+ if (! pig.getDigit2 (minutes))
+ {
+ pig.restoreTo (checkpoint);
+ return false;
+ }
+
+ if (pig.skip (':'))
+ {
+ if (! pig.getDigits (seconds))
+ {
+ pig.restoreTo (checkpoint);
+ return false;
+ }
+ }
+
+ needDesignator = false;
+ }
+
+ if (pig.skipLiteral ("am") ||
+ pig.skipLiteral ("a"))
+ {
+ haveDesignator = true;
+ if (hours == 12)
+ hours = 0;
+ }
+
+ else if (pig.skipLiteral ("pm") ||
+ pig.skipLiteral ("p"))
+ {
+ // Note: '12pm is an exception:
+ // 12am = 0h
+ // 11am = 11h + 12h
+ // 12pm = 12h
+ // 1pm = 1h + 12h
+ if (hours != 12)
+ hours += 12;
+
+ haveDesignator = true;
+ }
+
+ // Informal time needs to be terminated.
+ auto following = pig.peek ();
+ if (unicodeLatinAlpha (following) ||
+ unicodeLatinDigit (following) ||
+ following == ':' ||
+ following == '-' ||
+ following == '+')
+ {
+ pig.restoreTo (checkpoint);
+ return false;
+ }
+
+ if (haveDesignator || ! needDesignator)
+ {
+ // Midnight today + hours:minutes:seconds.
+ time_t now = time (nullptr);
+ struct tm* t = localtime (&now);
+
+ int now_seconds = (t->tm_hour * 3600) + (t->tm_min * 60) + t->tm_sec;
+ int calc_seconds = (hours * 3600) + (minutes * 60) + seconds;
+
+ if (calc_seconds < now_seconds)
+ ++t->tm_mday;
+
+ // Basic validation.
+ if (hours >= 0 && hours < 24 &&
+ minutes >= 0 && minutes < 60 &&
+ seconds >= 0 && seconds < 60)
+ {
+ t->tm_hour = hours;
+ t->tm_min = minutes;
+ t->tm_sec = seconds;
+ t->tm_isdst = -1;
+ _date = mktime (t);
+
+ return true;
+ }
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Datetime::easter (struct tm* t) const
+{
+ int Y = t->tm_year + 1900;
+ int a = Y % 19;
+ int b = Y / 100;
+ int c = Y % 100;
+ int d = b / 4;
+ int e = b % 4;
+ int f = (b + 8) / 25;
+ int g = (b - f + 1) / 3;
+ int h = (19 * a + b - d - g + 15) % 30;
+ int i = c / 4;
+ int k = c % 4;
+ int L = (32 + 2 * e + 2 * i - h - k) % 7;
+ int m = (a + 11 * h + 22 * L) / 451;
+ int month = (h + L - 7 * m + 114) / 31;
+ int day = ((h + L - 7 * m + 114) % 31) + 1;
+
+ t->tm_isdst = -1; // Requests that mktime determine summer time effect.
+ t->tm_mday = day;
+ t->tm_mon = month - 1;
+ t->tm_year = Y - 1900;
+ t->tm_isdst = -1;
+ t->tm_hour = t->tm_min = t->tm_sec = 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Datetime::midsommar (struct tm* t) const
+{
+ t->tm_mon = 5; // June.
+ t->tm_mday = 20; // Saturday after 20th.
+ t->tm_hour = t->tm_min = t->tm_sec = 0; // Midnight.
+ t->tm_isdst = -1; // Probably DST, but check.
+
+ time_t then = mktime (t); // Obtain the weekday of June 20th.
+ struct tm* mid = localtime (&then);
+ t->tm_mday += 6 - mid->tm_wday; // How many days after 20th.
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Datetime::midsommarafton (struct tm* t) const
+{
+ t->tm_mon = 5; // June.
+ t->tm_mday = 19; // Saturday after 20th.
+ t->tm_hour = t->tm_min = t->tm_sec = 0; // Midnight.
+ t->tm_isdst = -1; // Probably DST, but check.
+
+ time_t then = mktime (t); // Obtain the weekday of June 19th.
+ struct tm* mid = localtime (&then);
+ t->tm_mday += 5 - mid->tm_wday; // How many days after 19th.
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Suggested date expressions:
+// {ordinal} {day} in|of {month}
+// last|past|next|this {day}
+// last|past|next|this {month}
+// last|past|next|this week
+// last|past|next|this month
+// last|past|next|this weekend
+// last|past|next|this year
+// {day} last|past|next|this week
+// {day} [at] {time}
+// {time} {day}
+//
+// Candidates:
+// <dayname> <time>
+// <time>
+// tue 9am
+// Friday before easter
+// 3 days before eom
+// in the morning
+// am|pm
+// 4pm
+// noon
+// midnight
+// tomorrow in one year
+// in two weeks
+// 2 weeks from now
+// 2 weeks ago tuesday
+// thursday in 2 weeks
+// last day next month
+// 10 days from today
+// thursday before last weekend in may
+// friday last full week in may
+// 3rd wednesday this month
+// 3 weeks after 2nd tuesday next month
+// 100 days from the beginning of the month
+// 10 days after last monday
+// sunday in the evening
+// in 6 hours
+// 6 in the morning
+// kl 18
+// feb 11
+// 11 feb
+// 2011-02-08
+// 11/19/2011
+// next business day
+// new moon
+// full moon
+// in 28 days
+// 3rd quarter
+// week 23
+// {number} {unit}
+// - {number} {unit}
+// {ordinal} {unit} in {larger-unit}
+// end of day tomorrow
+// end of {day}
+// by {day}
+// first thing {day}
+//
+
+////////////////////////////////////////////////////////////////////////////////
+// <ordinal> <weekday> in|of <month>
+bool Datetime::initializeNthDayInMonth (const std::vector <std::string>& tokens)
+{
+ if (tokens.size () == 4)
+ {
+ int ordinal {0};
+ if (isOrdinal (tokens[0], ordinal))
+ {
+ auto day = Datetime::dayOfWeek (tokens[1]);
+ if (day != -1)
+ {
+ if (tokens[2] == "in" ||
+ tokens[2] == "of")
+ {
+ auto month = Datetime::monthOfYear (tokens[3]);
+ if (month != -1)
+ {
+ std::cout << "# ordinal=" << ordinal << " day=" << day << " in month=" << month << '\n';
+
+ // TODO Assume 1st of the month
+ // TODO Assume current year
+ // TODO Determine the day
+ // TODO Project forwards/backwards, to the desired day
+ // TODO Add ((ordinal - 1) * 7) days
+
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::isOrdinal (const std::string& token, int& ordinal)
+{
+ Pig p (token);
+ int number;
+ std::string suffix;
+ if (p.getDigits (number) &&
+ p.getRemainder (suffix))
+ {
+ if (((number >= 11 || number <= 13) && suffix == "th") ||
+ (number % 10 == 1 && suffix == "st") ||
+ (number % 10 == 2 && suffix == "nd") ||
+ (number % 10 == 3 && suffix == "rd") ||
+ ( suffix == "th"))
+ {
+ ordinal = number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Validation via simple range checking.
+bool Datetime::validate ()
+{
+ // _year;
+ if ((_year && (_year < 1900 || _year > 2200)) ||
+ (_month && (_month < 1 || _month > 12)) ||
+ (_week && (_week < 1 || _week > 53)) ||
+ (_weekday && (_weekday < 0 || _weekday > 6)) ||
+ (_julian && (_julian < 1 || _julian > Datetime::daysInYear (_year))) ||
+ (_day && (_day < 1 || _day > Datetime::daysInMonth (_year, _month))) ||
+ (_seconds && (_seconds < 1 || _seconds > 86400)) ||
+ (_offset && (_offset < -86400 || _offset > 86400)))
+ return false;
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// int tm_sec; seconds (0 - 60)
+// int tm_min; minutes (0 - 59)
+// int tm_hour; hours (0 - 23)
+// int tm_mday; day of month (1 - 31)
+// int tm_mon; month of year (0 - 11)
+// int tm_year; year - 1900
+// int tm_wday; day of week (Sunday = 0)
+// int tm_yday; day of year (0 - 365)
+// int tm_isdst; is summer time in effect?
+// char *tm_zone; abbreviation of timezone name
+// long tm_gmtoff; offset from UTC in seconds
+void Datetime::resolve ()
+{
+ // Don't touch the original values.
+ int year = _year;
+ int month = _month;
+ int week = _week;
+ int weekday = _weekday;
+ int julian = _julian;
+ int day = _day;
+ int seconds = _seconds;
+ int offset = _offset;
+ bool utc = _utc;
+
+ // Get current time.
+ time_t now = time (nullptr);
+
+ // A UTC offset needs to be accommodated. Once the offset is subtracted,
+ // only local and UTC times remain.
+ if (offset)
+ {
+ seconds -= offset;
+ now -= offset;
+ utc = true;
+ }
+
+ // Get 'now' in the relevant location.
+ struct tm* t_now = utc ? gmtime (&now) : localtime (&now);
+
+ int seconds_now = (t_now->tm_hour * 3600) +
+ (t_now->tm_min * 60) +
+ t_now->tm_sec;
+
+ // Project forward one day if the specified seconds are earlier in the day
+ // than the current seconds.
+ // TODO This does not cover the inverse case of subtracting 86400.
+ if (year == 0 &&
+ month == 0 &&
+ day == 0 &&
+ week == 0 &&
+ weekday == 0 &&
+ seconds < seconds_now)
+ {
+ seconds += 86400;
+ }
+
+ // Convert week + weekday --> julian.
+ if (week)
+ {
+ julian = (week * 7) + weekday - dayOfWeek (year, 1, 4) - 3;
+ }
+
+ // Provide default values for year, month, day.
+ else
+ {
+ // Default values for year, month, day:
+ //
+ // y m d --> y m d
+ // y m - --> y m 1
+ // y - - --> y 1 1
+ // - - - --> now now now
+ //
+ if (year == 0)
+ {
+ year = t_now->tm_year + 1900;
+ month = t_now->tm_mon + 1;
+ day = t_now->tm_mday;
+ }
+ else
+ {
+ if (month == 0)
+ {
+ month = 1;
+ day = 1;
+ }
+ else if (day == 0)
+ day = 1;
+ }
+ }
+
+ if (julian)
+ {
+ month = 1;
+ day = julian;
+ }
+
+ struct tm t {};
+ t.tm_isdst = -1; // Requests that mktime/gmtime determine summer time effect.
+ t.tm_year = year - 1900;
+ t.tm_mon = month - 1;
+ t.tm_mday = day;
+
+ if (seconds > 86400)
+ {
+ int days = seconds / 86400;
+ t.tm_mday += days;
+ seconds %= 86400;
+ }
+
+ t.tm_hour = seconds / 3600;
+ t.tm_min = (seconds % 3600) / 60;
+ t.tm_sec = seconds % 60;
+
+ _date = utc ? timegm (&t) : mktime (&t);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+time_t Datetime::toEpoch () const
+{
+ return _date;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string Datetime::toEpochString () const
+{
+ return format ("{1}", _date);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 19980119T070000Z = YYYYMMDDThhmmssZ
+std::string Datetime::toISO () const
+{
+ struct tm* t = gmtime (&_date);
+
+ std::stringstream iso;
+ iso << std::setw (4) << std::setfill ('0') << t->tm_year + 1900
+ << std::setw (2) << std::setfill ('0') << t->tm_mon + 1
+ << std::setw (2) << std::setfill ('0') << t->tm_mday
+ << 'T'
+ << std::setw (2) << std::setfill ('0') << t->tm_hour
+ << std::setw (2) << std::setfill ('0') << t->tm_min
+ << std::setw (2) << std::setfill ('0') << t->tm_sec
+ << 'Z';
+
+ return iso.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 1998-01-19T07:00:00 = YYYY-MM-DDThh:mm:ss
+std::string Datetime::toISOLocalExtended () const
+{
+ struct tm* t = localtime (&_date);
+
+ std::stringstream iso;
+ iso << std::setw (4) << std::setfill ('0') << t->tm_year + 1900
+ << '-'
+ << std::setw (2) << std::setfill ('0') << t->tm_mon + 1
+ << '-'
+ << std::setw (2) << std::setfill ('0') << t->tm_mday
+ << 'T'
+ << std::setw (2) << std::setfill ('0') << t->tm_hour
+ << ':'
+ << std::setw (2) << std::setfill ('0') << t->tm_min
+ << ':'
+ << std::setw (2) << std::setfill ('0') << t->tm_sec;
+
+ return iso.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+double Datetime::toJulian () const
+{
+ return (_date / 86400.0) + 2440587.5;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Datetime::toYMD (int& y, int& m, int& d) const
+{
+ struct tm* t = localtime (&_date);
+
+ m = t->tm_mon + 1;
+ d = t->tm_mday;
+ y = t->tm_year + 1900;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+const std::string Datetime::toString (const std::string& format) const
+{
+ std::stringstream formatted;
+ for (unsigned int i = 0; i < format.length (); ++i)
+ {
+ int c = format[i];
+ switch (c)
+ {
+ case 'm': formatted << month (); break;
+ case 'M': formatted << std::setw (2) << std::setfill ('0') << month (); break;
+ case 'd': formatted << day (); break;
+ case 'D': formatted << std::setw (2) << std::setfill ('0') << day (); break;
+ case 'y': formatted << std::setw (2) << std::setfill ('0') << (year () % 100); break;
+ case 'Y': formatted << year (); break;
+ case 'a': formatted << Datetime::dayNameShort (dayOfWeek ()); break;
+ case 'A': formatted << Datetime::dayName (dayOfWeek ()); break;
+ case 'b': formatted << Datetime::monthNameShort (month ()); break;
+ case 'B': formatted << Datetime::monthName (month ()); break;
+ case 'v': formatted << week (); break;
+ case 'V': formatted << std::setw (2) << std::setfill ('0') << week (); break;
+ case 'h': formatted << hour (); break;
+ case 'H': formatted << std::setw (2) << std::setfill ('0') << hour (); break;
+ case 'n': formatted << minute (); break;
+ case 'N': formatted << std::setw (2) << std::setfill ('0') << minute (); break;
+ case 's': formatted << second (); break;
+ case 'S': formatted << std::setw (2) << std::setfill ('0') << second (); break;
+ case 'j': formatted << dayOfYear (); break;
+ case 'J': formatted << std::setw (3) << std::setfill ('0') << dayOfYear (); break;
+ case 'w': formatted << dayOfWeek (); break;
+ default: formatted << static_cast <char> (c); break;
+ }
+ }
+
+ return formatted.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Datetime Datetime::startOfDay () const
+{
+ return Datetime (year (), month (), day ());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Datetime Datetime::startOfWeek () const
+{
+ Datetime sow (_date);
+ sow -= (dayOfWeek () * 86400);
+ return Datetime (sow.year (), sow.month (), sow.day ());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Datetime Datetime::startOfMonth () const
+{
+ return Datetime (year (), month (), 1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Datetime Datetime::startOfYear () const
+{
+ return Datetime (year (), 1, 1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::valid (const std::string& input, const std::string& format)
+{
+ try
+ {
+ Datetime test (input, format);
+ }
+
+ catch (...)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::valid (
+ const int y, const int m, const int d,
+ const int hr, const int mi, const int se)
+{
+ if (hr < 0 || hr > 24)
+ return false;
+
+ if (mi < 0 || mi > 59)
+ return false;
+
+ if (se < 0 || se > 59)
+ return false;
+
+ if (hr == 24 &&
+ (mi != 0 ||
+ se != 0))
+ return false;
+
+ return Datetime::valid (y, m, d);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::valid (const int y, const int m, const int d)
+{
+ // Check that the year is valid.
+ if (y < 0)
+ return false;
+
+ // Check that the month is valid.
+ if (m < 1 || m > 12)
+ return false;
+
+ // Finally check that the days fall within the acceptable range for this
+ // month, and whether or not this is a leap year.
+ if (d < 1 || d > Datetime::daysInMonth (y, m))
+ return false;
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Julian
+bool Datetime::valid (const int y, const int d)
+{
+ // Check that the year is valid.
+ if (y < 0)
+ return false;
+
+ if (d < 1 || d > Datetime::daysInYear (y))
+ return false;
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Static
+bool Datetime::leapYear (int year)
+{
+ return ((! (year % 4)) && (year % 100)) ||
+ ! (year % 400);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Static
+int Datetime::daysInMonth (int year, int month)
+{
+ // Protect against arguments being passed in the wrong order.
+ assert (year >= 1969 && year < 2100);
+ assert (month >= 1 && month <= 31);
+
+ static int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+ if (month == 2 && Datetime::leapYear (year))
+ return 29;
+
+ return days[month - 1];
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Static
+int Datetime::daysInYear (int year)
+{
+ return Datetime::leapYear (year) ? 366 : 365;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Static
+std::string Datetime::monthName (int month)
+{
+ assert (month > 0);
+ assert (month <= 12);
+ return upperCaseFirst (monthNames[month - 1]);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Static
+std::string Datetime::monthNameShort (int month)
+{
+ assert (month > 0);
+ assert (month <= 12);
+ return upperCaseFirst (monthNames[month - 1]).substr (0, 3);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Static
+std::string Datetime::dayName (int dow)
+{
+ assert (dow >= 0);
+ assert (dow <= 6);
+ return upperCaseFirst (dayNames[dow]);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Static
+std::string Datetime::dayNameShort (int dow)
+{
+ assert (dow >= 0);
+ assert (dow <= 6);
+ return upperCaseFirst (dayNames[dow]).substr (0, 3);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Static
+int Datetime::dayOfWeek (const std::string& input)
+{
+ if (Datetime::minimumMatchLength== 0)
+ Datetime::minimumMatchLength = 3;
+
+ for (unsigned int i = 0; i < dayNames.size (); ++i)
+ if (closeEnough (dayNames[i], input, Datetime::minimumMatchLength))
+ return i;
+
+ return -1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Using Zeller's Congruence.
+// Static
+int Datetime::dayOfWeek (int year, int month, int day)
+{
+ int adj = (14 - month) / 12;
+ int m = month + 12 * adj - 2;
+ int y = year - adj;
+ return (day + (13 * m - 1) / 5 + y + y / 4 - y / 100 + y / 400) % 7;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Static
+int Datetime::monthOfYear (const std::string& input)
+{
+ if (Datetime::minimumMatchLength== 0)
+ Datetime::minimumMatchLength = 3;
+
+ for (unsigned int i = 0; i < monthNames.size (); ++i)
+ if (closeEnough (monthNames[i], input, Datetime::minimumMatchLength))
+ return i + 1;
+
+ return -1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Static
+int Datetime::length (const std::string& format)
+{
+ int len = 0;
+ for (auto& i : format)
+ {
+ switch (i)
+ {
+ case 'm':
+ case 'M':
+ case 'd':
+ case 'D':
+ case 'y':
+ case 'v':
+ case 'V':
+ case 'h':
+ case 'H':
+ case 'n':
+ case 'N':
+ case 's':
+ case 'S': len += 2; break;
+ case 'b':
+ case 'j':
+ case 'J':
+ case 'a': len += 3; break;
+ case 'Y': len += 4; break;
+ case 'A':
+ case 'B': len += 10; break;
+
+ // Calculate the width, don't assume a single character width.
+ default: len += mk_wcwidth (i); break;
+ }
+ }
+
+ return len;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Datetime::month () const
+{
+ struct tm* t = localtime (&_date);
+ return t->tm_mon + 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Datetime::week () const
+{
+ struct tm* t = localtime (&_date);
+
+ char weekStr[3];
+ if (Datetime::weekstart == 0)
+ strftime (weekStr, sizeof (weekStr), "%U", t);
+ else if (Datetime::weekstart == 1)
+ strftime (weekStr, sizeof (weekStr), "%V", t);
+ else
+ throw std::string ("The week may only start on a Sunday or Monday.");
+
+ int weekNumber = strtol (weekStr, nullptr, 10);
+ if (weekstart == 0)
+ weekNumber += 1;
+
+ return weekNumber;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Datetime::day () const
+{
+ struct tm* t = localtime (&_date);
+ return t->tm_mday;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Datetime::year () const
+{
+ struct tm* t = localtime (&_date);
+ return t->tm_year + 1900;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Datetime::dayOfWeek () const
+{
+ struct tm* t = localtime (&_date);
+ return t->tm_wday;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Datetime::dayOfYear () const
+{
+ struct tm* t = localtime (&_date);
+ return t->tm_yday + 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Datetime::hour () const
+{
+ struct tm* t = localtime (&_date);
+ return t->tm_hour;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Datetime::minute () const
+{
+ struct tm* t = localtime (&_date);
+ return t->tm_min;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Datetime::second () const
+{
+ struct tm* t = localtime (&_date);
+ return t->tm_sec;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::operator== (const Datetime& rhs) const
+{
+ return rhs._date == _date;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::operator!= (const Datetime& rhs) const
+{
+ return rhs._date != _date;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::operator< (const Datetime& rhs) const
+{
+ return _date < rhs._date;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::operator> (const Datetime& rhs) const
+{
+ return _date > rhs._date;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::operator<= (const Datetime& rhs) const
+{
+ return _date <= rhs._date;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::operator>= (const Datetime& rhs) const
+{
+ return _date >= rhs._date;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::sameHour (const Datetime& rhs) const
+{
+ return year () == rhs.year () &&
+ month () == rhs.month () &&
+ day () == rhs.day () &&
+ hour () == rhs.hour ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::sameDay (const Datetime& rhs) const
+{
+ return year () == rhs.year () &&
+ month () == rhs.month () &&
+ day () == rhs.day ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::sameWeek (const Datetime& rhs) const
+{
+ return year () == rhs.year () &&
+ week () == rhs.week ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::sameMonth (const Datetime& rhs) const
+{
+ return year () == rhs.year () &&
+ month () == rhs.month ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::sameQuarter (const Datetime& rhs) const
+{
+ return year () == rhs.year () &&
+ ((month () - 1) / 3) == ((rhs.month () - 1) / 3);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Datetime::sameYear (const Datetime& rhs) const
+{
+ return year () == rhs.year ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Datetime Datetime::operator+ (const int delta)
+{
+ return Datetime (_date + delta);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Datetime Datetime::operator- (const int delta)
+{
+ return Datetime (_date - delta);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Datetime& Datetime::operator+= (const int delta)
+{
+ _date += (time_t) delta;
+ return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Datetime& Datetime::operator-= (const int delta)
+{
+ _date -= (time_t) delta;
+ return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+time_t Datetime::operator- (const Datetime& rhs)
+{
+ return _date - rhs._date;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Prefix decrement by one day.
+void Datetime::operator-- ()
+{
+ Datetime yesterday = startOfDay () - 1;
+ yesterday = Datetime (yesterday.year (),
+ yesterday.month (),
+ yesterday.day (),
+ hour (),
+ minute (),
+ second ());
+ _date = yesterday._date;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Postfix decrement by one day.
+void Datetime::operator-- (int)
+{
+ Datetime yesterday = startOfDay () - 1;
+ yesterday = Datetime (yesterday.year (),
+ yesterday.month (),
+ yesterday.day (),
+ hour (),
+ minute (),
+ second ());
+ _date = yesterday._date;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Prefix increment by one day.
+void Datetime::operator++ ()
+{
+ Datetime tomorrow = (startOfDay () + 90001).startOfDay ();
+ tomorrow = Datetime (tomorrow.year (),
+ tomorrow.month (),
+ tomorrow.day (),
+ hour (),
+ minute (),
+ second ());
+ _date = tomorrow._date;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Postfix increment by one day.
+void Datetime::operator++ (int)
+{
+ Datetime tomorrow = (startOfDay () + 90001).startOfDay ();
+ tomorrow = Datetime (tomorrow.year (),
+ tomorrow.month (),
+ tomorrow.day (),
+ hour (),
+ minute (),
+ second ());
+ _date = tomorrow._date;
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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_DATETIME
+#define INCLUDED_DATETIME
+
+#include <string>
+#include <ctime>
+#include <Pig.h>
+
+class Datetime
+{
+public:
+ static int weekstart;
+ static int minimumMatchLength;
+ static bool isoEnabled;
+ static bool standaloneDateEnabled;
+ static bool standaloneTimeEnabled;
+
+ Datetime ();
+ Datetime (const std::string&, const std::string& format = "");
+ Datetime (time_t);
+ Datetime (const int, const int, const int);
+ Datetime (const int, const int, const int, const int, const int, const int);
+ bool parse (const std::string&, std::string::size_type&, const std::string& format = "");
+ time_t toEpoch () const;
+ std::string toEpochString () const;
+ std::string toISO () const;
+ std::string toISOLocalExtended () const;
+ double toJulian () const;
+ void toYMD (int&, int&, int&) const;
+ const std::string toString (const std::string& format = "Y-M-D") const;
+
+ Datetime startOfDay () const;
+ Datetime startOfWeek () const;
+ Datetime startOfMonth () const;
+ Datetime startOfYear () const;
+
+ static bool valid (const std::string&, const std::string& format = "");
+ static bool valid (const int, const int, const int, const int, const int, const int);
+ static bool valid (const int, const int, const int);
+ static bool valid (const int, const int);
+ static bool leapYear (int);
+ static int daysInMonth (int, int);
+ static int daysInYear (int);
+ static std::string monthName (int);
+ static std::string monthNameShort (int);
+ static std::string dayName (int);
+ static std::string dayNameShort (int);
+ static int dayOfWeek (const std::string&);
+ static int dayOfWeek (int, int, int);
+ static int monthOfYear (const std::string&);
+ static int length (const std::string&);
+
+ int month () const;
+ int week () const;
+ int day () const;
+ int year () const;
+ int dayOfWeek () const;
+ int dayOfYear () const;
+ int hour () const;
+ int minute () const;
+ int second () const;
+
+ bool operator== (const Datetime&) const;
+ bool operator!= (const Datetime&) const;
+ bool operator< (const Datetime&) const;
+ bool operator> (const Datetime&) const;
+ bool operator<= (const Datetime&) const;
+ bool operator>= (const Datetime&) const;
+ bool sameHour (const Datetime&) const;
+ bool sameDay (const Datetime&) const;
+ bool sameWeek (const Datetime&) const;
+ bool sameMonth (const Datetime&) const;
+ bool sameQuarter (const Datetime&) const;
+ bool sameYear (const Datetime&) const;
+ Datetime operator+ (const int);
+ Datetime operator- (const int);
+ Datetime& operator+= (const int);
+ Datetime& operator-= (const int);
+ time_t operator- (const Datetime&);
+ void operator-- (); // Prefix
+ void operator-- (int); // Postfix
+ void operator++ (); // Prefix
+ void operator++ (int); // Postfix
+
+private:
+ void clear ();
+ bool parse_formatted (Pig&, const std::string&);
+ bool parse_named (Pig&);
+ bool parse_epoch (Pig&);
+ bool parse_date_time_ext (Pig&);
+ bool parse_date_ext (Pig&);
+ bool parse_off_ext (Pig&);
+ bool parse_time_ext (Pig&, bool terminated = true);
+ bool parse_time_utc_ext (Pig&);
+ bool parse_time_off_ext (Pig&);
+ bool parse_date_time (Pig&);
+ bool parse_date (Pig&);
+ bool parse_time_utc (Pig&);
+ bool parse_time_off (Pig&);
+ bool parse_time (Pig&, bool terminated = true);
+ bool parse_off (Pig&);
+
+ bool parse_year (Pig&, int&);
+ bool parse_month (Pig&, int&);
+ bool parse_week (Pig&, int&);
+ bool parse_julian (Pig&, int&);
+ bool parse_day (Pig&, int&);
+ bool parse_weekday (Pig&, int&);
+ bool parse_hour (Pig&, int&);
+ bool parse_minute (Pig&, int&);
+ bool parse_second (Pig&, int&);
+ bool parse_off_hour (Pig&, int&);
+ bool parse_off_minute (Pig&, int&);
+
+ bool initializeNow (Pig&);
+ bool initializeYesterday (Pig&);
+ bool initializeToday (Pig&);
+ bool initializeTomorrow (Pig&);
+ bool initializeOrdinal (Pig&);
+ bool initializeDayName (Pig&);
+ bool initializeMonthName (Pig&);
+ bool initializeLater (Pig&);
+ bool initializeSopd (Pig&);
+ bool initializeSod (Pig&);
+ bool initializeSond (Pig&);
+ bool initializeEopd (Pig&);
+ bool initializeEod (Pig&);
+ bool initializeEond (Pig&);
+ bool initializeSopw (Pig&);
+ bool initializeSow (Pig&);
+ bool initializeSonw (Pig&);
+ bool initializeEopw (Pig&);
+ bool initializeEow (Pig&);
+ bool initializeEonw (Pig&);
+ bool initializeSopww (Pig&);
+ bool initializeSonww (Pig&);
+ bool initializeSoww (Pig&);
+ bool initializeEopww (Pig&);
+ bool initializeEonww (Pig&);
+ bool initializeEoww (Pig&);
+ bool initializeSopm (Pig&);
+ bool initializeSom (Pig&);
+ bool initializeSonm (Pig&);
+ bool initializeEopm (Pig&);
+ bool initializeEom (Pig&);
+ bool initializeEonm (Pig&);
+ bool initializeSopq (Pig&);
+ bool initializeSoq (Pig&);
+ bool initializeSonq (Pig&);
+ bool initializeEopq (Pig&);
+ bool initializeEoq (Pig&);
+ bool initializeEonq (Pig&);
+ bool initializeSopy (Pig&);
+ bool initializeSoy (Pig&);
+ bool initializeSony (Pig&);
+ bool initializeEopy (Pig&);
+ bool initializeEoy (Pig&);
+ bool initializeEony (Pig&);
+ bool initializeEaster (Pig&);
+ bool initializeMidsommar (Pig&);
+ bool initializeMidsommarafton (Pig&);
+ bool initializeInformalTime (Pig&);
+ void easter (struct tm*) const;
+ void midsommar (struct tm*) const;
+ void midsommarafton (struct tm*) const;
+
+ bool initializeNthDayInMonth (const std::vector <std::string>&);
+
+ bool isOrdinal (const std::string&, int&);
+
+ bool validate ();
+ void resolve ();
+ std::string dump () const;
+
+public:
+ int _year {0};
+ int _month {0};
+ int _week {0};
+ int _weekday {0};
+ int _julian {0};
+ int _day {0};
+ int _seconds {0};
+ int _offset {0};
+ bool _utc {false};
+ time_t _date {0};
+};
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <Duration.h>
+#include <unicode.h>
+#include <sstream>
+#include <iomanip>
+#include <vector>
+
+bool Duration::standaloneSecondsEnabled = true;
+
+#define DAY 86400
+#define HOUR 3600
+#define MINUTE 60
+#define SECOND 1
+
+static struct
+{
+ std::string unit;
+ int seconds;
+ bool standalone;
+} durations[] =
+{
+ // These are sorted by first character, then length, so that Pig::getOneOf
+ // returns a maximal match.
+ {"annual", 365 * DAY, true },
+ {"biannual", 730 * DAY, true },
+ {"bimonthly", 61 * DAY, true },
+ {"biweekly", 14 * DAY, true },
+ {"biyearly", 730 * DAY, true },
+ {"daily", 1 * DAY, true },
+ {"days", 1 * DAY, false},
+ {"day", 1 * DAY, true },
+ {"d", 1 * DAY, false},
+ {"fortnight", 14 * DAY, true },
+ {"hours", 1 * HOUR, false},
+ {"hour", 1 * HOUR, true },
+ {"hrs", 1 * HOUR, false},
+ {"hr", 1 * HOUR, true },
+ {"h", 1 * HOUR, false},
+ {"minutes", 1 * MINUTE, false},
+ {"minute", 1 * MINUTE, true },
+ {"mins", 1 * MINUTE, false},
+ {"min", 1 * MINUTE, true },
+ {"monthly", 30 * DAY, true },
+ {"months", 30 * DAY, false},
+ {"month", 30 * DAY, true },
+ {"mnths", 30 * DAY, false},
+ {"mths", 30 * DAY, false},
+ {"mth", 30 * DAY, true },
+ {"mos", 30 * DAY, false},
+ {"mo", 30 * DAY, true },
+ {"m", 30 * DAY, false},
+ {"quarterly", 91 * DAY, true },
+ {"quarters", 91 * DAY, false},
+ {"quarter", 91 * DAY, true },
+ {"qrtrs", 91 * DAY, false},
+ {"qrtr", 91 * DAY, true },
+ {"qtrs", 91 * DAY, false},
+ {"qtr", 91 * DAY, true },
+ {"q", 91 * DAY, false},
+ {"semiannual", 183 * DAY, true },
+ {"sennight", 14 * DAY, false},
+ {"seconds", 1 * SECOND, false},
+ {"second", 1 * SECOND, true },
+ {"secs", 1 * SECOND, false},
+ {"sec", 1 * SECOND, true },
+ {"s", 1 * SECOND, false},
+ {"weekdays", 1 * DAY, true },
+ {"weekly", 7 * DAY, true },
+ {"weeks", 7 * DAY, false},
+ {"week", 7 * DAY, true },
+ {"wks", 7 * DAY, false},
+ {"wk", 7 * DAY, true },
+ {"w", 7 * DAY, false},
+ {"yearly", 365 * DAY, true },
+ {"years", 365 * DAY, false},
+ {"year", 365 * DAY, true },
+ {"yrs", 365 * DAY, false},
+ {"yr", 365 * DAY, true },
+ {"y", 365 * DAY, false},
+};
+
+#define NUM_DURATIONS (sizeof (durations) / sizeof (durations[0]))
+
+////////////////////////////////////////////////////////////////////////////////
+Duration::Duration ()
+{
+ clear ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Duration::Duration (const std::string& input)
+{
+ clear ();
+ std::string::size_type idx = 0;
+ parse (input, idx);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Duration::Duration (time_t input)
+{
+ clear ();
+ _period = input;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Duration::operator< (const Duration& other)
+{
+ return _period < other._period;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Duration::operator> (const Duration& other)
+{
+ return _period > other._period;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Duration::operator<= (const Duration& other)
+{
+ return _period <= other._period;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Duration::operator>= (const Duration& other)
+{
+ return _period >= other._period;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string Duration::toString () const
+{
+ std::stringstream s;
+ s << _period;
+ return s.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+time_t Duration::toTime_t () const
+{
+ return _period;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Duration::parse (const std::string& input, std::string::size_type& start)
+{
+ auto i = start;
+ Pig pig (input);
+ if (i)
+ pig.skipN (static_cast <int> (i));
+
+ if (Duration::standaloneSecondsEnabled && parse_seconds (pig))
+ {
+ // ::resolve is not needed in this case.
+ start = pig.cursor ();
+ return true;
+ }
+
+ else if (parse_designated (pig) ||
+ parse_weeks (pig) ||
+ parse_units (pig))
+ {
+ start = pig.cursor ();
+ resolve ();
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Duration::parse_seconds (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ int epoch {};
+ if (pig.getDigits (epoch) &&
+ ! unicodeLatinAlpha (pig.peek ()) &&
+ (epoch == 0 ||
+ epoch > 60))
+ {
+ _period = static_cast <time_t> (epoch);
+ return true;
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 'P' [nn 'Y'] [nn 'M'] [nn 'D'] ['T' [nn 'H'] [nn 'M'] [nn 'S']]
+bool Duration::parse_designated (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skip ('P') &&
+ ! pig.eos ())
+ {
+ int value;
+ pig.save ();
+ if (pig.getDigits (value) && pig.skip ('Y'))
+ _year = value;
+ else
+ pig.restore ();
+
+ pig.save ();
+ if (pig.getDigits (value) && pig.skip ('M'))
+ _month = value;
+ else
+ pig.restore ();
+
+ pig.save ();
+ if (pig.getDigits (value) && pig.skip ('D'))
+ _day = value;
+ else
+ pig.restore ();
+
+ if (pig.skip ('T') &&
+ ! pig.eos ())
+ {
+ pig.save ();
+ if (pig.getDigits (value) && pig.skip ('H'))
+ _hours = value;
+ else
+ pig.restore ();
+
+ pig.save ();
+ if (pig.getDigits (value) && pig.skip ('M'))
+ _minutes = value;
+ else
+ pig.restore ();
+
+ pig.save ();
+ if (pig.getDigits (value) && pig.skip ('S'))
+ _seconds = value;
+ else
+ pig.restore ();
+ }
+
+ auto following = pig.peek ();
+ if (pig.cursor () - checkpoint >= 3 &&
+ ! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ return true;
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 'P' [nn 'W']
+bool Duration::parse_weeks (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.skip ('P') &&
+ ! pig.eos ())
+ {
+ int value;
+ pig.save ();
+ if (pig.getDigits (value) && pig.skip ('W'))
+ _weeks = value;
+ else
+ pig.restore ();
+
+ auto following = pig.peek ();
+ if (pig.cursor () - checkpoint >= 3 &&
+ ! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ return true;
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Duration::parse_units (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ // Static and so preserved between calls.
+ static std::vector <std::string> units;
+ if (units.size () == 0)
+ for (unsigned int i = 0; i < NUM_DURATIONS; i++)
+ units.push_back (durations[i].unit);
+
+ double number;
+ std::string unit;
+ if (pig.getOneOf (units, unit))
+ {
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ for (unsigned int i = 0; i < NUM_DURATIONS; i++)
+ {
+ if (durations[i].unit == unit &&
+ durations[i].standalone)
+ {
+ _period = static_cast <int> (durations[i].seconds);
+ return true;
+ }
+ }
+ }
+ else
+ pig.restoreTo (checkpoint);
+ }
+
+ else if (pig.getDecimal (number))
+ {
+ pig.skipWS ();
+ if (pig.getOneOf (units, unit))
+ {
+ // The "d" unit is a special case, because it is the only one that can
+ // legitimately occur at the beginning of a UUID, and be followed by an
+ // operator:
+ //
+ // 1111111d-0000-0000-0000-000000000000
+ //
+ // Because Lexer::isDuration is higher precedence than Lexer::isUUID,
+ // the above UUID looks like:
+ //
+ // <1111111d> <-> ...
+ // duration op ...
+ //
+ // So as a special case, durations, with units of "d" are rejected if the
+ // quantity exceeds 10000.
+ //
+ if (unit == "d" && number > 10000.0)
+ {
+ pig.restoreTo (checkpoint);
+ return false;
+ }
+
+ auto following = pig.peek ();
+ if (! unicodeLatinAlpha (following) &&
+ ! unicodeLatinDigit (following))
+ {
+ // Linear lookup - should instead be logarithmic.
+ double seconds = 1;
+ for (unsigned int i = 0; i < NUM_DURATIONS; i++)
+ {
+ if (durations[i].unit == unit)
+ {
+ seconds = durations[i].seconds;
+ _period = static_cast <int> (number * static_cast <double> (seconds));
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Duration::clear ()
+{
+ _year = 0;
+ _month = 0;
+ _weeks = 0;
+ _day = 0;
+ _hours = 0;
+ _minutes = 0;
+ _seconds = 0;
+ _period = 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+const std::string Duration::format () const
+{
+ if (_period)
+ {
+ time_t t = _period;
+ int seconds = t % 60; t /= 60;
+ int minutes = t % 60; t /= 60;
+ int hours = t % 24; t /= 24;
+ int days = t;
+
+ std::stringstream s;
+ if (days)
+ s << days << "d ";
+
+ s << hours
+ << ':'
+ << std::setw (2) << std::setfill ('0') << minutes
+ << ':'
+ << std::setw (2) << std::setfill ('0') << seconds;
+
+ return s.str ();
+ }
+ else
+ {
+ return "0:00:00";
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+const std::string Duration::formatHours () const
+{
+ if (_period)
+ {
+ time_t t = _period;
+ int seconds = t % 60; t /= 60;
+ int minutes = t % 60; t /= 60;
+ int hours = t;
+
+ std::stringstream s;
+ s << hours
+ << ':'
+ << std::setw (2) << std::setfill ('0') << minutes
+ << ':'
+ << std::setw (2) << std::setfill ('0') << seconds;
+
+ return s.str ();
+ }
+ else
+ {
+ return "0:00:00";
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+const std::string Duration::formatISO () const
+{
+ if (_period)
+ {
+ time_t t = _period;
+ int seconds = t % 60; t /= 60;
+ int minutes = t % 60; t /= 60;
+ int hours = t % 24; t /= 24;
+ int days = t;
+
+ std::stringstream s;
+ s << 'P';
+ if (days) s << days << 'D';
+
+ if (hours || minutes || seconds)
+ {
+ s << 'T';
+ if (hours) s << hours << 'H';
+ if (minutes) s << minutes << 'M';
+ if (seconds) s << seconds << 'S';
+ }
+
+ return s.str ();
+ }
+ else
+ {
+ return "PT0S";
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Range Representation
+// --------- ---------------------
+// >= 365d {n.n}y
+// >= 90d {n}mo
+// >= 14d {n}w
+// >= 1d {n}d
+// >= 1h {n}h
+// >= 1min {n}min
+// {n}s
+//
+const std::string Duration::formatVague (bool padding) const
+{
+ float days = (float) _period / 86400.0;
+
+ std::stringstream formatted;
+ if (_period >= 86400 * 365) formatted << std::fixed << std::setprecision (1) << (days / 365) << (padding ? "y " : "y");
+ else if (_period >= 86400 * 90) formatted << static_cast <int> (days / 30) << (padding ? "mo " : "mo");
+ else if (_period >= 86400 * 14) formatted << static_cast <int> (days / 7) << (padding ? "w " : "w");
+ else if (_period >= 86400) formatted << static_cast <int> (days) << (padding ? "d " : "d");
+ else if (_period >= 3600) formatted << static_cast <int> (_period / 3600) << (padding ? "h " : "h");
+ else if (_period >= 60) formatted << static_cast <int> (_period / 60) << "min"; // Longest suffix - no padding
+ else if (_period >= 1) formatted << static_cast <int> (_period) << (padding ? "s " : "s");
+
+ return formatted.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Duration::days () const
+{
+ return _period / 86400;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Duration::hours () const
+{
+ return _period / 3600;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Duration::minutes () const
+{
+ return _period / 60;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Duration::seconds () const
+{
+ return _period;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Allow un-normalized values.
+void Duration::resolve ()
+{
+ if (! _period)
+ {
+ if (_weeks)
+ _period = (_weeks * 7 * 86400);
+ else
+ _period = (_year * 365 * 86400) +
+ (_month * 30 * 86400) +
+ (_day * 86400) +
+ (_hours * 3600) +
+ (_minutes * 60) +
+ _seconds;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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_DURATION
+#define INCLUDED_DURATION
+
+#include <Pig.h>
+#include <string>
+#include <time.h>
+
+class Duration
+{
+public:
+ static bool standaloneSecondsEnabled;
+
+ Duration ();
+ Duration (const std::string&);
+ Duration (time_t);
+ bool operator< (const Duration&);
+ bool operator> (const Duration&);
+ bool operator<= (const Duration&);
+ bool operator>= (const Duration&);
+ std::string toString () const;
+ time_t toTime_t () const;
+ bool parse (const std::string&, std::string::size_type&);
+ bool parse_seconds (Pig&);
+ bool parse_designated (Pig&);
+ bool parse_weeks (Pig&);
+ bool parse_units (Pig&);
+ const std::string format () const;
+ const std::string formatHours () const;
+ const std::string formatISO () const;
+ const std::string formatVague (bool padding = false) const;
+
+ int days () const;
+ int hours () const;
+ int minutes () const;
+ int seconds () const;
+
+private:
+ void clear ();
+ void resolve ();
+ std::string dump () const;
+
+public:
+ int _year {0};
+ int _month {0};
+ int _weeks {0};
+ int _day {0};
+ int _hours {0};
+ int _minutes {0};
+ int _seconds {0};
+ time_t _period {0};
+};
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <FS.h>
+#include <fstream>
+#include <glob.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include <cstdio>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include <string.h>
+#include <shared.h>
+#include <format.h>
+
+#if defined SOLARIS || defined NETBSD || defined FREEBSD || !defined(__GLIBC__)
+#include <limits.h>
+#endif
+
+#if defined __APPLE__
+#include <sys/syslimits.h>
+#endif
+
+// Fixes build with musl libc.
+#ifndef GLOB_TILDE
+#define GLOB_TILDE 0
+#endif
+
+#ifndef GLOB_BRACE
+#define GLOB_BRACE 0
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+Path::Path ()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Path::Path (const Path& other)
+{
+ if (this != &other)
+ {
+ _original = other._original;
+ _data = other._data;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Path::Path (const std::string& in)
+{
+ _original = in;
+ _data = expand (in);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Path& Path::operator= (const Path& other)
+{
+ if (this != &other)
+ {
+ this->_original = other._original;
+ this->_data = other._data;
+ }
+
+ return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Path::operator== (const Path& other)
+{
+ return _data == other._data;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Path& Path::operator+= (const std::string& dir)
+{
+ _data += '/' + dir;
+ return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Path::operator std::string () const
+{
+ return _data;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string Path::name () const
+{
+ if (_data.length ())
+ {
+ auto slash = _data.rfind ('/');
+ if (slash != std::string::npos)
+ return _data.substr (slash + 1, std::string::npos);
+ }
+
+ return _data;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string Path::parent () const
+{
+ if (_data.length ())
+ {
+ auto slash = _data.rfind ('/');
+ if (slash != std::string::npos)
+ return _data.substr (0, slash);
+ }
+
+ return "";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string Path::extension () const
+{
+ if (_data.length ())
+ {
+ auto dot = _data.rfind ('.');
+ if (dot != std::string::npos)
+ return _data.substr (dot + 1, std::string::npos);
+ }
+
+ return "";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Path::exists () const
+{
+ return access (_data.c_str (), F_OK) ? false : true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Path::is_directory () const
+{
+ if (exists ())
+ {
+ struct stat s {};
+ if (stat (_data.c_str (), &s))
+ throw format ("stat error {1}: {2}", errno, strerror (errno));
+
+ return S_ISDIR (s.st_mode);
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Path::is_absolute () const
+{
+ if (_data.length () && _data[0] == '/')
+ return true;
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Path::is_link () const
+{
+ struct stat s {};
+ if (lstat (_data.c_str (), &s))
+ throw format ("lstat error {1}: {2}", errno, strerror (errno));
+
+ return S_ISLNK (s.st_mode);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// EACCES is a permissions problem which is exactly what this method is trying
+// to determine.
+bool Path::readable () const
+{
+ auto status = access (_data.c_str (), R_OK);
+ if (status == -1 && errno != EACCES)
+ throw format ("access error {1}: {2}", errno, strerror (errno));
+
+ return status ? false : true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// EACCES is a permissions problem which is exactly what this method is trying
+// to determine.
+bool Path::writable () const
+{
+ auto status = access (_data.c_str (), W_OK);
+ if (status == -1 && errno != EACCES)
+ throw format ("access error {1}: {2}", errno, strerror (errno));
+
+ return status ? false : true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// EACCES is a permissions problem which is exactly what this method is trying
+// to determine.
+bool Path::executable () const
+{
+ auto status = access (_data.c_str (), X_OK);
+ if (status == -1 && errno != EACCES)
+ throw format ("access error {1}: {2}", errno, strerror (errno));
+
+ return status ? false : true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Path::rename (const std::string& new_name)
+{
+ auto expanded = expand (new_name);
+ if (_data != expanded)
+ {
+ if (std::rename (_data.c_str (), expanded.c_str ()) == 0)
+ {
+ _data = expanded;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ~ --> /home/user
+// ~foo/x --> /home/foo/s
+// ~/x --> /home/foo/x
+// ./x --> $PWD/x
+// x --> $PWD/x
+std::string Path::expand (const std::string& in)
+{
+ std::string copy = in;
+
+ auto tilde = copy.find ('~');
+ std::string::size_type slash;
+
+ if (tilde != std::string::npos)
+ {
+ const char *home = getenv("HOME");
+ if (home == nullptr)
+ {
+ struct passwd* pw = getpwuid (getuid ());
+ home = pw->pw_dir;
+ }
+
+ // Convert: ~ --> /home/user
+ if (copy.length () == 1)
+ copy = home;
+
+ // Convert: ~/x --> /home/user/x
+ else if (copy.length () > tilde + 1 &&
+ copy[tilde + 1] == '/')
+ {
+ copy.replace (tilde, 1, home);
+ }
+
+ // Convert: ~foo/x --> /home/foo/x
+ else if ((slash = copy.find ('/', tilde)) != std::string::npos)
+ {
+ std::string name = copy.substr (tilde + 1, slash - tilde - 1);
+ struct passwd* pw = getpwnam (name.c_str ());
+ if (pw)
+ copy.replace (tilde, slash - tilde, pw->pw_dir);
+ }
+ }
+
+ // Relative paths
+ else if (in.length () > 2 &&
+ in.substr (0, 2) == "./")
+ {
+ copy = Directory::cwd () + in.substr (1);
+ }
+ else if (in.length () > 1 &&
+ in[0] != '.' &&
+ in[0] != '/')
+ {
+ copy = Directory::cwd () + '/' + in;
+ }
+
+ return copy;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::vector <std::string> Path::glob (const std::string& pattern)
+{
+ std::vector <std::string> results;
+
+ glob_t g;
+#ifdef SOLARIS
+ if (!::glob (pattern.c_str (), GLOB_ERR, nullptr, &g))
+#else
+ if (!::glob (pattern.c_str (), GLOB_ERR | GLOB_BRACE | GLOB_TILDE, nullptr, &g))
+#endif
+ for (int i = 0; i < (int) g.gl_pathc; ++i)
+ results.push_back (g.gl_pathv[i]);
+
+ globfree (&g);
+ return results;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+File::File ()
+: Path::Path ()
+, _fh (nullptr)
+, _h (-1)
+, _locked (false)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+File::File (const Path& other)
+: Path::Path (other)
+, _fh (nullptr)
+, _h (-1)
+, _locked (false)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+File::File (const File& other)
+: Path::Path (other)
+, _fh (nullptr)
+, _h (-1)
+, _locked (false)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+File::File (const std::string& in)
+: Path::Path (in)
+, _fh (nullptr)
+, _h (-1)
+, _locked (false)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+File::~File ()
+{
+ if (_fh)
+ close ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+File& File::operator= (const File& other)
+{
+ if (this != &other)
+ Path::operator= (other);
+
+ _locked = false;
+ return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool File::create (int mode /* = 0640 */)
+{
+ if (open ())
+ {
+ fchmod (_h, mode);
+ close ();
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool File::remove () const
+{
+ return unlink (_data.c_str ()) == 0 ? true : false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string File::removeBOM (const std::string& input)
+{
+ if (input[0] && input[0] == '\xEF' &&
+ input[1] && input[1] == '\xBB' &&
+ input[2] && input[2] == '\xBF')
+ return input.substr (3);
+
+ return input;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool File::open ()
+{
+ if (_data != "")
+ {
+ if (! _fh)
+ {
+ bool already_exists = exists ();
+ if (already_exists)
+ if (!readable () || !writable ())
+ throw std::string (format ("Insufficient permissions for '{1}'.", _data));
+
+ _fh = fopen (_data.c_str (), (already_exists ? "r+" : "w+"));
+ if (_fh)
+ {
+ _h = fileno (_fh);
+ _locked = false;
+ return true;
+ }
+ else
+ throw format ("fopen error {1}: {2}", errno, strerror (errno));
+ }
+ else
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void File::close ()
+{
+ if (_fh)
+ {
+ if (_locked)
+ unlock ();
+
+ if (fclose (_fh))
+ throw format ("fclose error {1}: {2}", errno, strerror (errno));
+
+ _fh = nullptr;
+ _h = -1;
+ _locked = false;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool File::lock ()
+{
+ _locked = false;
+ if (_fh && _h != -1)
+ {
+#ifdef FREEBSD
+ // l_type l_whence l_start l_len l_pid l_sysid
+ struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0, 0 };
+#else
+ // l_type l_whence l_start l_len l_pid
+ struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0 };
+#endif
+ fl.l_pid = getpid ();
+ if (fcntl (_h, F_SETLKW, &fl) == 0)
+ _locked = true;
+ }
+
+ return _locked;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void File::unlock ()
+{
+ if (_locked)
+ {
+#ifdef FREEBSD
+ // l_type l_whence l_start l_len l_pid l_sysid
+ struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0, 0 };
+#else
+ // l_type l_whence l_start l_len l_pid
+ struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0 };
+#endif
+ fl.l_pid = getpid ();
+
+ fcntl (_h, F_SETLK, &fl);
+ _locked = false;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Opens if necessary.
+void File::read (std::string& contents)
+{
+ contents = "";
+ contents.reserve (size ());
+
+ std::ifstream in (_data.c_str ());
+ if (in.good ())
+ {
+ bool first = true;
+ std::string line;
+ line.reserve (512 * 1024);
+ while (getline (in, line))
+ {
+ // Detect forbidden BOM on first line.
+ if (first)
+ {
+ line = File::removeBOM (line);
+ first = false;
+ }
+
+ contents += line + '\n';
+ }
+
+ in.close ();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Opens if necessary.
+void File::read (std::vector <std::string>& contents)
+{
+ contents.clear ();
+
+ std::ifstream in (_data.c_str ());
+ if (in.good ())
+ {
+ bool first = true;
+ std::string line;
+ line.reserve (512 * 1024);
+ while (getline (in, line))
+ {
+ // Detect forbidden BOM on first line.
+ if (first)
+ {
+ line = File::removeBOM (line);
+ first = false;
+ }
+
+ contents.push_back (line);
+ }
+
+ in.close ();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Opens if necessary.
+void File::append (const std::string& line)
+{
+ if (!_fh)
+ open ();
+
+ if (_fh)
+ {
+ fseek (_fh, 0, SEEK_END);
+
+ if (fputs (line.c_str (), _fh) == EOF)
+ throw format ("fputs error {1}: {2}", errno, strerror (errno));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Opens if necessary.
+void File::append (const std::vector <std::string>& lines)
+{
+ if (!_fh)
+ open ();
+
+ if (_fh)
+ {
+ fseek (_fh, 0, SEEK_END);
+
+ for (auto& line : lines)
+ if (fputs (line.c_str (), _fh) == EOF)
+ throw format ("fputs error {1}: {2}", errno, strerror (errno));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void File::write_raw (const std::string& line)
+{
+ if (!_fh)
+ open ();
+
+ if (_fh)
+ if (fputs (line.c_str (), _fh) == EOF)
+ throw format ("fputs error {1}: {2}", errno, strerror (errno));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void File::truncate ()
+{
+ if (!_fh)
+ open ();
+
+ if (_fh)
+ if (ftruncate (_h, 0))
+ throw format ("ftruncate error {1}: {2}", errno, strerror (errno));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// S_IFMT 0170000 type of file
+// S_IFIFO 0010000 named pipe (fifo)
+// S_IFCHR 0020000 character special
+// S_IFDIR 0040000 directory
+// S_IFBLK 0060000 block special
+// S_IFREG 0100000 regular
+// S_IFLNK 0120000 symbolic link
+// S_IFSOCK 0140000 socket
+// S_IFWHT 0160000 whiteout
+// S_ISUID 0004000 set user id on execution
+// S_ISGID 0002000 set group id on execution
+// S_ISVTX 0001000 save swapped text even after use
+// S_IRUSR 0000400 read permission, owner
+// S_IWUSR 0000200 write permission, owner
+// S_IXUSR 0000100 execute/search permission, owner
+mode_t File::mode ()
+{
+ struct stat s;
+ if (stat (_data.c_str (), &s))
+ throw format ("stat error {1}: {2}", errno, strerror (errno));
+
+ return s.st_mode;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+size_t File::size () const
+{
+ struct stat s;
+ if (stat (_data.c_str (), &s))
+ throw format ("stat error {1}: {2}", errno, strerror (errno));
+
+ return s.st_size;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+time_t File::mtime () const
+{
+ struct stat s;
+ if (stat (_data.c_str (), &s))
+ throw format ("stat error {1}: {2}", errno, strerror (errno));
+
+ return s.st_mtime;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+time_t File::ctime () const
+{
+ struct stat s;
+ if (stat (_data.c_str (), &s))
+ throw format ("stat error {1}: {2}", errno, strerror (errno));
+
+ return s.st_ctime;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+time_t File::btime () const
+{
+ struct stat s;
+ if (stat (_data.c_str (), &s))
+ throw format ("stat error {1}: {2}", errno, strerror (errno));
+
+#ifdef HAVE_ST_BIRTHTIME
+ return s.st_birthtime;
+#else
+ return s.st_ctime;
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool File::create (const std::string& name, int mode /* = 0640 */)
+{
+ std::string full_name = expand (name);
+ std::ofstream out (full_name.c_str ());
+ if (out.good ())
+ {
+ out.close ();
+ if (chmod (full_name.c_str (), mode))
+ throw format ("chmod error {1}: {2}", errno, strerror (errno));
+
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool File::read (const std::string& name, std::string& contents)
+{
+ contents = "";
+
+ std::ifstream in (name.c_str ());
+ if (in.good ())
+ {
+ bool first = true;
+ std::string line;
+ line.reserve (1024);
+ while (getline (in, line))
+ {
+ // Detect forbidden BOM on first line.
+ if (first)
+ {
+ line = File::removeBOM (line);
+ first = false;
+ }
+
+ contents += line + '\n';
+ }
+
+ in.close ();
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool File::read (const std::string& name, std::vector <std::string>& contents)
+{
+ contents.clear ();
+
+ std::ifstream in (name.c_str ());
+ if (in.good ())
+ {
+ bool first = true;
+ std::string line;
+ line.reserve (1024);
+ while (getline (in, line))
+ {
+ // Detect forbidden BOM on first line.
+ if (first)
+ {
+ line = File::removeBOM (line);
+ first = false;
+ }
+
+ contents.push_back (line);
+ }
+
+ in.close ();
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool File::write (const std::string& name, const std::string& contents)
+{
+ std::ofstream out (expand (name).c_str (),
+ std::ios_base::out | std::ios_base::trunc);
+ if (out.good ())
+ {
+ out << contents;
+ out.close ();
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool File::write (
+ const std::string& name,
+ const std::vector <std::string>& lines,
+ bool addNewlines /* = true */)
+{
+ std::ofstream out (expand (name).c_str (),
+ std::ios_base::out | std::ios_base::trunc);
+ if (out.good ())
+ {
+ for (auto& line : lines)
+ {
+ out << line;
+
+ if (addNewlines)
+ out << '\n';
+ }
+
+ out.close ();
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool File::remove (const std::string& name)
+{
+ return unlink (expand (name).c_str ()) == 0 ? true : false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool File::copy (const std::string& from, const std::string& to)
+{
+ // 'from' must exist.
+ if (! access (from.c_str (), F_OK))
+ {
+ std::ifstream src (from, std::ios::binary);
+ std::ofstream dst (to, std::ios::binary);
+
+ dst << src.rdbuf ();
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool File::move (const std::string& from, const std::string& to)
+{
+ auto expanded = expand (to);
+ if (from != expanded)
+ if (std::rename (from.c_str (), to.c_str ()) == 0)
+ return true;
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Directory::Directory ()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Directory::Directory (const Directory& other)
+: File::File (other)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Directory::Directory (const File& other)
+: File::File (other)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Directory::Directory (const Path& other)
+: File::File (other)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Directory::Directory (const std::string& in)
+: File::File (in)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+Directory& Directory::operator= (const Directory& other)
+{
+ if (this != &other)
+ File::operator= (other);
+
+ return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Directory::create (int mode /* = 0755 */)
+{
+ // No error handling because we want failure to be silent, somewhat emulating
+ // "mkdir -p".
+ return mkdir (_data.c_str (), mode) == 0 ? true : false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Directory::remove () const
+{
+ return remove_directory (_data);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Directory::remove_directory (const std::string& dir) const
+{
+ DIR* dp = opendir (dir.c_str ());
+ if (dp != nullptr)
+ {
+ struct dirent* de;
+ while ((de = readdir (dp)) != nullptr)
+ {
+ if (! strcmp (de->d_name, ".") ||
+ ! strcmp (de->d_name, ".."))
+ continue;
+
+#if defined (SOLARIS) || defined (HAIKU)
+ struct stat s;
+ if (lstat ((dir + '/' + de->d_name).c_str (), &s))
+ throw format ("lstat error {1}: {2}", errno, strerror (errno));
+
+ if (S_ISDIR (s.st_mode))
+ remove_directory (dir + '/' + de->d_name);
+ else
+ unlink ((dir + '/' + de->d_name).c_str ());
+#else
+ if (de->d_type == DT_UNKNOWN)
+ {
+ struct stat s;
+ if (lstat ((dir + '/' + de->d_name).c_str (), &s))
+ throw format ("lstat error {1}: {2}", errno, strerror (errno));
+
+ if (S_ISDIR (s.st_mode))
+ de->d_type = DT_DIR;
+ }
+ if (de->d_type == DT_DIR)
+ remove_directory (dir + '/' + de->d_name);
+ else
+ unlink ((dir + '/' + de->d_name).c_str ());
+#endif
+ }
+
+ closedir (dp);
+ }
+
+ return rmdir (dir.c_str ()) ? false : true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::vector <std::string> Directory::list ()
+{
+ std::vector <std::string> files;
+ list (_data, files, false);
+ return files;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::vector <std::string> Directory::listRecursive ()
+{
+ std::vector <std::string> files;
+ list (_data, files, true);
+ return files;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string Directory::cwd ()
+{
+#ifdef HAVE_GET_CURRENT_DIR_NAME
+ char *buf = get_current_dir_name ();
+ if (buf == nullptr)
+ throw std::bad_alloc ();
+ std::string result (buf);
+ free (buf);
+ return result;
+#else
+ char buf[PATH_MAX];
+ getcwd (buf, PATH_MAX - 1);
+ return std::string (buf);
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Directory::up ()
+{
+ if (_data == "/")
+ return false;
+
+ auto slash = _data.rfind ('/');
+ if (slash == 0)
+ {
+ _data = "/"; // Root dir should retain the slash.
+ return true;
+ }
+ else if (slash != std::string::npos)
+ {
+ _data = _data.substr (0, slash);
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Directory::cd () const
+{
+ return chdir (_data.c_str ()) == 0 ? true : false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Directory::list (
+ const std::string& base,
+ std::vector <std::string>& results,
+ bool recursive)
+{
+ DIR* dp = opendir (base.c_str ());
+ if (dp != nullptr)
+ {
+ struct dirent* de;
+ while ((de = readdir (dp)) != nullptr)
+ {
+ if (!strcmp (de->d_name, ".") ||
+ !strcmp (de->d_name, ".."))
+ continue;
+
+#if defined (SOLARIS) || defined (HAIKU)
+ struct stat s;
+ if (stat ((base + '/' + de->d_name).c_str (), &s))
+ throw format ("stat error {1}: {2}", errno, strerror (errno));
+
+ if (recursive && S_ISDIR (s.st_mode))
+ list (base + '/' + de->d_name, results, recursive);
+ else
+ results.push_back (base + '/' + de->d_name);
+#else
+ if (recursive && de->d_type == DT_UNKNOWN)
+ {
+ struct stat s;
+ if (lstat ((base + '/' + de->d_name).c_str (), &s))
+ throw format ("lstat error {1}: {2}", errno, strerror (errno));
+
+ if (S_ISDIR (s.st_mode))
+ de->d_type = DT_DIR;
+ }
+ if (recursive && de->d_type == DT_DIR)
+ list (base + '/' + de->d_name, results, recursive);
+ else
+ results.push_back (base + '/' + de->d_name);
+#endif
+ }
+
+ closedir (dp);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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_FS
+#define INCLUDED_FS
+
+#include <stdio.h>
+#include <string>
+#include <vector>
+#include <sys/stat.h>
+
+#ifndef PATH_MAX
+#define PATH_MAX 4096
+#endif
+
+class Path
+{
+public:
+ Path ();
+ explicit Path (const Path&);
+ Path (const std::string&);
+
+ Path& operator= (const Path&);
+ bool operator== (const Path&);
+ Path& operator+= (const std::string&);
+ operator std::string () const;
+
+ std::string name () const;
+ std::string parent () const;
+ std::string extension () const;
+ bool exists () const;
+ bool is_directory () const;
+ bool is_absolute () const;
+ bool is_link () const;
+ bool readable () const;
+ bool writable () const;
+ bool executable () const;
+ bool rename (const std::string&);
+
+ // Statics
+ static std::string expand (const std::string&);
+ static std::vector<std::string> glob (const std::string&);
+
+public:
+ std::string _original;
+ std::string _data;
+};
+
+class File : public Path
+{
+public:
+ File ();
+ explicit File (const Path&);
+ explicit File (const File&);
+ File (const std::string&);
+ virtual ~File ();
+
+ File& operator= (const File&);
+
+ virtual bool create (int mode = 0640);
+ virtual bool remove () const;
+
+ bool open ();
+ void close ();
+
+ bool lock ();
+ void unlock ();
+
+ void read (std::string&);
+ void read (std::vector <std::string>&);
+
+ void append (const std::string&);
+ void append (const std::vector <std::string>&);
+ void write_raw (const std::string&);
+
+ void truncate ();
+
+ virtual mode_t mode ();
+ virtual size_t size () const;
+ virtual time_t mtime () const;
+ virtual time_t ctime () const;
+ virtual time_t btime () const;
+
+ static bool create (const std::string&, int mode = 0640);
+ static bool read (const std::string&, std::string&);
+ static bool read (const std::string&, std::vector <std::string>&);
+ static bool write (const std::string&, const std::string&);
+ static bool write (const std::string&, const std::vector <std::string>&, bool addNewlines = true);
+ static bool remove (const std::string&);
+ static bool copy (const std::string&, const std::string&);
+ static bool move (const std::string&, const std::string&);
+ static std::string removeBOM (const std::string&);
+
+private:
+ FILE* _fh;
+ int _h;
+ bool _locked;
+};
+
+class Directory : public File
+{
+public:
+ Directory ();
+ explicit Directory (const Directory&);
+ explicit Directory (const File&);
+ explicit Directory (const Path&);
+ Directory (const std::string&);
+
+ Directory& operator= (const Directory&);
+
+ virtual bool create (int mode = 0755);
+ virtual bool remove () const;
+
+ std::vector <std::string> list ();
+ std::vector <std::string> listRecursive ();
+
+ static std::string cwd ();
+ bool up ();
+ bool cd () const;
+
+private:
+ void list (const std::string&, std::vector <std::string>&, bool);
+ bool remove_directory (const std::string&) const;
+};
+
+#endif
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <JSON.h>
+#include <shared.h>
+#include <format.h>
+#include <utf8.h>
+
+const char *json_encode[] = {
+ "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07",
+ "\\b", "\\t", "\\n", "\x0b", "\\f", "\\r", "\x0e", "\x0f",
+ "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17",
+ "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f",
+ "\x20", "\x21", "\\\"", "\x23", "\x24", "\x25", "\x26", "\x27",
+ "\x28", "\x29", "\x2a", "\x2b", "\x2c", "\x2d", "\x2e", "\\/",
+ "\x30", "\x31", "\x32", "\x33", "\x34", "\x35", "\x36", "\x37",
+ "\x38", "\x39", "\x3a", "\x3b", "\x3c", "\x3d", "\x3e", "\x3f",
+ "\x40", "\x41", "\x42", "\x43", "\x44", "\x45", "\x46", "\x47",
+ "\x48", "\x49", "\x4a", "\x4b", "\x4c", "\x4d", "\x4e", "\x4f",
+ "\x50", "\x51", "\x52", "\x53", "\x54", "\x55", "\x56", "\x57",
+ "\x58", "\x59", "\x5a", "\x5b", "\\\\", "\x5d", "\x5e", "\x5f",
+ "\x60", "\x61", "\x62", "\x63", "\x64", "\x65", "\x66", "\x67",
+ "\x68", "\x69", "\x6a", "\x6b", "\x6c", "\x6d", "\x6e", "\x6f",
+ "\x70", "\x71", "\x72", "\x73", "\x74", "\x75", "\x76", "\x77",
+ "\x78", "\x79", "\x7a", "\x7b", "\x7c", "\x7d", "\x7e", "\x7f",
+ "\x80", "\x81", "\x82", "\x83", "\x84", "\x85", "\x86", "\x87",
+ "\x88", "\x89", "\x8a", "\x8b", "\x8c", "\x8d", "\x8e", "\x8f",
+ "\x90", "\x91", "\x92", "\x93", "\x94", "\x95", "\x96", "\x97",
+ "\x98", "\x99", "\x9a", "\x9b", "\x9c", "\x9d", "\x9e", "\x9f",
+ "\xa0", "\xa1", "\xa2", "\xa3", "\xa4", "\xa5", "\xa6", "\xa7",
+ "\xa8", "\xa9", "\xaa", "\xab", "\xac", "\xad", "\xae", "\xaf",
+ "\xb0", "\xb1", "\xb2", "\xb3", "\xb4", "\xb5", "\xb6", "\xb7",
+ "\xb8", "\xb9", "\xba", "\xbb", "\xbc", "\xbd", "\xbe", "\xbf",
+ "\xc0", "\xc1", "\xc2", "\xc3", "\xc4", "\xc5", "\xc6", "\xc7",
+ "\xc8", "\xc9", "\xca", "\xcb", "\xcc", "\xcd", "\xce", "\xcf",
+ "\xd0", "\xd1", "\xd2", "\xd3", "\xd4", "\xd5", "\xd6", "\xd7",
+ "\xd8", "\xd9", "\xda", "\xdb", "\xdc", "\xdd", "\xde", "\xdf",
+ "\xe0", "\xe1", "\xe2", "\xe3", "\xe4", "\xe5", "\xe6", "\xe7",
+ "\xe8", "\xe9", "\xea", "\xeb", "\xec", "\xed", "\xee", "\xef",
+ "\xf0", "\xf1", "\xf2", "\xf3", "\xf4", "\xf5", "\xf6", "\xf7",
+ "\xf8", "\xf9", "\xfa", "\xfb", "\xfc", "\xfd", "\xfe", "\xff"
+};
+
+////////////////////////////////////////////////////////////////////////////////
+json::value* json::value::parse (Pig& pig)
+{
+ json::value* v;
+ if ((v = json::object::parse (pig)) ||
+ (v = json::array::parse (pig)) ||
+ (v = json::string::parse (pig)) ||
+ (v = json::number::parse (pig)) ||
+ (v = json::literal::parse (pig)))
+ return v;
+
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+json::jtype json::value::type ()
+{
+ return json::j_value;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string json::value::dump () const
+{
+ return "<value>";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+json::string::string (const std::string& other)
+{
+ _data = other;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+json::string* json::string::parse (Pig& pig)
+{
+ std::string value;
+ if (pig.getQuoted ('"', value))
+ {
+ json::string* s = new json::string ();
+ s->_data = value;
+ return s;
+ }
+
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+json::jtype json::string::type ()
+{
+ return json::j_string;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string json::string::dump () const
+{
+ return std::string ("\"") + _data + '"';
+}
+
+////////////////////////////////////////////////////////////////////////////////
+json::number* json::number::parse (Pig& pig)
+{
+ double d;
+ if (pig.getNumber (d))
+ {
+ json::number* s = new json::number ();
+ s->_dvalue = d;
+ return s;
+ }
+
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+json::jtype json::number::type ()
+{
+ return json::j_number;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string json::number::dump () const
+{
+ return format (_dvalue);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+json::number::operator double () const
+{
+ return _dvalue;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+json::literal* json::literal::parse (Pig& pig)
+{
+ if (pig.skipLiteral ("null"))
+ {
+ json::literal* s = new json::literal ();
+ s->_lvalue = nullvalue;
+ return s;
+ }
+ else if (pig.skipLiteral ("false"))
+ {
+ json::literal* s = new json::literal ();
+ s->_lvalue = falsevalue;
+ return s;
+ }
+ else if (pig.skipLiteral ("true"))
+ {
+ json::literal* s = new json::literal ();
+ s->_lvalue = truevalue;
+ return s;
+ }
+
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+json::jtype json::literal::type ()
+{
+ return json::j_literal;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string json::literal::dump () const
+{
+ if (_lvalue == nullvalue) return "null";
+ else if (_lvalue == falsevalue) return "false";
+ else return "true";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+json::array::~array ()
+{
+ for (auto& i : _data)
+ delete i;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+json::array* json::array::parse (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ pig.skipWS ();
+ if (pig.skip ('['))
+ {
+ pig.skipWS ();
+
+ json::array* arr = new json::array ();
+
+ json::value* value;
+ if ((value = json::value::parse (pig)))
+ {
+ arr->_data.push_back (value);
+ value = NULL; // Not a leak. Looks like a leak.
+ pig.skipWS ();
+ while (pig.skip (','))
+ {
+ pig.skipWS ();
+
+ if ((value = json::value::parse (pig)))
+ {
+ arr->_data.push_back (value);
+ pig.skipWS ();
+ }
+ else
+ {
+ delete arr;
+ throw format ("Error: missing value after ',' at position {1}", (int) pig.cursor ());
+ }
+ }
+ }
+
+ if (pig.skip (']'))
+ return arr;
+ else
+ throw format ("Error: missing ']' at position {1}", (int) pig.cursor ());
+
+ delete arr;
+ }
+
+ pig.restoreTo (checkpoint);
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+json::jtype json::array::type ()
+{
+ return json::j_array;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string json::array::dump () const
+{
+ std::string output;
+ output += '[';
+
+ for (auto i = _data.begin (); i != _data.end (); ++i)
+ {
+ if (i != _data.begin ())
+ output += ',';
+
+ output += (*i)->dump ();
+ }
+
+ output += ']';
+ return output;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+json::object::~object ()
+{
+ for (auto& i : _data)
+ delete i.second;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+json::object* json::object::parse (Pig& pig)
+{
+ auto checkpoint = pig.cursor ();
+
+ pig.skipWS ();
+ if (pig.skip ('{'))
+ {
+ pig.skipWS ();
+
+ json::object* obj = new json::object ();
+
+ std::string name;
+ json::value* value;
+ if (json::object::parse_pair (pig, name, value))
+ {
+ obj->_data.insert (std::pair <std::string, json::value*> (name, value));
+ value = NULL; // Not a leak. Looks like a leak.
+
+ pig.skipWS ();
+ while (pig.skip (','))
+ {
+ pig.skipWS ();
+
+ if (json::object::parse_pair (pig, name, value))
+ {
+ obj->_data.insert (std::pair <std::string, json::value*> (name, value));
+ pig.skipWS ();
+ }
+ else
+ {
+ delete obj;
+ throw format ("Error: missing value after ',' at position {1}", (int) pig.cursor ());
+ }
+ }
+ }
+
+ if (pig.skip ('}'))
+ return obj;
+ else
+ throw format ("Error: missing '}' at position {1}", (int) pig.cursor ());
+
+ delete obj;
+ }
+
+ pig.restoreTo (checkpoint);
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool json::object::parse_pair (
+ Pig& pig,
+ std::string& name,
+ json::value*& val)
+{
+ auto checkpoint = pig.cursor ();
+
+ if (pig.getQuoted ('"', name))
+ {
+ pig.skipWS ();
+ if (pig.skip (':'))
+ {
+ pig.skipWS ();
+ if ((val = json::value::parse (pig)))
+ return true;
+ else
+ throw format ("Error: missing value at position {1}", (int) pig.cursor ());
+ }
+ else
+ throw format ("Error: missing ':' at position {1}", (int) pig.cursor ());
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+json::jtype json::object::type ()
+{
+ return json::j_object;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string json::object::dump () const
+{
+ std::string output;
+ output += '{';
+
+ for (auto i = _data.begin (); i != _data.end (); ++i)
+ {
+ if (i != _data.begin ())
+ output += ',';
+
+ output += '"' + i->first + "\":";
+ output += i->second->dump ();
+ }
+
+ output += '}';
+ return output;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+json::value* json::parse (const std::string& input)
+{
+ json::value* root = NULL;
+
+ Pig n (input);
+ n.skipWS ();
+
+ if (n.peek () == '{') root = json::object::parse (n);
+ else if (n.peek () == '[') root = json::array::parse (n);
+ else
+ throw format ("Error: expected '{' or '[' at position {1}", (int) n.cursor ());
+
+ // Check for end condition.
+ n.skipWS ();
+ if (!n.eos ())
+ {
+ delete root;
+ throw format ("Error: extra characters found at position {1}", (int) n.cursor ());
+ }
+
+ return root;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string json::encode (const std::string& input)
+{
+ std::string output;
+ output.reserve ((input.size () * 6) / 5); // 20% increase.
+
+ auto last = input.begin ();
+ for (auto i = input.begin (); i != input.end (); ++i)
+ {
+ switch (*i)
+ {
+ // Simple translations.
+ case '"':
+ case '\\':
+ case '/':
+ case '\b':
+ case '\f':
+ case '\n':
+ case '\r':
+ case '\t':
+ output.append (last, i);
+ output += json_encode[(unsigned char)(*i)];
+ last = i + 1;
+
+ // Default NOP.
+ }
+ }
+
+ output.append (last, input.end ());
+
+ return output;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string json::decode (const std::string& input)
+{
+ std::string output;
+ output.reserve (input.size ()); // Same size.
+
+ size_t pos = 0;
+
+ while (pos < input.length ())
+ {
+ if (input[pos] == '\\')
+ {
+ ++pos;
+ switch (input[pos])
+ {
+ // Simple translations.
+ case '"': output += '"'; break;
+ case '\\': output += '\\'; break;
+ case '/': output += '/'; break;
+ case 'b': output += '\b'; break;
+ case 'f': output += '\f'; break;
+ case 'n': output += '\n'; break;
+ case 'r': output += '\r'; break;
+ case 't': output += '\t'; break;
+
+ // Compose a UTF8 unicode character.
+ case 'u':
+ output += utf8_character (utf8_codepoint (input.substr (++pos)));
+ pos += 3;
+ break;
+
+ // If it is an unrecognized sequence, do nothing.
+ default:
+ output += '\\';
+ output += input[pos];
+ break;
+ }
+ ++pos;
+ }
+ else
+ {
+ size_t next_backslash = input.find ('\\', pos);
+ output.append (input, pos, next_backslash - pos);
+ pos = next_backslash;
+ }
+ }
+
+ return output;
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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_JSON
+#define INCLUDED_JSON
+
+#include <map>
+#include <vector>
+#include <string>
+#include <Pig.h>
+
+namespace json
+{
+ enum jtype
+ {
+ j_value, // 0
+ j_object, // 1
+ j_array, // 2
+ j_string, // 3
+ j_number, // 4
+ j_literal // 5
+ };
+
+ class value
+ {
+ public:
+ value () {}
+ virtual ~value () {}
+ static value* parse (Pig&);
+ virtual jtype type ();
+ virtual std::string dump () const;
+ };
+
+ class string : public value
+ {
+ public:
+ string () {}
+ string (const std::string&);
+ ~string () {}
+ static string* parse (Pig&);
+ jtype type ();
+ std::string dump () const;
+
+ public:
+ std::string _data;
+ };
+
+ class number : public value
+ {
+ public:
+ number () : _dvalue (0.0) {}
+ ~number () {}
+ static number* parse (Pig&);
+ jtype type ();
+ std::string dump () const;
+ operator double () const;
+
+ public:
+ double _dvalue;
+ };
+
+ class literal : public value
+ {
+ public:
+ literal () : _lvalue (none) {}
+ ~literal () {}
+ static literal* parse (Pig&);
+ jtype type ();
+ std::string dump () const;
+
+ public:
+ enum literal_value {none, nullvalue, falsevalue, truevalue};
+ literal_value _lvalue;
+ };
+
+ class array : public value
+ {
+ public:
+ array () {}
+ ~array ();
+ static array* parse (Pig&);
+ jtype type ();
+ std::string dump () const;
+
+ public:
+ std::vector <value*> _data;
+ };
+
+ class object : public value
+ {
+ public:
+ object () {}
+ ~object ();
+ static object* parse (Pig&);
+ static bool parse_pair (Pig&, std::string&, value*&);
+ jtype type ();
+ std::string dump () const;
+
+ public:
+ std::map <std::string, value*> _data;
+ };
+
+ // Parser entry point.
+ value* parse (const std::string&);
+
+ // Encode/decode for JSON entities.
+ std::string encode (const std::string&);
+ std::string decode (const std::string&);
+
+ class SAX
+ {
+ public:
+ class Sink
+ {
+ public:
+ virtual void eventDocStart () {}
+ virtual void eventDocEnd () {}
+ virtual void eventObjectStart () {}
+ virtual void eventObjectEnd (int) {}
+ virtual void eventArrayStart () {}
+ virtual void eventArrayEnd (int) {}
+ virtual void eventName (const std::string&) {}
+ virtual void eventValueNull () {}
+ virtual void eventValueBool (bool) {}
+ virtual void eventValueInt (int64_t) {}
+ virtual void eventValueUint (uint64_t) {}
+ virtual void eventValueDouble (double) {}
+ virtual void eventValueString (const std::string&) {}
+ };
+
+ bool parse (const std::string&, json::SAX::Sink&);
+
+ private:
+ void ignoreWhitespace (const std::string&, std::string::size_type&);
+ bool isObject (const std::string&, std::string::size_type&, SAX::Sink&);
+ bool isArray (const std::string&, std::string::size_type&, SAX::Sink&);
+ bool isPair (const std::string&, std::string::size_type&, SAX::Sink&);
+ bool isValue (const std::string&, std::string::size_type&, SAX::Sink&);
+ bool isKey (const std::string&, std::string::size_type&, SAX::Sink&);
+ bool isString (const std::string&, std::string::size_type&, SAX::Sink&);
+ bool isStringValue (const std::string&, std::string::size_type&, std::string&);
+ bool isNumber (const std::string&, std::string::size_type&, SAX::Sink&);
+ bool isInt (const std::string&, std::string::size_type&, std::string&);
+ bool isFrac (const std::string&, std::string::size_type&, std::string&);
+ bool isDigits (const std::string&, std::string::size_type&);
+ bool isDecDigit (int);
+ bool isHexDigit (int);
+ int hexToInt (int);
+ int hexToInt (int, int, int, int);
+ bool isExp (const std::string&, std::string::size_type&, std::string&);
+ bool isE (const std::string&, std::string::size_type&);
+ bool isBool (const std::string&, std::string::size_type&, SAX::Sink&);
+ bool isNull (const std::string&, std::string::size_type&, SAX::Sink&);
+ bool isLiteral (const std::string&, char, std::string::size_type&);
+ void error (const std::string&, std::string::size_type);
+ };
+}
+
+#endif
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// Copyright 2013 - 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 <cmake.h>
+#include <Lexer.h>
+#include <Datetime.h>
+#include <Duration.h>
+#include <algorithm>
+#include <tuple>
+#include <ctype.h>
+#include <unicode.h>
+#include <utf8.h>
+
+static const std::string uuid_pattern = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
+static const unsigned int uuid_min_length = 8;
+
+std::string Lexer::dateFormat = "";
+
+////////////////////////////////////////////////////////////////////////////////
+Lexer::Lexer (const std::string& text)
+: _text (text)
+, _eos (text.size ())
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// When a Lexer object is constructed with a string, this method walks through
+// the stream of low-level tokens.
+bool Lexer::token (std::string& token, Lexer::Type& type)
+{
+ // Eat white space.
+ while (unicodeWhitespace (_text[_cursor]))
+ utf8_next_char (_text, _cursor);
+
+ // Terminate at EOS.
+ if (isEOS ())
+ return false;
+
+ if (isString (token, type, "'\"") ||
+ isUUID (token, type, true) ||
+ isDate (token, type) ||
+ isDuration (token, type) ||
+ isURL (token, type) ||
+ isHexNumber (token, type) ||
+ isNumber (token, type) ||
+ isPath (token, type) ||
+ isPattern (token, type) ||
+ isOperator (token, type) ||
+ isWord (token, type))
+ return true;
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::vector <std::tuple <std::string, Lexer::Type>> Lexer::tokenize (const std::string& input)
+{
+ std::vector <std::tuple <std::string, Lexer::Type>> tokens;
+
+ std::string token;
+ Lexer::Type type;
+ Lexer lexer (input);
+ while (lexer.token (token, type))
+ tokens.push_back (std::make_tuple (token, type));
+
+ return tokens;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// No L10N - these are for internal purposes.
+const std::string Lexer::typeName (const Lexer::Type& type)
+{
+ switch (type)
+ {
+ case Lexer::Type::uuid: return "uuid";
+ case Lexer::Type::number: return "number";
+ case Lexer::Type::hex: return "hex";
+ case Lexer::Type::string: return "string";
+ case Lexer::Type::url: return "url";
+ case Lexer::Type::path: return "path";
+ case Lexer::Type::pattern: return "pattern";
+ case Lexer::Type::op: return "op";
+ case Lexer::Type::word: return "word";
+ case Lexer::Type::date: return "date";
+ case Lexer::Type::duration: return "duration";
+ }
+
+ return "unknown";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Lexer::Type::number
+// \d+
+// [ . \d+ ]
+// [ e|E [ +|- ] \d+ [ . \d+ ] ]
+// not followed by non-operator.
+bool Lexer::isNumber (std::string& token, Lexer::Type& type)
+{
+ std::size_t marker = _cursor;
+
+ if (unicodeLatinDigit (_text[marker]))
+ {
+ ++marker;
+ while (unicodeLatinDigit (_text[marker]))
+ utf8_next_char (_text, marker);
+
+ if (_text[marker] == '.')
+ {
+ ++marker;
+ if (unicodeLatinDigit (_text[marker]))
+ {
+ ++marker;
+ while (unicodeLatinDigit (_text[marker]))
+ utf8_next_char (_text, marker);
+ }
+ }
+
+ if (_text[marker] == 'e' ||
+ _text[marker] == 'E')
+ {
+ ++marker;
+
+ if (_text[marker] == '+' ||
+ _text[marker] == '-')
+ ++marker;
+
+ if (unicodeLatinDigit (_text[marker]))
+ {
+ ++marker;
+ while (unicodeLatinDigit (_text[marker]))
+ utf8_next_char (_text, marker);
+
+ if (_text[marker] == '.')
+ {
+ ++marker;
+ if (unicodeLatinDigit (_text[marker]))
+ {
+ ++marker;
+ while (unicodeLatinDigit (_text[marker]))
+ utf8_next_char (_text, marker);
+ }
+ }
+ }
+ }
+
+ // Lookahread: !<unicodeWhitespace> | !<isSingleCharOperator>
+ // If there is an immediately consecutive character, that is not an operator, fail.
+ if (_eos > marker &&
+ ! unicodeWhitespace (_text[marker]) &&
+ ! isSingleCharOperator (_text[marker]))
+ return false;
+
+ token = _text.substr (_cursor, marker - _cursor);
+ type = Lexer::Type::number;
+ _cursor = marker;
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Lexer::Type::number
+// \d+
+bool Lexer::isInteger (std::string& token, Lexer::Type& type)
+{
+ std::size_t marker = _cursor;
+
+ if (unicodeLatinDigit (_text[marker]))
+ {
+ ++marker;
+ while (unicodeLatinDigit (_text[marker]))
+ utf8_next_char (_text, marker);
+
+ token = _text.substr (_cursor, marker - _cursor);
+ type = Lexer::Type::number;
+ _cursor = marker;
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Lexer::isSingleCharOperator (int c)
+{
+ return c == '+' || // Addition
+ c == '-' || // Subtraction or unary minus = ambiguous
+ c == '*' || // Multiplication
+ c == '/' || // Diviѕion
+ c == '(' || // Precedence open parenthesis
+ c == ')' || // Precedence close parenthesis
+ c == '<' || // Less than
+ c == '>' || // Greater than
+ c == '^' || // Exponent
+ c == '!' || // Unary not
+ c == '%' || // Modulus
+ c == '=' || // Partial match
+ c == '~'; // Pattern match
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Lexer::isDoubleCharOperator (int c0, int c1, int c2)
+{
+ return (c0 == '=' && c1 == '=') ||
+ (c0 == '!' && c1 == '=') ||
+ (c0 == '<' && c1 == '=') ||
+ (c0 == '>' && c1 == '=') ||
+ (c0 == 'o' && c1 == 'r' && isBoundary (c1, c2)) ||
+ (c0 == '|' && c1 == '|') ||
+ (c0 == '&' && c1 == '&') ||
+ (c0 == '!' && c1 == '~');
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Lexer::isTripleCharOperator (int c0, int c1, int c2, int c3)
+{
+ return (c0 == 'a' && c1 == 'n' && c2 == 'd' && isBoundary (c2, c3)) ||
+ (c0 == 'x' && c1 == 'o' && c2 == 'r' && isBoundary (c2, c3)) ||
+ (c0 == '!' && c1 == '=' && c2 == '=');
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Lexer::isBoundary (int left, int right)
+{
+ // EOS
+ if (right == '\0') return true;
+
+ // XOR
+ if (unicodeLatinAlpha (left) != unicodeLatinAlpha (right)) return true;
+ if (unicodeLatinDigit (left) != unicodeLatinDigit (right)) return true;
+ if (unicodeWhitespace (left) != unicodeWhitespace (right)) return true;
+
+ // OR
+ if (isPunctuation (left) || isPunctuation (right)) return true;
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Lexer::isHardBoundary (int left, int right)
+{
+ // EOS
+ if (right == '\0')
+ return true;
+
+ // FILTER operators that don't need to be surrounded by whitespace.
+ if (left == '(' ||
+ left == ')' ||
+ right == '(' ||
+ right == ')')
+ return true;
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Lexer::isPunctuation (int c)
+{
+ return isprint (c) &&
+ c != ' ' &&
+ c != '@' &&
+ c != '#' &&
+ c != '$' &&
+ c != '_' &&
+ ! unicodeLatinDigit (c) &&
+ ! unicodeLatinAlpha (c);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Assumes that quotes is a string containing a non-trivial set of quote
+// characters.
+std::string Lexer::dequote (const std::string& input, const std::string& quotes)
+{
+ if (input.length () > 1)
+ {
+ int quote = input[0];
+ if (quotes.find (quote) != std::string::npos)
+ {
+ size_t len = input.length ();
+ if (quote == input[len - 1])
+ return input.substr (1, len - 2);
+ }
+ }
+
+ return input;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Detects characters in an input string that indicate quotes were required, or
+// escapes, to get them past the shell.
+bool Lexer::wasQuoted (const std::string& input)
+{
+ if (input.find_first_of (" \t()<>&~") != std::string::npos)
+ return true;
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Lexer::isEOS () const
+{
+ return _cursor >= _eos;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Converts '0' -> 0
+// '9' -> 9
+// 'a'/'A' -> 10
+// 'f'/'F' -> 15
+int Lexer::hexToInt (int c)
+{
+ if (c >= '0' && c <= '9') return (c - '0');
+ else if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
+ else return (c - 'A' + 10);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Lexer::hexToInt (int c0, int c1)
+{
+ return (hexToInt (c0) << 4) + hexToInt (c1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Lexer::hexToInt (int c0, int c1, int c2, int c3)
+{
+ return (hexToInt (c0) << 12) +
+ (hexToInt (c1) << 8) +
+ (hexToInt (c2) << 4) +
+ hexToInt (c3);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string Lexer::trimLeft (const std::string& in, const std::string& t /*= " "*/)
+{
+ std::string::size_type ws = in.find_first_not_of (t);
+ if (ws > 0)
+ {
+ std::string out {in};
+ return out.erase (0, ws);
+ }
+
+ return in;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string Lexer::trimRight (const std::string& in, const std::string& t /*= " "*/)
+{
+ std::string out {in};
+ return out.erase (in.find_last_not_of (t) + 1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string Lexer::trim (const std::string& in, const std::string& t /*= " "*/)
+{
+ return trimLeft (trimRight (in, t), t);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Lexer::Type::string
+// '|"
+// [ U+XXXX | \uXXXX | \" | \' | \\ | \/ | \b | \f | \n | \r | \t | . ]
+// '|"
+bool Lexer::isString (std::string& token, Lexer::Type& type, const std::string& quotes)
+{
+ if (_enableString)
+ {
+ std::size_t marker = _cursor;
+ if (readWord (_text, quotes, marker, token))
+ {
+ type = Lexer::Type::string;
+ _cursor = marker;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Lexer::Type::date
+// <Datetime> (followed by eos, WS, operator)
+bool Lexer::isDate (std::string& token, Lexer::Type& type)
+{
+ if (_enableDate)
+ {
+ // Try an ISO date parse.
+ std::size_t i = _cursor;
+ Datetime d;
+ if (d.parse (_text, i, Lexer::dateFormat) &&
+ (i >= _eos ||
+ unicodeWhitespace (_text[i]) ||
+ isSingleCharOperator (_text[i])))
+ {
+ type = Lexer::Type::date;
+ token = _text.substr (_cursor, i - _cursor);
+ _cursor = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Lexer::Type::duration
+// <Duration> (followed by eos, WS, operator)
+bool Lexer::isDuration (std::string& token, Lexer::Type& type)
+{
+ if (_enableDuration)
+ {
+ std::size_t marker = _cursor;
+
+ std::string extractedToken;
+ Lexer::Type extractedType;
+ if (isOperator(extractedToken, extractedType))
+ {
+ _cursor = marker;
+ return false;
+ }
+
+ marker = _cursor;
+ Duration dur;
+ if (dur.parse (_text, marker) &&
+ (marker >= _eos ||
+ unicodeWhitespace (_text[marker]) ||
+ isSingleCharOperator (_text[marker])))
+ {
+ type = Lexer::Type::duration;
+ token = _text.substr (_cursor, marker - _cursor);
+ _cursor = marker;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Lexer::Type::uuid
+// XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+// XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX
+// XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX
+// XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX
+// ...
+// XXXXXXXX-XX
+// XXXXXXXX-X
+// XXXXXXXX-
+// XXXXXXXX
+// Followed only by EOS, whitespace, or single character operator.
+bool Lexer::isUUID (std::string& token, Lexer::Type& type, bool endBoundary)
+{
+ if (_enableUUID)
+ {
+ std::size_t marker = _cursor;
+
+ // Greedy.
+ std::size_t i = 0;
+ for (; i < 36 && marker + i < _eos; i++)
+ {
+ if (uuid_pattern[i] == 'x')
+ {
+ if (! unicodeHexDigit (_text[marker + i]))
+ break;
+ }
+ else if (uuid_pattern[i] != _text[marker + i])
+ break;
+ }
+
+ if (i >= uuid_min_length &&
+ (! endBoundary ||
+ ! _text[marker + i] ||
+ unicodeWhitespace (_text[marker + i]) ||
+ isSingleCharOperator (_text[marker + i])))
+ {
+ token = _text.substr (_cursor, i);
+ type = Lexer::Type::uuid;
+ _cursor += i;
+ return true;
+ }
+
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Lexer::Type::hex
+// 0xX+
+bool Lexer::isHexNumber (std::string& token, Lexer::Type& type)
+{
+ if (_enableHexNumber)
+ {
+ std::size_t marker = _cursor;
+
+ if (_eos - marker >= 3 &&
+ _text[marker + 0] == '0' &&
+ _text[marker + 1] == 'x')
+ {
+ marker += 2;
+
+ while (unicodeHexDigit (_text[marker]))
+ ++marker;
+
+ if (marker - _cursor > 2)
+ {
+ token = _text.substr (_cursor, marker - _cursor);
+ type = Lexer::Type::hex;
+ _cursor = marker;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Lexer::Type::word
+// [^\s]+
+bool Lexer::isWord (std::string& token, Lexer::Type& type)
+{
+ if (_enableWord)
+ {
+ std::size_t marker = _cursor;
+
+ while (_text[marker] &&
+ ! unicodeWhitespace (_text[marker]) &&
+ (! _enableOperator || ! isSingleCharOperator (_text[marker])))
+ utf8_next_char (_text, marker);
+
+ if (marker > _cursor)
+ {
+ token = _text.substr (_cursor, marker - _cursor);
+ type = Lexer::Type::word;
+ _cursor = marker;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Lexer::Type::url
+// http [s] :// ...
+bool Lexer::isURL (std::string& token, Lexer::Type& type)
+{
+ if (_enableURL)
+ {
+ std::size_t marker = _cursor;
+
+ if (_eos - _cursor > 9 && // length 'https://*'
+ (_text[marker + 0] == 'h' || _text[marker + 0] == 'H') &&
+ (_text[marker + 1] == 't' || _text[marker + 1] == 'T') &&
+ (_text[marker + 2] == 't' || _text[marker + 2] == 'T') &&
+ (_text[marker + 3] == 'p' || _text[marker + 3] == 'P'))
+ {
+ marker += 4;
+ if (_text[marker + 0] == 's' || _text[marker + 0] == 'S')
+ ++marker;
+
+ if (_text[marker + 0] == ':' &&
+ _text[marker + 1] == '/' &&
+ _text[marker + 2] == '/')
+ {
+ marker += 3;
+
+ while (marker < _eos &&
+ ! unicodeWhitespace (_text[marker]))
+ utf8_next_char (_text, marker);
+
+ token = _text.substr (_cursor, marker - _cursor);
+ type = Lexer::Type::url;
+ _cursor = marker;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Lexer::Type::path
+// ( / <non-slash, non-whitespace> )+
+bool Lexer::isPath (std::string& token, Lexer::Type& type)
+{
+ if (_enablePath)
+ {
+ std::size_t marker = _cursor;
+ int slashCount = 0;
+
+ while (1)
+ {
+ if (_text[marker] == '/')
+ {
+ ++marker;
+ ++slashCount;
+ }
+ else
+ break;
+
+ if (_text[marker] &&
+ ! unicodeWhitespace (_text[marker]) &&
+ _text[marker] != '/')
+ {
+ utf8_next_char (_text, marker);
+ while (_text[marker] &&
+ ! unicodeWhitespace (_text[marker]) &&
+ _text[marker] != '/')
+ utf8_next_char (_text, marker);
+ }
+ else
+ break;
+ }
+
+ if (marker > _cursor &&
+ slashCount > 3)
+ {
+ type = Lexer::Type::path;
+ token = _text.substr (_cursor, marker - _cursor);
+ _cursor = marker;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Lexer::Type::pattern
+// / <unquoted-string> / <EOS> | <unicodeWhitespace>
+bool Lexer::isPattern (std::string& token, Lexer::Type& type)
+{
+ if (_enablePattern)
+ {
+ std::size_t marker = _cursor;
+
+ std::string word;
+ if (readWord (_text, "/", _cursor, word) &&
+ (isEOS () ||
+ unicodeWhitespace (_text[_cursor])))
+ {
+ token = _text.substr (marker, _cursor - marker);
+ type = Lexer::Type::pattern;
+ return true;
+ }
+
+ _cursor = marker;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Lexer::Type::op
+// _hastag_ | _notag | _neg_ | _pos_ |
+// <isTripleCharOperator> |
+// <isDoubleCharOperator> |
+// <isSingleCharOperator> |
+bool Lexer::isOperator (std::string& token, Lexer::Type& type)
+{
+ if (_enableOperator)
+ {
+ std::size_t marker = _cursor;
+
+ if (_eos - marker >= 8 && _text.substr (marker, 8) == "_hastag_")
+ {
+ marker += 8;
+ type = Lexer::Type::op;
+ token = _text.substr (_cursor, marker - _cursor);
+ _cursor = marker;
+ return true;
+ }
+
+ else if (_eos - marker >= 7 && _text.substr (marker, 7) == "_notag_")
+ {
+ marker += 7;
+ type = Lexer::Type::op;
+ token = _text.substr (_cursor, marker - _cursor);
+ _cursor = marker;
+ return true;
+ }
+
+ else if (_eos - marker >= 5 && _text.substr (marker, 5) == "_neg_")
+ {
+ marker += 5;
+ type = Lexer::Type::op;
+ token = _text.substr (_cursor, marker - _cursor);
+ _cursor = marker;
+ return true;
+ }
+
+ else if (_eos - marker >= 5 && _text.substr (marker, 5) == "_pos_")
+ {
+ marker += 5;
+ type = Lexer::Type::op;
+ token = _text.substr (_cursor, marker - _cursor);
+ _cursor = marker;
+ return true;
+ }
+
+ else if (_eos - marker >= 3 &&
+ isTripleCharOperator (_text[marker], _text[marker + 1], _text[marker + 2], _text[marker + 3]))
+ {
+ marker += 3;
+ type = Lexer::Type::op;
+ token = _text.substr (_cursor, marker - _cursor);
+ _cursor = marker;
+ return true;
+ }
+
+ else if (_eos - marker >= 2 &&
+ isDoubleCharOperator (_text[marker], _text[marker + 1], _text[marker + 2]))
+ {
+ marker += 2;
+ type = Lexer::Type::op;
+ token = _text.substr (_cursor, marker - _cursor);
+ _cursor = marker;
+ return true;
+ }
+
+ else if (isSingleCharOperator (_text[marker]))
+ {
+ token = _text[marker];
+ type = Lexer::Type::op;
+ _cursor = ++marker;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Static
+std::string Lexer::typeToString (Lexer::Type type)
+{
+ if (type == Lexer::Type::string) return std::string ("\033[38;5;7m\033[48;5;3m") + "string" + "\033[0m";
+ else if (type == Lexer::Type::uuid) return std::string ("\033[38;5;7m\033[48;5;10m") + "uuid" + "\033[0m";
+ else if (type == Lexer::Type::hex) return std::string ("\033[38;5;7m\033[48;5;14m") + "hex" + "\033[0m";
+ else if (type == Lexer::Type::number) return std::string ("\033[38;5;7m\033[48;5;6m") + "number" + "\033[0m";
+ else if (type == Lexer::Type::url) return std::string ("\033[38;5;7m\033[48;5;4m") + "url" + "\033[0m";
+ else if (type == Lexer::Type::path) return std::string ("\033[37;102m") + "path" + "\033[0m";
+ else if (type == Lexer::Type::pattern) return std::string ("\033[37;42m") + "pattern" + "\033[0m";
+ else if (type == Lexer::Type::op) return std::string ("\033[38;5;7m\033[48;5;203m") + "op" + "\033[0m";
+ else if (type == Lexer::Type::word) return std::string ("\033[38;5;15m\033[48;5;236m") + "word" + "\033[0m";
+ else if (type == Lexer::Type::date) return std::string ("\033[38;5;15m\033[48;5;34m") + "date" + "\033[0m";
+ else if (type == Lexer::Type::duration) return std::string ("\033[38;5;15m\033[48;5;34m") + "duration" + "\033[0m";
+ else return std::string ("\033[37;41m") + "unknown" + "\033[0m";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Full implementation of a quoted word. Includes:
+// '\''
+// '"'
+// "'"
+// "\""
+// 'one two'
+// Result includes the quotes.
+bool Lexer::readWord (
+ const std::string& text,
+ const std::string& quotes,
+ std::string::size_type& cursor,
+ std::string& word)
+{
+ if (quotes.find (text[cursor]) == std::string::npos)
+ return false;
+
+ std::string::size_type eos = text.length ();
+ int quote = text[cursor++];
+ word = quote;
+
+ int c;
+ while ((c = text[cursor]))
+ {
+ // Quoted word ends on a quote.
+ if (quote && quote == c)
+ {
+ word += utf8_character (utf8_next_char (text, cursor));
+ break;
+ }
+
+ // Unicode U+XXXX or \uXXXX codepoint.
+ else if (eos - cursor >= 6 &&
+ ((text[cursor + 0] == 'U' && text[cursor + 1] == '+') ||
+ (text[cursor + 0] == '\\' && text[cursor + 1] == 'u')) &&
+ unicodeHexDigit (text[cursor + 2]) &&
+ unicodeHexDigit (text[cursor + 3]) &&
+ unicodeHexDigit (text[cursor + 4]) &&
+ unicodeHexDigit (text[cursor + 5]))
+ {
+ word += utf8_character (
+ hexToInt (
+ text[cursor + 2],
+ text[cursor + 3],
+ text[cursor + 4],
+ text[cursor + 5]));
+ cursor += 6;
+ }
+
+ // An escaped thing.
+ else if (c == '\\')
+ {
+ c = text[++cursor];
+
+ switch (c)
+ {
+ case '"': word += (char) 0x22; ++cursor; break;
+ case '\'': word += (char) 0x27; ++cursor; break;
+ case '\\': word += (char) 0x5C; ++cursor; break;
+ case 'b': word += (char) 0x08; ++cursor; break;
+ case 'f': word += (char) 0x0C; ++cursor; break;
+ case 'n': word += (char) 0x0A; ++cursor; break;
+ case 'r': word += (char) 0x0D; ++cursor; break;
+ case 't': word += (char) 0x09; ++cursor; break;
+ case 'v': word += (char) 0x0B; ++cursor; break;
+
+ // This pass-through default case means that anything can be escaped
+ // harmlessly. In particular 'quote' is included, if it not one of the
+ // above characters.
+ default: word += (char) c; ++cursor; break;
+ }
+ }
+
+ // Ordinary character.
+ else
+ word += utf8_character (utf8_next_char (text, cursor));
+ }
+
+ // Verify termination.
+ return word[0] == quote &&
+ word[word.length () - 1] == quote &&
+ word.length () >= 2;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Full implementation of an unquoted word. Includes:
+// one\ two
+// abcU+0020def
+// abc\u0020def
+// a\tb
+//
+// Ends at:
+// Lexer::isEOS
+// unicodeWhitespace
+// Lexer::isHardBoundary
+bool Lexer::readWord (
+ const std::string& text,
+ std::string::size_type& cursor,
+ std::string& word)
+{
+ std::string::size_type eos = text.length ();
+
+ word = "";
+ int c;
+ int prev = 0;
+ while ((c = text[cursor])) // Handles EOS.
+ {
+ // Unquoted word ends on white space.
+ if (unicodeWhitespace (c))
+ break;
+
+ // Parentheses mostly.
+ if (prev && Lexer::isHardBoundary (prev, c))
+ break;
+
+ // Unicode U+XXXX or \uXXXX codepoint.
+ else if (eos - cursor >= 6 &&
+ ((text[cursor + 0] == 'U' && text[cursor + 1] == '+') ||
+ (text[cursor + 0] == '\\' && text[cursor + 1] == 'u')) &&
+ unicodeHexDigit (text[cursor + 2]) &&
+ unicodeHexDigit (text[cursor + 3]) &&
+ unicodeHexDigit (text[cursor + 4]) &&
+ unicodeHexDigit (text[cursor + 5]))
+ {
+ word += utf8_character (
+ hexToInt (
+ text[cursor + 2],
+ text[cursor + 3],
+ text[cursor + 4],
+ text[cursor + 5]));
+ cursor += 6;
+ }
+
+ // An escaped thing.
+ else if (c == '\\')
+ {
+ c = text[++cursor];
+
+ switch (c)
+ {
+ case '"': word += (char) 0x22; ++cursor; break;
+ case '\'': word += (char) 0x27; ++cursor; break;
+ case '\\': word += (char) 0x5C; ++cursor; break;
+ case 'b': word += (char) 0x08; ++cursor; break;
+ case 'f': word += (char) 0x0C; ++cursor; break;
+ case 'n': word += (char) 0x0A; ++cursor; break;
+ case 'r': word += (char) 0x0D; ++cursor; break;
+ case 't': word += (char) 0x09; ++cursor; break;
+ case 'v': word += (char) 0x0B; ++cursor; break;
+
+ // This pass-through default case means that anything can be escaped
+ // harmlessly. In particular 'quote' is included, if it not one of the
+ // above characters.
+ default: word += (char) c; ++cursor; break;
+ }
+ }
+
+ // Ordinary character.
+ else
+ word += utf8_character (utf8_next_char (text, cursor));
+
+ prev = c;
+ }
+
+ return word.length () > 0 ? true : false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// Copyright 2013 - 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_LEXER
+#define INCLUDED_LEXER
+
+#include <string>
+#include <map>
+#include <vector>
+#include <tuple>
+#include <cstddef>
+
+class Lexer
+{
+public:
+ // These are overridable.
+ static std::string dateFormat;
+
+ enum class Type { uuid, number, hex,
+ string,
+ url,
+ path,
+ pattern,
+ op,
+ word,
+ date, duration };
+
+ explicit Lexer (const std::string&);
+ bool token (std::string&, Lexer::Type&);
+ static std::string typeToString (Lexer::Type);
+
+ // Static helpers.
+ static std::vector <std::tuple <std::string, Lexer::Type>> tokenize (const std::string&);
+ static const std::string typeName (const Lexer::Type&);
+ static bool isSingleCharOperator (int);
+ static bool isDoubleCharOperator (int, int, int);
+ static bool isTripleCharOperator (int, int, int, int);
+ static bool isBoundary (int, int);
+ static bool isHardBoundary (int, int);
+ static bool isPunctuation (int);
+ static bool wasQuoted (const std::string&);
+ static bool readWord (const std::string&, const std::string&, std::string::size_type&, std::string&);
+ static bool readWord (const std::string&, std::string::size_type&, std::string&);
+ static int hexToInt (int);
+ static int hexToInt (int, int);
+ static int hexToInt (int, int, int, int);
+ static std::string trimLeft (const std::string& in, const std::string& t = " ");
+ static std::string trimRight (const std::string& in, const std::string& t = " ");
+ static std::string trim (const std::string& in, const std::string& t = " ");
+ static std::string dequote (const std::string&, const std::string& quotes = "'\"");
+
+ // Stream Classifiers.
+ bool isEOS () const;
+ bool isString (std::string&, Lexer::Type&, const std::string&);
+ bool isDate (std::string&, Lexer::Type&);
+ bool isDuration (std::string&, Lexer::Type&);
+ bool isUUID (std::string&, Lexer::Type&, bool);
+ bool isNumber (std::string&, Lexer::Type&);
+ bool isInteger (std::string&, Lexer::Type&);
+ bool isHexNumber (std::string&, Lexer::Type&);
+ bool isURL (std::string&, Lexer::Type&);
+ bool isPath (std::string&, Lexer::Type&);
+ bool isPattern (std::string&, Lexer::Type&);
+ bool isOperator (std::string&, Lexer::Type&);
+ bool isWord (std::string&, Lexer::Type&);
+
+ // Disabling features.
+ void noString () { _enableString = false; }
+ void noDate () { _enableDate = false; }
+ void noDuration () { _enableDuration = false; }
+ void noUUID () { _enableUUID = false; }
+ void noHexNumber () { _enableHexNumber = false; }
+ void noWord () { _enableWord = false; }
+ void noURL () { _enableURL = false; }
+ void noPath () { _enablePath = false; }
+ void noPattern () { _enablePattern = false; }
+ void noOperator () { _enableOperator = false; }
+
+private:
+ std::string _text {};
+ std::size_t _cursor {0};
+ std::size_t _eos {0};
+
+ bool _enableString {true};
+ bool _enableDate {true};
+ bool _enableDuration {true};
+ bool _enableUUID {true};
+ bool _enableHexNumber {true};
+ bool _enableWord {true};
+ bool _enableURL {true};
+ bool _enablePath {true};
+ bool _enablePattern {true};
+ bool _enableOperator {true};
+};
+
+#endif
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <Log.h>
+#include <shared.h>
+#include <format.h>
+
+////////////////////////////////////////////////////////////////////////////////
+std::string Log::file () const
+{
+ return _name;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Log::file (const std::string& name)
+{
+ _name = name;
+ _file = File (_name);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Log::ignore (const std::string& category)
+{
+ _ignore.insert (category);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Log::write (const std::string& line)
+{
+ write ("info", line);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Log::write (const std::string& category, const std::string& line)
+{
+ // Determine if this category is in the ignore list.
+ if (_ignore.find (category) != _ignore.end ())
+ return;
+
+ if (line != "")
+ {
+ // If line contains newlines, split it into separate lines and log each one.
+ if (line.find ('\n') != std::string::npos)
+ {
+ for (const auto& single : split (line, '\n'))
+ write (category, single);
+
+ return;
+ }
+
+ // Single line.
+ if (line == _prior)
+ {
+ ++_repetition;
+ }
+ else
+ {
+ _prior = line;
+
+ // Catch up by writing out the backlog.
+ if (_name != "" && _backlog.size ())
+ {
+ for (const auto& line : _backlog)
+ _file.append (line);
+
+ _backlog.clear ();
+ }
+
+ if (_repetition)
+ {
+ std::string message = Datetime ().toISO ()
+ + ' ' + PACKAGE_VERSION
+ + ' ' + category
+ + ' ' + format ("(Repeated {1} times)", _repetition)
+ + '\n';
+
+ if (_name != "")
+ _file.append (message);
+ else
+ _backlog.push_back (message);
+
+ _repetition = 0;
+ }
+
+ std::string message = Datetime ().toISO ()
+ + ' ' + PACKAGE_VERSION
+ + ' ' + category
+ + ' ' + line
+ + '\n';
+
+ if (_name != "")
+ _file.append (message);
+ else
+ _backlog.push_back (message);
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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_LOG
+#define INCLUDED_LOG
+
+#include <FS.h>
+#include <Datetime.h>
+#include <vector>
+#include <set>
+#include <string>
+#include <stdio.h>
+
+class Log
+{
+public:
+ std::string file () const;
+ void file (const std::string&);
+ void ignore (const std::string&);
+ void write (const std::string&);
+ void write (const std::string&, const std::string&);
+
+private:
+ std::string _name {};
+ std::vector <std::string> _backlog {};
+ File _file {};
+ std::string _prior {"none"};
+ int _repetition {0};
+ std::set <std::string> _ignore {};
+};
+
+#endif
+
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <Msg.h>
+#include <shared.h>
+#include <format.h>
+
+////////////////////////////////////////////////////////////////////////////////
+void Msg::set (const std::string& name, int value)
+{
+ _header[name] = format (value);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Msg::set (const std::string& name, const std::string& value)
+{
+ _header[name] = value;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string Msg::get (const std::string& name) const
+{
+ auto i = _header.find (name);
+ if (i != _header.end ())
+ return i->second;
+
+ return "";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Msg::setPayload (const std::string& payload)
+{
+ _payload = payload;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string Msg::getPayload () const
+{
+ return _payload;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::vector <std::string> Msg::all () const
+{
+ std::vector <std::string> names;
+ for (auto& i : _header)
+ names.push_back (i.first);
+
+ return names;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string Msg::serialize () const
+{
+ std::string output;
+ for (auto& i : _header)
+ output += i.first + ": " + i.second + '\n';
+
+ output += '\n' + _payload + '\n';
+
+ return output;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Msg::parse (const std::string& input)
+{
+ _header.clear ();
+ _payload = "";
+
+ auto separator = input.find ("\n\n");
+ if (separator == std::string::npos)
+ throw std::string ("Malformed message");
+
+ // Parse header.
+ auto lines = split (input.substr (0, separator), '\n');
+ for (auto& i : lines)
+ {
+ auto delimiter = i.find (':');
+ if (delimiter == std::string::npos)
+ throw std::string ("Malformed message header '") + i + '\'';
+
+ _header[trim (i.substr (0, delimiter))] = trim (i.substr (delimiter + 1));
+ }
+
+ // Parse payload.
+ _payload = input.substr (separator + 2);
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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_MSG
+#define INCLUDED_MSG
+
+#include <map>
+#include <vector>
+#include <string>
+
+class Msg
+{
+public:
+ void set (const std::string&, int);
+ void set (const std::string&, const std::string&);
+ std::string get (const std::string&) const;
+
+ void setPayload (const std::string&);
+ std::string getPayload () const;
+
+ std::vector <std::string> all () const;
+ std::string serialize () const;
+ bool parse (const std::string&);
+
+private:
+ std::map <std::string, std::string> _header {};
+ std::string _payload {""};
+};
+
+#endif
+
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <PEG.h>
+#include <Lexer.h>
+#include <shared.h>
+#include <format.h>
+#include <iostream>
+
+////////////////////////////////////////////////////////////////////////////////
+std::string PEG::Token::dump () const
+{
+ std::stringstream out;
+ out << (_lookahead == Token::Lookahead::positive ? "\e[34m&\e[0m" :
+ _lookahead == Token::Lookahead::negative ? "\e[34m!\e[0m" : "")
+ << _token
+ << (_quantifier == Token::Quantifier::zero_or_one ? "\e[34m?\e[0m" :
+ _quantifier == Token::Quantifier::one_or_more ? "\e[34m+\e[0m" :
+ _quantifier == Token::Quantifier::zero_or_more ? "\e[34m*\e[0m" : "");
+
+ for (const auto& tag : _tags)
+ out << " \e[34m" << tag << "\e[0m";
+
+ return out.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void PEG::loadFromFile (File& file)
+{
+ if (! file.exists ())
+ throw format ("PEG file '{1}' not found.", file._data);
+
+ std::string contents;
+ file.read (contents);
+
+ // TODO Instead of simply reading a file, read a file and allow lines that
+ // match /^include <path>$/ to represent nested files.
+
+ loadFromString (contents);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Load and parse PEG.
+//
+// Syntax:
+// rule-name: alternate1-token1 alternate1-token2
+// alternate2-token1
+//
+// - Rules are aligned at left margin only, followed by colon.
+// - Productions are indented and never at left margin.
+// - Blank lines delineate rules.
+//
+// Details:
+// - String literals are always double-quoted.
+// - Character literals are alをays single-quoted.
+// - "*", "+" and "?" suffixes have POSIX wildcard semantics.
+//
+void PEG::loadFromString (const std::string& input)
+{
+ // This is a state machine. Read each line.
+ std::string rule_name = "";
+ for (auto& line : loadImports (split (input, '\n')))
+ {
+ line = trim (line);
+
+ // Eliminate inline comments.
+ auto hash = line.find ('#');
+ if (hash != std::string::npos)
+ {
+ line.resize (hash);
+ line = trim (line);
+
+ if (line == "")
+ continue;
+ }
+ else
+ {
+ line = trim (line);
+ }
+
+ // Skip blank lines with no semantics.
+ if (line == "" and rule_name == "")
+ continue;
+
+ if (line != "")
+ {
+ int token_count = 0;
+
+ // Instantiate and configure the Lexer.
+ Lexer l (line);
+ l.noDate ();
+ l.noDuration ();
+ l.noUUID ();
+ l.noHexNumber ();
+ l.noURL ();
+ l.noPath ();
+ l.noPattern ();
+ l.noOperator ();
+
+ Lexer::Type type;
+ std::string token;
+ while (l.token (token, type))
+ {
+ ++token_count;
+
+ // Rule definitions end in a colon.
+ if (token.back () == ':')
+ {
+ // Capture the Rule_name.
+ rule_name = token.substr (0, token.size () - 1);
+
+ // If this is the first Rule, capture it as a starting point.
+ if (_start == "")
+ _start = rule_name;
+
+ _rules[rule_name] = PEG::Rule ();
+ token_count = 0;
+ }
+ // Production definition.
+ else
+ {
+ // If no Production was added yet, add one.
+ if (token_count <= 1)
+ _rules[rule_name].push_back (PEG::Production ());
+
+ // Decorate the token, if necessary.
+ std::string::size_type start = 0;
+ std::string::size_type end = token.length ();
+
+ auto q = Token::Quantifier::one;
+ auto l = Token::Lookahead::none;
+
+ if (token.back () == '?')
+ {
+ q = Token::Quantifier::zero_or_one;
+ --end;
+ }
+ else if (token.back () == '+')
+ {
+ q = Token::Quantifier::one_or_more;
+ --end;
+ }
+ else if (token.back () == '*')
+ {
+ q = Token::Quantifier::zero_or_more;
+ --end;
+ }
+
+ if (token.front () == '&')
+ {
+ l = Token::Lookahead::positive;
+ ++start;
+ }
+ else if (token.front () == '!')
+ {
+ l = Token::Lookahead::negative;
+ ++start;
+ }
+
+ PEG::Token t (token.substr (start, end - start));
+ t._quantifier = q;
+ t._lookahead = l;
+
+ if (type == Lexer::Type::string)
+ {
+ t.tag ("literal");
+ t.tag (token[0] == '\'' ? "character" : "string");
+ }
+ else if (t._token.front () == '<' and
+ t._token.back () == '>')
+ {
+ t.tag ("intrinsic");
+
+ if (t._token.substr (0, 8) == "<entity:")
+ t.tag ("entity");
+
+ if (t._token.substr (0, 10) == "<external:")
+ t.tag ("external");
+ }
+
+ // Add the new Token to the most recent Production, of the current
+ // Rule.
+ _rules[rule_name].back ().push_back (t);
+ }
+ }
+ }
+
+ // A blank line in the input ends the current rule definition.
+ else
+ rule_name = "";
+ }
+
+ if (_debug)
+ std::cout << dump ();
+
+ // Validate the parsed grammar.
+ validate ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::map <std::string, PEG::Rule> PEG::syntax () const
+{
+ return _rules;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string PEG::firstRule () const
+{
+ return _start;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void PEG::debug ()
+{
+ ++_debug;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void PEG::strict (bool value)
+{
+ _strict = value;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string PEG::dump () const
+{
+ std::stringstream out;
+ out << "PEG\n";
+
+ // Show the import files, if any.
+ if (_imports.size ())
+ {
+ for (const auto& import : _imports)
+ out << " import " << import << '\n';
+ out << '\n';
+ }
+
+ // Determine longest rule name, for display alignment.
+ size_t longest = 0;
+ for (const auto& rule : _rules)
+ if (rule.first.length () > longest)
+ longest = rule.first.length ();
+
+ for (const auto& rule : _rules)
+ {
+ // Indicate the start Rule.
+ out << " "
+ << (rule.first == _start ? "▶" : " ")
+ << ' '
+ << rule.first
+ << ':'
+ << std::string (1 + longest - rule.first.length (), ' ');
+
+ int count = 0;
+ for (const auto& production : rule.second)
+ {
+ if (count)
+ out << std::string (6 + longest, ' ');
+
+ for (const auto& token : production)
+ out << token.dump () << ' ';
+
+ out << "\n";
+ ++count;
+ }
+
+ out << "\n";
+ }
+
+ return out.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::vector <std::string> PEG::loadImports (const std::vector <std::string>& lines)
+{
+ std::vector <std::string> resolved;
+
+ for (auto& line : lines)
+ {
+ auto copy = trim (line);
+
+ auto hash = copy.find ('#');
+ if (hash != std::string::npos)
+ {
+ copy.resize (hash);
+ copy = trim (copy);
+ }
+
+ if (copy.find ("import ") == 0)
+ {
+ File file (trim (copy.substr (7)));
+ if (file.exists () &&
+ file.readable ())
+ {
+ // Only import files that are not already imported.
+ if (std::find (_imports.begin (), _imports.end (), file._data) == _imports.end ())
+ {
+ _imports.push_back (file._data);
+
+ std::vector <std::string> imported;
+ file.read (imported);
+ imported = loadImports (imported);
+
+ resolved.insert(std::end(resolved), std::begin(imported), std::end(imported));
+ }
+ }
+ else
+ throw format ("Cannot import '{1}'", file._data);
+ }
+ else
+ {
+ // Store original line.
+ resolved.push_back (line);
+ }
+ }
+
+ return resolved;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void PEG::validate () const
+{
+ if (_start == "")
+ throw std::string ("There are no rules defined.");
+
+ std::vector <std::string> allRules;
+ std::vector <std::string> allTokens;
+ std::vector <std::string> allLeftRecursive;
+ std::vector <std::string> intrinsics;
+ std::vector <std::string> externals;
+
+ for (const auto& rule : _rules)
+ {
+ allRules.push_back (rule.first);
+
+ for (const auto& production : rule.second)
+ {
+ for (const auto& token : production)
+ {
+ if (token.hasTag ("intrinsic"))
+ intrinsics.push_back (token._token);
+
+ else if (token.hasTag ("external"))
+ externals.push_back (token._token);
+
+ else if (! token.hasTag ("literal"))
+ allTokens.push_back (token._token);
+
+ if (token._token == production[0]._token and
+ rule.first == production[0]._token and
+ production.size () == 1)
+ allLeftRecursive.push_back (token._token);
+ }
+ }
+ }
+
+ std::vector <std::string> notUsed;
+ std::vector <std::string> notDefined;
+ listDiff (allRules, allTokens, notUsed, notDefined);
+
+ // Undefined value - these are definitions that appear in token, but are
+ // not in _rules.
+ for (const auto& nd : notDefined)
+ if (std::find (externals.begin (), externals.end (), nd) == externals.end ())
+ throw format ("Definition '{1}' referenced, but not defined.", nd);
+
+ // Circular definitions - these are names in _rules that also appear as
+ // the only token in any of the alternates for that definition.
+ for (const auto& lr : allLeftRecursive)
+ throw format ("Definition '{1}' is left recursive.", lr);
+
+ for (const auto& r : allRules)
+ if (r[0] == '<')
+ throw format ("Definition '{1}' may not redefine an intrinsic.");
+
+ for (const auto& r : allRules)
+ if (r[0] == '"' or
+ r[0] == '\'')
+ throw format ("Definition '{1}' may not be a literal.");
+
+ // Unused definitions - these are names in _rules that are never
+ // referenced as token.
+ for (const auto& nu : notUsed)
+ {
+ if (std::find (externals.begin (), externals.end (), nu) == externals.end () &&
+ nu != _start)
+ {
+ if (_strict)
+ throw format ("Definition '{1}' is defined, but not referenced.", nu);
+ else
+ std::cout << "Warning: Definition '" << nu << "' is defined, but not referenced.\n";
+ }
+ }
+
+ // Intrinsics must be recognized.
+ for (auto& intrinsic : intrinsics)
+ if (intrinsic != "<digit>" &&
+ intrinsic != "<hex>" &&
+ intrinsic != "<punct>" &&
+ intrinsic != "<eol>" &&
+ intrinsic != "<sep>" &&
+ intrinsic != "<ws>" &&
+ intrinsic != "<alpha>" &&
+ intrinsic != "<character>" &&
+ intrinsic != "<word>" &&
+ intrinsic != "<token>" &&
+ intrinsic.substr (0, 8) != "<entity:" &&
+ intrinsic.substr (0, 10) != "<external:")
+ throw format ("Specified intrinsic '{1}' is not supported.", intrinsic);
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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_PEG
+#define INCLUDED_PEG
+
+#include <FS.h>
+#include <string>
+#include <vector>
+#include <map>
+#include <set>
+
+class PEG
+{
+public:
+ class Token
+ {
+ public:
+ Token (const std::string& value) { _token = value; }
+ void tag (const std::string& tag) { _tags.insert (tag); }
+ bool hasTag (const std::string& tag) const { return _tags.find (tag) != _tags.end (); };
+ std::string dump () const;
+
+ enum class Quantifier { one, zero_or_one, one_or_more, zero_or_more };
+ enum class Lookahead { none, positive, negative };
+
+ std::string _token {};
+ std::set <std::string> _tags {};
+ Quantifier _quantifier {Quantifier::one};
+ Lookahead _lookahead {Lookahead::none};
+ // TODO Added Lexer::Type support, which allows the PEG to specify
+ // "<Lexer::Type>" as a built-in type.
+ };
+
+ class Production : public std::vector <Token>
+ {
+ };
+
+ class Rule : public std::vector <Production>
+ {
+ };
+
+public:
+ void loadFromFile (File&);
+ void loadFromString (const std::string&);
+ std::map <std::string, PEG::Rule> syntax () const;
+ std::string firstRule () const;
+ void debug ();
+ void strict (bool);
+ std::string dump () const;
+
+private:
+ std::vector <std::string> loadImports (const std::vector <std::string>&);
+ void validate () const;
+
+private:
+ // rule name rule
+ // | |
+ std::map <std::string, PEG::Rule> _rules {};
+ std::string _start {};
+ int _debug {0};
+ bool _strict {false};
+ std::vector <std::string> _imports {};
+};
+
+#endif
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <Packrat.h>
+#include <shared.h>
+#include <format.h>
+#include <unicode.h>
+#include <utf8.h>
+#include <iostream>
+
+int Packrat::minimumMatchLength = 3;
+
+////////////////////////////////////////////////////////////////////////////////
+void Packrat::debug ()
+{
+ ++_debug;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Walk the grammar tree to parse the input text, resulting in a parse tree.
+void Packrat::parse (const PEG& peg, const std::string& input)
+{
+ // Used to walk the grammar tree.
+ // Note there is only one rule at the top of the syntax tree, which was the
+ // first one defined.
+ _syntax = peg.syntax ();
+ _tree->_name = peg.firstRule ();
+
+ // The pig that will be sent down the pipe.
+ Pig pig (input);
+ if (_debug)
+ std::cout << "trace " << pig.dump () << "\n";
+
+ // Match the first rule. Recursion does the rest.
+ if (! matchRule (_tree->_name, pig, _tree, 0))
+ throw std::string ("Parse failed.");
+
+ if (! pig.eos ())
+ throw format ("Parse failed - extra character at position {1}.", pig.cursor ());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Packrat::entity (const std::string& category, const std::string& name)
+{
+ // Walk the list of entities for category.
+ auto c = _entities.equal_range (category);
+ for (auto e = c.first; e != c.second; ++e)
+ if (e->second == name)
+ return;
+
+ // The category/name pair was not found, therefore add it.
+ _entities.insert (std::pair <std::string, std::string> (category, name));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Packrat::external (
+ const std::string& rule,
+ bool (*fn)(Pig&, std::shared_ptr <Tree>))
+{
+ if (_externals.find (rule) != _externals.end ())
+ throw format ("There is already an external parser defined for rule '{1}'.", rule);
+
+ _externals[rule] = fn;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// If there is a match, pig advances further down the pipe.
+bool Packrat::matchRule (
+ const std::string& rule,
+ Pig& pig,
+ std::shared_ptr <Tree> parseTree,
+ int indent)
+{
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "matchRule " << rule << "\n";
+ auto checkpoint = pig.cursor ();
+
+ for (const auto& production : _syntax.find (rule)->second)
+ if (matchProduction (production, pig, parseTree, indent + 1))
+ return true;
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Packrat::matchProduction (
+ const PEG::Production& production,
+ Pig& pig,
+ std::shared_ptr <Tree> parseTree,
+ int indent)
+{
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "matchProduction\n";
+ auto checkpoint = pig.cursor ();
+
+ auto collector = std::make_shared <Tree> ();
+ for (const auto& token : production)
+ {
+ auto b = std::make_shared <Tree> ();
+ if (! matchTokenQuant (token, pig, b, indent + 1))
+ {
+ pig.restoreTo (checkpoint);
+ return false;
+ }
+
+ // Accumulate branches.
+ collector->addBranch (b);
+ }
+
+ // On success transfer all sub-branches.
+ for (auto& b : collector->_branches)
+ for (auto sub : b->_branches)
+ parseTree->addBranch (sub);
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Wraps calls to matchTokenLookahead, while properly handling the quantifier.
+bool Packrat::matchTokenQuant (
+ const PEG::Token& token,
+ Pig& pig,
+ std::shared_ptr <Tree> parseTree,
+ int indent)
+{
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "matchTokenQuant " << token.dump () << "\n";
+
+ // Must match exactly once, so run once and return the result.
+ if (token._quantifier == PEG::Token::Quantifier::one)
+ {
+ return matchTokenLookahead (token, pig, parseTree, indent + 1);
+ }
+
+ // May match zero or one time. If it matches, the cursor will be advanced.
+ // If it fails, the cursor will not be advanced, but this is still considered
+ // successful. Return true either way, but backtrack the cursor on failure.
+ else if (token._quantifier == PEG::Token::Quantifier::zero_or_one)
+ {
+ // Check for a single match, succeed anyway.
+ matchTokenLookahead (token, pig, parseTree, indent + 1);
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[32mmatch ?\e[0m " << token.dump () << "\n";
+ if (_debug)
+ std::cout << "trace " << pig.dump () << ' ' << token.dump () << "\n";
+ return true;
+ }
+
+ // May match 1 or more times. If it matches on the first attempt, continue
+ // to greedily match until it fails. If it fails on the first attempt, then
+ // the rule fails.
+ else if (token._quantifier == PEG::Token::Quantifier::one_or_more)
+ {
+ if (! matchTokenLookahead (token, pig, parseTree, indent + 1))
+ return false;
+
+ while (matchTokenLookahead (token, pig, parseTree, indent + 1))
+ {
+ // "Forget it, he's rolling."
+ }
+
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[32mmatch +\e[0m " << token.dump () << "\n";
+ if (_debug)
+ std::cout << "trace " << pig.dump () << ' ' << token.dump () << "\n";
+ return true;
+ }
+
+ // May match zero or more times. Keep calling while there are matches, and
+ // return true always. Backtrack the cursor on failure.
+ else if (token._quantifier == PEG::Token::Quantifier::zero_or_more)
+ {
+ while (matchTokenLookahead (token, pig, parseTree, indent + 1))
+ {
+ // Let it go.
+ }
+
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[32mmatch *\e[0m " << token.dump () << "\n";
+ if (_debug)
+ std::cout << "trace " << pig.dump () << ' ' << token.dump () << "\n";
+ return true;
+ }
+
+ throw std::string ("This should never happen.");
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Wraps calls to matchToken, while properly handling lookahead.
+bool Packrat::matchTokenLookahead (
+ const PEG::Token& token,
+ Pig& pig,
+ std::shared_ptr <Tree> parseTree,
+ int indent)
+{
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "matchTokenLookahead " << token.dump () << "\n";
+
+ if (token._lookahead == PEG::Token::Lookahead::none)
+ {
+ return matchToken (token, pig, parseTree, indent + 1);
+ }
+ else if (token._lookahead == PEG::Token::Lookahead::positive)
+ {
+ auto checkpoint = pig.cursor ();
+ auto b = std::make_shared <Tree> ();
+ if (matchToken (token, pig, b, indent + 1))
+ {
+ pig.restoreTo (checkpoint);
+ return true;
+ }
+ }
+ else if (token._lookahead == PEG::Token::Lookahead::negative)
+ {
+ auto checkpoint = pig.cursor ();
+ auto b = std::make_shared <Tree> ();
+ if (! matchToken (token, pig, b, indent + 1))
+ {
+ return true;
+ }
+
+ pig.restoreTo (checkpoint);
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Packrat::matchToken (
+ const PEG::Token& token,
+ Pig& pig,
+ std::shared_ptr <Tree> parseTree,
+ int indent)
+{
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "matchToken " << token.dump () << "\n";
+
+ auto checkpoint = pig.cursor ();
+ auto b = std::make_shared <Tree> ();
+
+ if (token.hasTag ("intrinsic") &&
+ matchIntrinsic (token, pig, parseTree, indent + 1))
+ {
+ return true;
+ }
+
+ else if (_syntax.find (token._token) != _syntax.end () &&
+ matchRule (token._token, pig, b, indent + 1))
+ {
+ // This is the only case that adds a sub-branch.
+ b->_name = token._token;
+ parseTree->addBranch (b);
+ return true;
+ }
+
+ else if (token.hasTag ("literal") &&
+ token.hasTag ("character") &&
+ matchCharLiteral (token, pig, parseTree, indent + 1))
+ {
+ return true;
+ }
+
+ else if (token.hasTag ("literal") &&
+ token.hasTag ("string") &&
+ matchStringLiteral (token, pig, parseTree, indent + 1))
+ {
+ return true;
+ }
+
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Supports the following:
+// <digit> --> unicodeLatinDigit
+// <hex> --> unicodeHexDigit
+// <character> --> anything
+// <alpha> --> unicodeAlpha
+// <punct> --> unicodePunctuation
+// <ws> --> unicodeWhitespace
+// <sep> --> unicodeHorizontalWhitespace
+// <eol> --> unicodeVerticalWhitespace
+// <word> --> <alpha>
+// <token> --> consecutive non <ws>
+// <entity:e> --> Any category 'e' token
+// <external:x> --> Delegate to external function
+bool Packrat::matchIntrinsic (
+ const PEG::Token& token,
+ Pig& pig,
+ std::shared_ptr <Tree> parseTree,
+ int indent)
+{
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "matchIntrinsic " << token.dump () << "\n";
+ auto checkpoint = pig.cursor ();
+
+ // There are only 10 digits.
+ if (token._token == "<digit>")
+ {
+ int digit;
+ if (pig.getDigit (digit))
+ {
+ // Create a populated branch.
+ auto b = std::make_shared <Tree> ();
+ b->_name = "intrinsic";
+ b->attribute ("expected", token._token);
+ b->attribute ("value", format ("{1}", digit));
+ parseTree->addBranch (b);
+
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[32mmatch\e[0m " << digit << "\n";
+ if (_debug)
+ std::cout << "trace " << pig.dump () << ' ' << token.dump () << "\n";
+ return true;
+ }
+ }
+
+ // Upper or lower case hex digit.
+ if (token._token == "<hex>")
+ {
+ int digit;
+ if (pig.getHexDigit (digit))
+ {
+ // Create a populated branch.
+ auto b = std::make_shared <Tree> ();
+ b->_name = "intrinsic";
+ b->attribute ("expected", token._token);
+ b->attribute ("value", format ("{1}", digit));
+ parseTree->addBranch (b);
+
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[32mmatch\e[0m " << digit << "\n";
+ if (_debug)
+ std::cout << "trace " << pig.dump () << ' ' << token.dump () << "\n";
+ return true;
+ }
+ }
+
+ // Character means anything.
+ else if (token._token == "<character>")
+ {
+ int character;
+ if (pig.getCharacter (character))
+ {
+ // Create a populated branch.
+ auto b = std::make_shared <Tree> ();
+ b->_name = "intrinsic";
+ b->attribute ("expected", token._token);
+ b->attribute ("value", format ("{1}", character));
+ parseTree->addBranch (b);
+
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[32mmatch\e[0m " << character << "\n";
+ if (_debug)
+ std::cout << "trace " << pig.dump () << ' ' << token.dump () << "\n";
+ return true;
+ }
+ }
+
+ // <punct> ::ispunct
+ else if (token._token == "<punct>")
+ {
+ int character = pig.peek ();
+ if (unicodePunctuation (character))
+ {
+ pig.skip (character);
+
+ // Create a populated branch.
+ auto b = std::make_shared <Tree> ();
+ b->_name = "intrinsic";
+ b->attribute ("expected", token._token);
+ b->attribute ("value", format ("{1}", character));
+ parseTree->addBranch (b);
+
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[32mmatch\e[0m " << character << "\n";
+ if (_debug)
+ std::cout << "trace " << pig.dump () << ' ' << token.dump () << "\n";
+ return true;
+ }
+ }
+
+ // <alpha>
+ else if (token._token == "<alpha>")
+ {
+ int character = pig.peek ();
+ if (unicodeAlpha (character))
+ {
+ pig.skip (character);
+
+ // Create a populated branch.
+ auto b = std::make_shared <Tree> ();
+ b->_name = "intrinsic";
+ b->attribute ("expected", token._token);
+ b->attribute ("value", format ("{1}", character));
+ parseTree->addBranch (b);
+
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[32mmatch\e[0m " << character << "\n";
+ if (_debug)
+ std::cout << "trace " << pig.dump () << ' ' << token.dump () << "\n";
+ return true;
+ }
+ }
+
+ // <ws>
+ else if (token._token == "<ws>")
+ {
+ int character = pig.peek ();
+ if (unicodeWhitespace (character))
+ {
+ pig.skip (character);
+
+ // Create a populated branch.
+ auto b = std::make_shared <Tree> ();
+ b->_name = "intrinsic";
+ b->attribute ("expected", token._token);
+ b->attribute ("value", format ("{1}", character));
+ parseTree->addBranch (b);
+
+ if (_debug > 10)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[32mmatch\e[0m " << character << "\n";
+ if (_debug)
+ std::cout << "trace " << pig.dump () << ' ' << token.dump () << "\n";
+ return true;
+ }
+ }
+
+ // <sep>
+ else if (token._token == "<sep>")
+ {
+ int character = pig.peek ();
+ if (unicodeHorizontalWhitespace (character))
+ {
+ pig.skip (character);
+
+ // Create a populated branch.
+ auto b = std::make_shared <Tree> ();
+ b->_name = "intrinsic";
+ b->attribute ("expected", token._token);
+ b->attribute ("value", format ("{1}", character));
+ parseTree->addBranch (b);
+
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[32mmatch\e[0m " << character << "\n";
+ if (_debug)
+ std::cout << "trace " << pig.dump () << ' ' << token.dump () << "\n";
+ return true;
+ }
+ }
+
+ // <eol>
+ else if (token._token == "<eol>")
+ {
+ int character = pig.peek ();
+ if (unicodeVerticalWhitespace (character))
+ {
+ pig.skip (character);
+
+ // Create a populated branch.
+ auto b = std::make_shared <Tree> ();
+ b->_name = "intrinsic";
+ b->attribute ("expected", token._token);
+ b->attribute ("value", format ("{1}", character));
+ parseTree->addBranch (b);
+
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[32mmatch\e[0m " << character << "\n";
+ if (_debug)
+ std::cout << "trace " << pig.dump () << ' ' << token.dump () << "\n";
+ return true;
+ }
+ }
+
+ // <word> consecutive non-<ws>, non-<punct>.
+ else if (token._token == "<word>")
+ {
+ while (auto character = pig.peek ())
+ {
+ if (! character ||
+ unicodeWhitespace (character) ||
+ unicodePunctuation (character))
+ break;
+
+ pig.skip (character);
+ }
+
+ if (pig.cursor () > checkpoint)
+ {
+ auto word = pig.substr (checkpoint, pig.cursor () - checkpoint + 1);
+
+ // Create a populated branch.
+ auto b = std::make_shared <Tree> ();
+ b->_name = "intrinsic";
+ b->attribute ("expected", token._token);
+ b->attribute ("value", word);
+ parseTree->addBranch (b);
+
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[32mmatch\e[0m " << word << "\n";
+ if (_debug)
+ std::cout << "trace " << pig.dump () << ' ' << token.dump () << "\n";
+ return true;
+ }
+ }
+
+ // <token> consecutive non-<ws>.
+ else if (token._token == "<token>")
+ {
+ while (auto character = pig.peek ())
+ {
+ if (! character ||
+ unicodeWhitespace (character))
+ break;
+
+ pig.skip (character);
+ }
+
+ if (pig.cursor () > checkpoint)
+ {
+ auto word = pig.substr (checkpoint, pig.cursor () - checkpoint);
+
+ // Create a populated branch.
+ auto b = std::make_shared <Tree> ();
+ b->_name = "intrinsic";
+ b->attribute ("expected", token._token);
+ b->attribute ("value", word);
+ parseTree->addBranch (b);
+
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[32mmatch\e[0m " << word << "\n";
+ if (_debug)
+ std::cout << "trace " << pig.dump () << ' ' << token.dump () << "\n";
+ return true;
+ }
+ }
+
+ // <entity:category>.
+ else if (token._token.find ("<entity:") == 0)
+ {
+ // Extract entity category
+ auto category = token._token.substr (8, token._token.length () - 9);
+
+ // Match against any one of the entity values in this category.
+ auto values = _entities.equal_range (category);
+ for (auto value = values.first; value != values.second; ++value)
+ {
+ if (pig.skipLiteral (value->second))
+ {
+ // Create a populated branch.
+ auto b = std::make_shared <Tree> ();
+ b->_name = "intrinsic";
+ b->tag ("entity");
+ b->attribute ("expected", token._token);
+ b->attribute ("value", value->second);
+ parseTree->addBranch (b);
+
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[32mmatch\e[0m " << value->second << "\n";
+ if (_debug)
+ std::cout << "trace " << pig.dump () << ' ' << token.dump () << "\n";
+
+ return true;
+ }
+ }
+ }
+
+ // <external:rule>
+ else if (token._token.find ("<external:") == 0)
+ {
+ // Extract entity category
+ auto rule = token._token.substr (10, token._token.length () - 11);
+
+ // Any rule can be overridden by an external parser.
+ if (_externals.find (rule) != _externals.end ())
+ {
+ // Create a pre-populated branch, which is attached on success only.
+ auto newBranch = std::make_shared <Tree> ();
+ newBranch->_name = "intrinsic";
+ newBranch->tag ("external");
+ newBranch->attribute ("expected", token._token);
+
+ if (_externals[rule] (pig, newBranch))
+ {
+ // Determine what was parsed.
+ auto word = pig.substr (checkpoint, pig.cursor () - checkpoint);
+
+ // Attach the new branch.
+ newBranch->attribute ("value", word);
+ parseTree->addBranch (newBranch);
+
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[32mmatch\e[0m " << word << "\n";
+ if (_debug)
+ std::cout << "trace " << pig.dump () << ' ' << token.dump () << "\n";
+
+ return true;
+ }
+
+ // Note: Branch 'newBranch' goes out of scope here if parsing fails.
+ }
+ }
+
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[31mfail\e[0m " << token._token << "\n";
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Packrat::matchCharLiteral (
+ const PEG::Token& token,
+ Pig& pig,
+ std::shared_ptr <Tree> parseTree,
+ int indent)
+{
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "matchCharLiteral " << token.dump () << "\n";
+ auto checkpoint = pig.cursor ();
+
+ if (token._token.length () >= 3 &&
+ token._token[0] == '\'' &&
+ token._token[2] == '\'')
+ {
+ int literal = token._token[1];
+ if (pig.skip (literal))
+ {
+ // Create a populated branch.
+ auto b = std::make_shared <Tree> ();
+ b->_name = "charLiteral";
+ b->attribute ("expected", token._token);
+ b->attribute ("value", utf8_character (literal));
+ parseTree->addBranch (b);
+
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[32mmatch\e[0m " << token._token << "\n";
+ if (_debug)
+ std::cout << "trace " << pig.dump () << ' ' << token.dump () << "\n";
+ return true;
+ }
+ }
+
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[31mfail\e[0m " << token._token << "\n";
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Packrat::matchStringLiteral (
+ const PEG::Token& token,
+ Pig& pig,
+ std::shared_ptr <Tree> parseTree,
+ int indent)
+{
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "matchStringLiteral " << token.dump () << "\n";
+ auto checkpoint = pig.cursor ();
+
+ std::string literal = token._token.substr (1, token._token.length () - 2);
+ if (pig.skipLiteral (literal))
+ {
+ // Create a populated branch.
+ auto b = std::make_shared <Tree> ();
+ b->_name = "stringLiteral";
+ b->attribute ("expected", token._token);
+ b->attribute ("value", literal);
+ parseTree->addBranch (b);
+
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[32mmatch\e[0m " << literal << "\n";
+ if (_debug)
+ std::cout << "trace " << pig.dump () << ' ' << token.dump () << "\n";
+ return true;
+ }
+
+ if (_debug > 1)
+ std::cout << "trace " << std::string (indent, ' ') << "\e[31mfail\e[0m " << token._token << "\n";
+ pig.restoreTo (checkpoint);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Search for 'value' in _entities category, return canonicalized value.
+bool Packrat::canonicalize (
+ std::string& canonicalized,
+ const std::string& category,
+ const std::string& value) const
+{
+ // Extract a list of entities for category.
+ std::vector <std::string> options;
+ auto c = _entities.equal_range (category);
+ for (auto e = c.first; e != c.second; ++e)
+ {
+ // Shortcut: if an exact match is found, success.
+ if (value == e->second)
+ {
+ canonicalized = value;
+ return true;
+ }
+
+ options.push_back (e->second);
+ }
+
+ // Match against the options, throw away results.
+ std::vector <std::string> matches;
+ if (autoComplete (value, options, matches, minimumMatchLength) == 1)
+ {
+ canonicalized = matches[0];
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string Packrat::dump () const
+{
+ std::stringstream out;
+ if (_debug)
+ out << '\n';
+
+ out << "Packrat Parse "
+ << _tree->dump ();
+
+ if (_entities.size ())
+ {
+ out << " Entities\n";
+ for (const auto& entity : _entities)
+ out << " " << entity.first << ':' << entity.second << '\n';
+ }
+
+ if (_externals.size ())
+ {
+ out << " Externals\n";
+ for (const auto& external : _externals)
+ out << " " << external.first << "\n";
+ }
+
+ return out.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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_PACKRAT
+#define INCLUDED_PACKRAT
+
+#include <PEG.h>
+#include <Tree.h>
+#include <Pig.h>
+#include <string>
+
+class Packrat
+{
+public:
+ void parse (const PEG&, const std::string&);
+ void entity (const std::string&, const std::string&);
+ void external (const std::string&, bool (*)(Pig&, std::shared_ptr <Tree>));
+
+ void debug ();
+ std::string dump () const;
+
+private:
+ bool matchRule (const std::string&, Pig&, std::shared_ptr <Tree>, int);
+ bool matchProduction (const PEG::Production&, Pig&, std::shared_ptr <Tree>, int);
+ bool matchTokenQuant (const PEG::Token&, Pig&, std::shared_ptr <Tree>, int);
+ bool matchTokenLookahead (const PEG::Token&, Pig&, std::shared_ptr <Tree>, int);
+ bool matchToken (const PEG::Token&, Pig&, std::shared_ptr <Tree>, int);
+ bool matchIntrinsic (const PEG::Token&, Pig&, std::shared_ptr <Tree>, int);
+ bool matchCharLiteral (const PEG::Token&, Pig&, std::shared_ptr <Tree>, int);
+ bool matchStringLiteral (const PEG::Token&, Pig&, std::shared_ptr <Tree>, int);
+
+ bool canonicalize (std::string&, const std::string&, const std::string&) const;
+
+public:
+ static int minimumMatchLength;
+
+private:
+ int _debug {0};
+ std::map <std::string, PEG::Rule> _syntax {};
+ std::shared_ptr <Tree> _tree {std::make_shared <Tree> ()};
+ std::multimap <std::string, std::string> _entities {};
+ std::map <std::string, bool (*)(Pig&, std::shared_ptr <Tree>)> _externals {};
+};
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <Palette.h>
+
+////////////////////////////////////////////////////////////////////////////////
+// Use a default palette, which is overwritten in ::initialize.
+Palette::Palette ()
+{
+ _colors = {
+ Color ("white on red"),
+ Color ("white on blue"),
+ Color ("black on green"),
+ Color ("black on magenta"),
+ Color ("black on cyan"),
+ Color ("black on yellow"),
+ Color ("black on white"),
+ Color ("white on bright red"),
+ Color ("white on bright blue"),
+ Color ("black on bright green"),
+ Color ("black on bright magenta"),
+ Color ("black on bright cyan"),
+ Color ("black on bright yellow"),
+ };
+
+ _current = 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Palette::add (Color c)
+{
+ _colors.push_back (c);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Return the next color in the list. Cycle to the beginning if necessary.
+Color Palette::next ()
+{
+ if (enabled)
+ return _colors[_current++ % _colors.size ()];
+
+ return Color ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Palette::size () const
+{
+ return static_cast <int> (_colors.size ());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Palette::clear ()
+{
+ _colors.clear ();
+ _current = 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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_PALETTE
+#define INCLUDED_PALETTE
+
+#include <Color.h>
+#include <vector>
+#include <string>
+
+class Palette
+{
+public:
+ Palette ();
+ void add (Color);
+ Color next ();
+ int size () const;
+ void clear ();
+
+public:
+ bool enabled {true};
+
+private:
+ std::vector <Color> _colors {};
+ int _current {0};
+};
+
+#endif
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <Pig.h>
+#include <shared.h>
+#include <unicode.h>
+#include <utf8.h>
+#include <algorithm>
+#include <sstream>
+#include <cinttypes>
+#include <cstdlib>
+
+////////////////////////////////////////////////////////////////////////////////
+Pig::Pig (const std::string& text)
+: _text {std::make_shared <std::string> (text)}
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Pig::skip (int c)
+{
+ if ((*_text)[_cursor] == c)
+ {
+ ++_cursor;
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Pig::skipN (const int quantity)
+{
+ auto save = _cursor;
+
+ auto count = 0;
+ while (count++ < quantity)
+ {
+ if (! utf8_next_char (*_text, _cursor))
+ {
+ _cursor = save;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Pig::skipWS ()
+{
+ auto save = _cursor;
+
+ int c;
+ auto prev = _cursor;
+ while ((c = utf8_next_char (*_text, _cursor)))
+ {
+ if (! unicodeWhitespace (c))
+ {
+ _cursor = prev;
+ break;
+ }
+ prev = _cursor;
+ }
+
+ return _cursor > save;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Pig::skipLiteral (const std::string& literal)
+{
+ if (_text->find (literal, _cursor) == _cursor)
+ {
+ _cursor += literal.length ();
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Pig::skipPartial (const std::string& reference, std::string& result)
+{
+ // Walk the common substring.
+ auto pos = 0;
+ while (reference[pos] &&
+ (*_text)[_cursor + pos] &&
+ reference[pos] == (*_text)[_cursor + pos])
+ ++pos;
+
+ if (pos > 0)
+ {
+ result = _text->substr (_cursor, pos);
+ _cursor += pos;
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Pig::getUntil (int end, std::string& result)
+{
+ auto save = _cursor;
+
+ int c;
+ auto prev = _cursor;
+ while ((c = utf8_next_char (*_text, _cursor)))
+ {
+ if (c == end)
+ {
+ _cursor = prev;
+ result = _text->substr (save, _cursor - save);
+ return true;
+ }
+
+ else if (eos ())
+ {
+ result = _text->substr (save, _cursor - save);
+ return true;
+ }
+
+ prev = _cursor;
+ }
+
+ return _cursor > save;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Pig::getUntilWS (std::string& result)
+{
+ auto save = _cursor;
+
+ int c;
+ auto prev = _cursor;
+ while ((c = utf8_next_char (*_text, _cursor)))
+ {
+ if (unicodeWhitespace (c))
+ {
+ _cursor = prev;
+ result = _text->substr (save, _cursor - save);
+ return true;
+ }
+
+ // Note: This test must follow the above unicodeWhitespace(c) test because
+ // it is testing the value of 'c', and eos() is testing _cursor,
+ // which has already been advanced.
+ else if (eos ())
+ {
+ result = _text->substr (save, _cursor - save);
+ return true;
+ }
+
+ prev = _cursor;
+ }
+
+ return _cursor > save;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Pig::getCharacter (int& result)
+{
+ int c = (*_text)[_cursor];
+ if (c)
+ {
+ result = c;
+ ++_cursor;
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Pig::getDigit (int& result)
+{
+ int c = (*_text)[_cursor];
+ if (c &&
+ unicodeLatinDigit (c))
+ {
+ result = c - '0';
+ ++_cursor;
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Pig::getDigit2 (int& result)
+{
+ if (unicodeLatinDigit ((*_text)[_cursor + 0]))
+ {
+ if (unicodeLatinDigit ((*_text)[_cursor + 1]))
+ {
+ result = strtoimax (_text->substr (_cursor, 2).c_str (), NULL, 10);
+ _cursor += 2;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Pig::getDigit3 (int& result)
+{
+ if (unicodeLatinDigit ((*_text)[_cursor + 0]))
+ {
+ if (unicodeLatinDigit ((*_text)[_cursor + 1]))
+ {
+ if (unicodeLatinDigit ((*_text)[_cursor + 2]))
+ {
+ result = strtoimax (_text->substr (_cursor, 3).c_str (), NULL, 10);
+ _cursor += 3;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Pig::getDigit4 (int& result)
+{
+ if (unicodeLatinDigit ((*_text)[_cursor + 0]))
+ {
+ if (unicodeLatinDigit ((*_text)[_cursor + 1]))
+ {
+ if (unicodeLatinDigit ((*_text)[_cursor + 2]))
+ {
+ if (unicodeLatinDigit ((*_text)[_cursor + 3]))
+ {
+ result = strtoimax (_text->substr (_cursor, 4).c_str (), NULL, 10);
+ _cursor += 4;
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Pig::getDigits (int& result)
+{
+ auto save = _cursor;
+
+ int c;
+ auto prev = _cursor;
+ while ((c = utf8_next_char (*_text, _cursor)))
+ {
+ if (! unicodeLatinDigit (c))
+ {
+ _cursor = prev;
+ break;
+ }
+
+ prev = _cursor;
+ }
+
+ if (_cursor > save)
+ {
+ result = strtoimax (_text->substr (save, _cursor - save).c_str (), NULL, 10);
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Pig::getHexDigit (int& result)
+{
+ int c = (*_text)[_cursor];
+ if (c &&
+ unicodeHexDigit (c))
+ {
+ if (c >= '0' && c <= '9')
+ {
+ result = c - '0';
+ ++_cursor;
+ return true;
+ }
+ else if (c >= 'A' && c <= 'F')
+ {
+ result = c - 'A' + 10;
+ ++_cursor;
+ return true;
+ }
+ else if (c >= 'a' && c <= 'f')
+ {
+ result = c - 'a' + 10;
+ ++_cursor;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// number:
+// int frac? exp?
+//
+// int:
+// (-|+)? digit+
+//
+// frac:
+// . digit+
+//
+// exp:
+// e digit+
+//
+// e:
+// e|E (+|-)?
+//
+bool Pig::getNumber (std::string& result)
+{
+ auto i = _cursor;
+
+ // [+-]?
+ if ((*_text)[i] &&
+ ((*_text)[i] == '-' ||
+ (*_text)[i] == '+'))
+ ++i;
+
+ // digit+
+ if ((*_text)[i] &&
+ unicodeLatinDigit ((*_text)[i]))
+ {
+ ++i;
+
+ while ((*_text)[i] && unicodeLatinDigit ((*_text)[i]))
+ ++i;
+
+ // ( . digit+ )?
+ if ((*_text)[i] && (*_text)[i] == '.')
+ {
+ ++i;
+
+ while ((*_text)[i] && unicodeLatinDigit ((*_text)[i]))
+ ++i;
+ }
+
+ // ( [eE] [+-]? digit+ )?
+ if ((*_text)[i] &&
+ ((*_text)[i] == 'e' ||
+ (*_text)[i] == 'E'))
+ {
+ ++i;
+
+ if ((*_text)[i] &&
+ ((*_text)[i] == '+' ||
+ (*_text)[i] == '-'))
+ ++i;
+
+ if ((*_text)[i] && unicodeLatinDigit ((*_text)[i]))
+ {
+ ++i;
+
+ while ((*_text)[i] && unicodeLatinDigit ((*_text)[i]))
+ ++i;
+
+ result = _text->substr (_cursor, i - _cursor);
+ _cursor = i;
+ return true;
+ }
+
+ return false;
+ }
+
+ result = _text->substr (_cursor, i - _cursor);
+ _cursor = i;
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Pig::getNumber (double& result)
+{
+ std::string s;
+ if (getNumber (s))
+ {
+ result = std::strtod (s.c_str (), NULL);
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// [ + | - ] \d+ [ . [ \d+ ]]
+bool Pig::getDecimal (std::string& result)
+{
+ auto i = _cursor;
+
+ // [+-]?
+ if ((*_text)[i] &&
+ ((*_text)[i] == '-' ||
+ (*_text)[i] == '+'))
+ ++i;
+
+ // digit+
+ if ((*_text)[i] && unicodeLatinDigit ((*_text)[i]))
+ {
+ ++i;
+
+ while ((*_text)[i] && unicodeLatinDigit ((*_text)[i]))
+ ++i;
+
+ // ( . digit+ )?
+ if ((*_text)[i] && (*_text)[i] == '.')
+ {
+ ++i;
+
+ while ((*_text)[i] && unicodeLatinDigit ((*_text)[i]))
+ ++i;
+ }
+
+ result = _text->substr (_cursor, i - _cursor);
+ _cursor = i;
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Pig::getDecimal (double& result)
+{
+ std::string s;
+ if (getDecimal (s))
+ {
+ result = std::strtod (s.c_str (), NULL);
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Gets quote content: "foobar" -> foobar (for c = '"')
+// Handles escaped quotes: "foo\"bar" -> foo\"bar (for c = '"')
+// Returns false if first character is not c, or if there is no closing c.
+// Does not modify content between quotes.
+bool Pig::getQuoted (int quote, std::string& result)
+{
+ if (! (*_text)[_cursor] ||
+ (*_text)[_cursor] != quote)
+ return false;
+
+ auto start = _cursor + utf8_sequence (quote);
+ auto i = start;
+
+ while ((*_text)[i])
+ {
+ i = _text->find (quote, i);
+ if (i == std::string::npos)
+ return false; // Unclosed quote. Short cut, not definitive.
+
+ if (i == start)
+ {
+ // Empty quote
+ _cursor += 2 * utf8_sequence (quote); // Skip both quote chars
+ result = "";
+ return true;
+ }
+
+ if ((*_text)[i - 1] == '\\')
+ {
+ // Check for escaped backslashes. Backtracking like this is not very
+ // efficient, but is only done in extreme corner cases.
+
+ auto j = i - 2; // Start one character further left
+ bool is_escaped_quote = true;
+ while (j >= start && (*_text)[j] == '\\')
+ {
+ // Toggle flag for each further backslash encountered.
+ is_escaped_quote = is_escaped_quote ? false : true;
+ --j;
+ }
+
+ if (is_escaped_quote)
+ {
+ // Keep searching
+ ++i;
+ continue;
+ }
+ }
+
+ // None of the above applied, we must have found the closing quote char.
+ result.assign (*_text, start, i - start);
+ _cursor = i + utf8_sequence (quote); // Skip closing quote char
+ return true;
+ }
+
+ // This should never be reached. We could throw here instead.
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Assumes that the options are sorted by decreasing length, so that if the
+// options contain 'fourteen' and 'four', the stream is first matched against
+// the longer entry.
+bool Pig::getOneOf (
+ const std::vector <std::string>& options,
+ std::string& found)
+{
+ for (const auto& option : options)
+ {
+ if (skipLiteral (option))
+ {
+ found = option;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Pig::getHMS (int& hours, int& minutes, int& seconds)
+{
+ auto save = _cursor;
+
+ if ((getDigit2 (hours) || getDigit (hours)) &&
+ skip (':') &&
+ getDigit2 (minutes))
+ {
+ seconds = 0;
+ if (skip (':') &&
+ ! getDigit2 (seconds))
+ return false;
+
+ return true;
+ }
+
+ _cursor = save;
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Pig::getRemainder (std::string& result)
+{
+ if ((*_text)[_cursor])
+ {
+ result = _text->substr (_cursor);
+ _cursor += result.length ();
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Pig::eos () const
+{
+ return (*_text)[_cursor] == '\0';
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Peeks ahead - does not move cursor.
+int Pig::peek () const
+{
+ return (*_text)[_cursor];
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Peeks ahead - does not move cursor.
+std::string Pig::peek (const int quantity) const
+{
+ std::string::size_type adjusted = std::min (static_cast <std::string::size_type> (quantity), _text->length () - _cursor);
+ if ((*_text)[_cursor])
+ return _text->substr (_cursor, adjusted);
+
+ return "";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string::size_type Pig::cursor () const
+{
+ return _cursor;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Note: never called internally, otherwise the client cannot rely on iṫ.
+std::string::size_type Pig::save ()
+{
+ return _saved = _cursor;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Note: never called internally, otherwise the client cannot rely on iṫ.
+std::string::size_type Pig::restore ()
+{
+ return _cursor = _saved;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string::size_type Pig::restoreTo (std::string::size_type previous)
+{
+ return _cursor = previous;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string Pig::substr (
+ std::string::size_type start,
+ std::string::size_type end) const
+{
+ return _text->substr (start, end - start);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string Pig::str () const
+{
+ return _text->substr (_cursor);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Show the text, with the matched part in white on green, and the unmatched
+// part white on red, followed by the index equivalent.
+std::string Pig::dump () const
+{
+ std::stringstream out;
+ if (_cursor)
+ out << "\e[37;42m"
+ << _text->substr (0, _cursor)
+ << "\e[0m";
+
+ out << "\e[37;41m"
+ << _text->substr (_cursor)
+ << "\e[0m "
+ << _cursor
+ << '/'
+ << _text->length ();
+
+ return str_replace (out.str (), "\n", "\\n");
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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_PIG
+#define INCLUDED_PIG
+
+#include <string>
+#include <vector>
+#include <memory>
+
+class Pig
+{
+public:
+ explicit Pig (const std::string&);
+
+ bool skip (int);
+ bool skipN (const int quantity = 1);
+ bool skipWS ();
+ bool skipLiteral (const std::string&);
+ bool skipPartial (const std::string&, std::string&);
+
+ bool getUntil (int, std::string&);
+ bool getUntilWS (std::string&);
+ bool getCharacter (int&);
+ bool getDigit (int&);
+ bool getDigit2 (int&);
+ bool getDigit3 (int&);
+ bool getDigit4 (int&);
+ bool getDigits (int&);
+ bool getHexDigit (int&);
+ bool getNumber (std::string&);
+ bool getNumber (double&);
+ bool getDecimal (std::string&);
+ bool getDecimal (double&);
+ bool getQuoted (int, std::string&);
+ bool getOneOf (const std::vector <std::string>&, std::string&);
+ bool getHMS (int&, int&, int&);
+ bool getRemainder (std::string&);
+
+ bool eos () const;
+ int peek () const;
+ std::string peek (const int) const;
+ std::string::size_type cursor () const;
+ std::string::size_type save ();
+ std::string::size_type restore ();
+ std::string::size_type restoreTo (std::string::size_type);
+
+ std::string substr (std::string::size_type, std::string::size_type) const;
+ std::string str () const;
+ std::string dump () const;
+
+private:
+ std::shared_ptr<std::string> _text;
+ std::string::size_type _cursor {0};
+ std::string::size_type _saved {0};
+};
+
+#endif
--- /dev/null
+Shared
+======
+
+The 'shared' library is a set of reusable objects to be shared as-is between
+various programs, including Taskwarrior, Taskserver, Tasksh and Timewarrior.
+
+In order to be reusable, and therefore a member of libshared, conditions must
+be met:
+
+ - Shared objects may only make use of other shared objects, or external
+ dependencies (libuuid, gettext, readline etc), but no objects from the
+ project directories.
+
+ - Shared objects may assume only one external 'cmake.h' that defines
+ platform/portability constants.
+
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <RX.h>
+#include <cstdlib>
+#include <cstring>
+
+////////////////////////////////////////////////////////////////////////////////
+RX::RX ()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+RX::RX (
+ const std::string& pattern,
+ bool case_sensitive /* = true */)
+: _compiled (false)
+, _pattern (pattern)
+, _case_sensitive (case_sensitive)
+{
+ compile ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+RX::RX (const RX& other)
+{
+ _compiled = false;
+ _pattern = other._pattern;
+ _case_sensitive = other._case_sensitive;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+RX::~RX ()
+{
+ if (_compiled)
+ regfree (&_regex);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+RX& RX::operator= (const RX& other)
+{
+ _compiled = false;
+ _pattern = other._pattern;
+ _case_sensitive = other._case_sensitive;
+
+ return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void RX::compile ()
+{
+ if (! _compiled)
+ {
+ memset (&_regex, 0, sizeof (regex_t));
+
+ int result;
+ if ((result = regcomp (&_regex, _pattern.c_str (),
+#if defined REG_ENHANCED
+ REG_ENHANCED | REG_EXTENDED | REG_NEWLINE |
+#else
+ REG_EXTENDED | REG_NEWLINE |
+#endif
+ (_case_sensitive ? 0 : REG_ICASE))) != 0)
+ {
+ char message[256];
+ regerror (result, &_regex, message, 256);
+ throw std::string (message);
+ }
+
+ _compiled = true;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool RX::match (const std::string& in)
+{
+ if (! _compiled)
+ compile ();
+
+ return regexec (&_regex, in.c_str (), 0, nullptr, 0) == 0 ? true : false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool RX::match (
+ std::vector<std::string>& matches,
+ const std::string& in)
+{
+ if (! _compiled)
+ compile ();
+
+ regmatch_t rm[2];
+ int offset = 0;
+ int length = in.length ();
+ while (regexec (&_regex, in.c_str () + offset, 2, &rm[0], 0) == 0 &&
+ offset < length)
+ {
+ matches.push_back (in.substr (rm[0].rm_so + offset, rm[0].rm_eo - rm[0].rm_so));
+ offset += rm[0].rm_eo;
+
+ // Protection against zero-width patterns causing infinite loops.
+ if (rm[0].rm_so == rm[0].rm_eo)
+ ++offset;
+ }
+
+ return matches.size () ? true : false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool RX::match (
+ std::vector <int>& start,
+ std::vector <int>& end,
+ const std::string& in)
+{
+ if (! _compiled)
+ compile ();
+
+ regmatch_t rm[2];
+ int offset = 0;
+ int length = in.length ();
+ while (regexec (&_regex, in.c_str () + offset, 2, &rm[0], 0) == 0 &&
+ offset < length)
+ {
+ start.push_back (rm[0].rm_so + offset);
+ end.push_back (rm[0].rm_eo + offset);
+ offset += rm[0].rm_eo;
+
+ // Protection against zero-width patterns causing infinite loops.
+ if (rm[0].rm_so == rm[0].rm_eo)
+ ++offset;
+ }
+
+ return start.size () ? true : false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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_RX
+#define INCLUDED_RX
+
+#include <string>
+#include <vector>
+#include <regex.h>
+
+class RX
+{
+public:
+ RX ();
+ RX (const std::string&, bool caseSensitive = true);
+ RX (const RX&);
+ ~RX ();
+ RX& operator= (const RX&);
+
+ bool match (const std::string&);
+ bool match (std::vector<std::string>&, const std::string&);
+ bool match (std::vector <int>&, std::vector <int>&, const std::string&);
+
+private:
+ void compile ();
+
+private:
+ bool _compiled {false};
+ std::string _pattern {};
+ bool _case_sensitive {false};
+ regex_t _regex;
+};
+
+#endif
+
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <JSON.h>
+#include <utf8.h>
+#include <sstream>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <errno.h>
+
+////////////////////////////////////////////////////////////////////////////////
+bool json::SAX::parse (const std::string& input, SAX::Sink& sink)
+{
+ sink.eventDocStart ();
+ std::string::size_type cursor = 0;
+ ignoreWhitespace (input, cursor);
+ if (isObject (input, cursor, sink) ||
+ isArray (input, cursor, sink))
+ {
+ ignoreWhitespace (input, cursor);
+ if (cursor < input.length ())
+ error ("Error: extra characters found at position ", cursor);
+
+ sink.eventDocEnd ();
+ return true;
+ }
+
+ error ("Error: Missing '{' or '[' at position ", cursor);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Complete Unicode whitespace list.
+//
+// http://en.wikipedia.org/wiki/Whitespace_character
+// Updated 2015-09-13
+void json::SAX::ignoreWhitespace (const std::string& input, std::string::size_type& cursor)
+{
+ int c = input[cursor];
+ while (c == 0x0020 || // space Common Separator, space
+ c == 0x0009 || // Common Other, control HT, Horizontal Tab
+ c == 0x000A || // Common Other, control LF, Line feed
+ c == 0x000B || // Common Other, control VT, Vertical Tab
+ c == 0x000C || // Common Other, control FF, Form feed
+ c == 0x000D || // Common Other, control CR, Carriage return
+ c == 0x0085 || // Common Other, control NEL, Next line
+ c == 0x00A0 || // no-break space Common Separator, space
+ c == 0x1680 || // ogham space mark Ogham Separator, space
+ c == 0x180E || // mongolian vowel separator Mongolian Separator, space
+ c == 0x2000 || // en quad Common Separator, space
+ c == 0x2001 || // em quad Common Separator, space
+ c == 0x2002 || // en space Common Separator, space
+ c == 0x2003 || // em space Common Separator, space
+ c == 0x2004 || // three-per-em space Common Separator, space
+ c == 0x2005 || // four-per-em space Common Separator, space
+ c == 0x2006 || // six-per-em space Common Separator, space
+ c == 0x2007 || // figure space Common Separator, space
+ c == 0x2008 || // punctuation space Common Separator, space
+ c == 0x2009 || // thin space Common Separator, space
+ c == 0x200A || // hair space Common Separator, space
+ c == 0x200B || // zero width space
+ c == 0x200C || // zero width non-joiner
+ c == 0x200D || // zero width joiner
+ c == 0x2028 || // line separator Common Separator, line
+ c == 0x2029 || // paragraph separator Common Separator, paragraph
+ c == 0x202F || // narrow no-break space Common Separator, space
+ c == 0x205F || // medium mathematical space Common Separator, space
+ c == 0x2060 || // word joiner
+ c == 0x3000) // ideographic space Common Separator, space
+ {
+ c = input[++cursor];
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// object := '{' [<pair> [, <pair> ...]] '}'
+bool json::SAX::isObject (const std::string& input, std::string::size_type& cursor, SAX::Sink& sink)
+{
+ ignoreWhitespace (input, cursor);
+ auto backup = cursor;
+
+ if (isLiteral (input, '{', cursor))
+ {
+ sink.eventObjectStart ();
+ int counter = 0;
+
+ if (isPair (input, cursor, sink))
+ {
+ ++counter;
+ while (isLiteral (input, ',', cursor) &&
+ isPair (input, cursor, sink))
+ {
+ ++counter;
+ }
+ }
+
+ ignoreWhitespace (input, cursor);
+ if (isLiteral (input, '}', cursor))
+ {
+ sink.eventObjectEnd (counter);
+ return true;
+ }
+ else
+ error ("Error: Missing '}' at position ", cursor);
+ }
+
+ cursor = backup;
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// array := '[' [<value> [, <value> ...]] ']'
+bool json::SAX::isArray (const std::string& input, std::string::size_type& cursor, SAX::Sink& sink)
+{
+ ignoreWhitespace (input, cursor);
+ auto backup = cursor;
+
+ if (isLiteral (input, '[', cursor))
+ {
+ sink.eventArrayStart ();
+ int counter = 0;
+
+ if (isValue (input, cursor, sink))
+ {
+ ++counter;
+ while (isLiteral (input, ',', cursor) &&
+ isValue (input, cursor, sink))
+ {
+ ++counter;
+ }
+ }
+
+ ignoreWhitespace (input, cursor);
+ if (isLiteral (input, ']', cursor))
+ {
+ sink.eventArrayEnd (counter);
+ return true;
+ }
+ else
+ error ("Error: Missing ']' at position ", cursor);
+ }
+
+ cursor = backup;
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// pair := <string> ':' <value>
+bool json::SAX::isPair (const std::string& input, std::string::size_type& cursor, SAX::Sink& sink)
+{
+ ignoreWhitespace (input, cursor);
+ auto backup = cursor;
+
+ if (isKey (input, cursor, sink))
+ {
+ if (isLiteral (input, ':', cursor))
+ {
+ if (isValue (input, cursor, sink))
+ return true;
+
+ error ("Error: Missing value at position ", cursor);
+ }
+ else
+ error ("Error: Missing ':' at position ", cursor);
+ }
+
+ cursor = backup;
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// value := <string>
+// | <number>
+// | <object>
+// | <array>
+// | 'true'
+// | 'false'
+// | 'null'
+bool json::SAX::isValue (const std::string& input, std::string::size_type& cursor, SAX::Sink& sink)
+{
+ ignoreWhitespace (input, cursor);
+
+ return isString (input, cursor, sink) ||
+ isNumber (input, cursor, sink) ||
+ isObject (input, cursor, sink) ||
+ isArray (input, cursor, sink) ||
+ isBool (input, cursor, sink) ||
+ isNull (input, cursor, sink);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool json::SAX::isKey (const std::string& input, std::string::size_type& cursor, SAX::Sink& sink)
+{
+ ignoreWhitespace (input, cursor);
+
+ std::string value;
+ if (isStringValue (input, cursor, value))
+ {
+ sink.eventName (value);
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool json::SAX::isString (const std::string& input, std::string::size_type& cursor, SAX::Sink& sink)
+{
+ ignoreWhitespace (input, cursor);
+
+ std::string value;
+ if (isStringValue (input, cursor, value))
+ {
+ sink.eventValueString (value);
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// string := '"' [<chars> ...] '"'
+// chars := <unicode>
+// | '\"'
+// | '\\'
+// | '\/'
+// | '\b'
+// | '\f'
+// | '\n'
+// | '\r'
+// | '\t'
+// | \uXXXX
+bool json::SAX::isStringValue (const std::string& input, std::string::size_type& cursor, std::string& value)
+{
+ auto backup = cursor;
+
+ if (isLiteral (input, '"', cursor))
+ {
+ std::string word;
+ int c;
+ while ((c = input[cursor]))
+ {
+ // EOS.
+ if (c == '"')
+ {
+ ++cursor;
+ value = word;
+ return true;
+ }
+
+ // Unicode \uXXXX codepoint.
+ else if (input[cursor + 0] == '\\' &&
+ input[cursor + 1] == 'u' &&
+ isHexDigit (input[cursor + 2]) &&
+ isHexDigit (input[cursor + 3]) &&
+ isHexDigit (input[cursor + 4]) &&
+ isHexDigit (input[cursor + 5]))
+ {
+ word += utf8_character (
+ hexToInt (
+ input[cursor + 2],
+ input[cursor + 3],
+ input[cursor + 4],
+ input[cursor + 5]));
+ cursor += 6;
+ }
+
+ // An escaped thing.
+ else if (c == '\\')
+ {
+ c = input[++cursor];
+ switch (c)
+ {
+ case '"': word += (char) 0x22; ++cursor; break;
+ case '\'': word += (char) 0x27; ++cursor; break;
+ case '\\': word += (char) 0x5C; ++cursor; break;
+ case 'b': word += (char) 0x08; ++cursor; break;
+ case 'f': word += (char) 0x0C; ++cursor; break;
+ case 'n': word += (char) 0x0A; ++cursor; break;
+ case 'r': word += (char) 0x0D; ++cursor; break;
+ case 't': word += (char) 0x09; ++cursor; break;
+ case 'v': word += (char) 0x0B; ++cursor; break;
+
+ // This pass-through default case means that anything can be escaped
+ // harmlessly. In particular 'quote' is included, if it not one of the
+ // above characters.
+ default: word += (char) c; ++cursor; break;
+ }
+ }
+
+ // Ordinary character.
+ else
+ {
+ word += (char) c;
+ ++cursor;
+ }
+ }
+
+ error ("Error: Missing '\"' at position ", cursor);
+ }
+
+ cursor = backup;
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// number := <int> [<frac>] [<exp>]
+bool json::SAX::isNumber (const std::string& input, std::string::size_type& cursor, SAX::Sink& sink)
+{
+ ignoreWhitespace (input, cursor);
+ auto backup = cursor;
+
+ std::string integerPart;
+ if (isInt (input, cursor, integerPart))
+ {
+ std::string fractionalPart;
+ isFrac (input, cursor, fractionalPart);
+
+ std::string exponentPart;
+ isExp (input, cursor, exponentPart);
+
+ // Does it fit in a long?
+ std::string combined = integerPart + fractionalPart + exponentPart;
+ char* end;
+ long longValue = strtol (combined.c_str (), &end, 10);
+ if (! *end && errno != ERANGE)
+ {
+ sink.eventValueInt (longValue);
+ return true;
+ }
+
+ // Does it fit in an unsigned long?
+ unsigned long ulongValue = strtoul (combined.c_str (), &end, 10);
+ if (! *end && errno != ERANGE)
+ {
+ sink.eventValueUint (ulongValue);
+ return true;
+ }
+
+ // If the above fail, allow this one to be capped at imax.
+ double doubleValue = strtod (combined.c_str (), &end);
+ if (! *end)
+ {
+ sink.eventValueDouble (doubleValue);
+ return true;
+ }
+ }
+
+ cursor = backup;
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// int := ['-'] <digits>
+bool json::SAX::isInt (const std::string& input, std::string::size_type& cursor, std::string& value)
+{
+ auto backup = cursor;
+
+ isLiteral (input, '-', cursor);
+ if (isDigits (input, cursor))
+ {
+ value = input.substr (backup, cursor - backup);
+ return true;
+ }
+
+ // No restore necessary.
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// frac := '.' <digits>
+bool json::SAX::isFrac (const std::string& input, std::string::size_type& cursor, std::string& value)
+{
+ auto backup = cursor;
+
+ if (isLiteral (input, '.', cursor) &&
+ isDigits (input, cursor))
+ {
+ value = input.substr (backup, cursor - backup);
+ return true;
+ }
+
+ cursor = backup;
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// digits := <digit> [<digit> ...]
+bool json::SAX::isDigits (const std::string& input, std::string::size_type& cursor)
+{
+ int c = input[cursor];
+ if (isDecDigit (c))
+ {
+ c = input[++cursor];
+
+ while (isDecDigit (c))
+ c = input[++cursor];
+
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// digit := 0x30 ('0') .. 0x39 ('9')
+bool json::SAX::isDecDigit (int c)
+{
+ return c >= 0x30 && c <= 0x39;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// hex := 0x30 ('0') .. 0x39 ('9')
+bool json::SAX::isHexDigit (int c)
+{
+ return (c >= 0x30 && c <= 0x39) ||
+ (c >= 0x61 && c <= 0x66) ||
+ (c >= 0x41 && c <= 0x46);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// exp := <e> <digits>
+bool json::SAX::isExp (const std::string& input, std::string::size_type& cursor, std::string& value)
+{
+ auto backup = cursor;
+
+ if (isE (input, cursor) &&
+ isDigits (input, cursor))
+ {
+ value = input.substr (backup, cursor - backup);
+ return true;
+ }
+
+ cursor = backup;
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// e := e
+// | e+
+// | e-
+// | E
+// | E+
+// | E-
+bool json::SAX::isE (const std::string& input, std::string::size_type& cursor)
+{
+ int c = input[cursor];
+ if (c == 'e' ||
+ c == 'E')
+ {
+ c = input[++cursor];
+
+ if (c == '+' ||
+ c == '-')
+ {
+ ++cursor;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool json::SAX::isBool (const std::string& input, std::string::size_type& cursor, SAX::Sink& sink)
+{
+ ignoreWhitespace (input, cursor);
+
+ if (input[cursor + 0] == 't' &&
+ input[cursor + 1] == 'r' &&
+ input[cursor + 2] == 'u' &&
+ input[cursor + 3] == 'e')
+ {
+ cursor += 4;
+ sink.eventValueBool (true);
+ return true;
+ }
+ else if (input[cursor + 0] == 'f' &&
+ input[cursor + 1] == 'a' &&
+ input[cursor + 2] == 'l' &&
+ input[cursor + 3] == 's' &&
+ input[cursor + 4] == 'e')
+ {
+ cursor += 5;
+ sink.eventValueBool (false);
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool json::SAX::isNull (const std::string& input, std::string::size_type& cursor, SAX::Sink& sink)
+{
+ ignoreWhitespace (input, cursor);
+
+ if (input[cursor + 0] == 'n' &&
+ input[cursor + 1] == 'u' &&
+ input[cursor + 2] == 'l' &&
+ input[cursor + 3] == 'l')
+ {
+ cursor += 4;
+ sink.eventValueNull ();
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool json::SAX::isLiteral (const std::string& input, char literal, std::string::size_type& cursor)
+{
+ ignoreWhitespace (input, cursor);
+
+ if (input[cursor] == literal)
+ {
+ ++cursor;
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Converts '0' -> 0
+// '9' -> 9
+// 'a'/'A' -> 10
+// 'f'/'F' -> 15
+int json::SAX::hexToInt (int c)
+{
+ if (c >= 0x30 && c <= 0x39) return (c - 0x30);
+ else if (c >= 0x41 && c <= 0x46) return (c - 0x41 + 10);
+ else return (c - 0x61 + 10);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int json::SAX::hexToInt (int c0, int c1, int c2, int c3)
+{
+ return (hexToInt (c0) << 12) +
+ (hexToInt (c1) << 8) +
+ (hexToInt (c2) << 4) +
+ hexToInt (c3);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void json::SAX::error (const std::string& message, std::string::size_type cursor)
+{
+ std::stringstream error;
+ error << message << cursor;
+ throw error.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <Table.h>
+#include <shared.h>
+#include <format.h>
+#include <utf8.h>
+#include <unistd.h>
+
+////////////////////////////////////////////////////////////////////////////////
+void Table::add (const std::string& col, bool alignLeft, bool wrap)
+{
+ _columns.push_back (col);
+ _align.push_back (alignLeft);
+ _wrap.push_back (wrap);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Table::addRow ()
+{
+ _data.push_back (std::vector <std::string> (_columns.size (), ""));
+ _color.push_back (std::vector <Color> (_columns.size (), Color::nocolor));
+ _oddness.push_back (_data.size () % 2 ? true : false);
+ return _data.size () - 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Table::addRowOdd ()
+{
+ _data.push_back (std::vector <std::string> (_columns.size (), ""));
+ _color.push_back (std::vector <Color> (_columns.size (), Color::nocolor));
+ _oddness.push_back (true);
+ return _data.size () - 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Table::addRowEven ()
+{
+ _data.push_back (std::vector <std::string> (_columns.size (), ""));
+ _color.push_back (std::vector <Color> (_columns.size (), Color::nocolor));
+ _oddness.push_back (false);
+ return _data.size () - 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Table::set (int row, int col, const std::string& value, const Color color)
+{
+ _data[row][col] = value;
+
+ if (color.nontrivial ())
+ _color[row][col] = color;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Table::set (int row, int col, int value, const Color color)
+{
+ std::string string_value = format (value);
+ _data[row][col] = string_value;
+
+ if (color.nontrivial ())
+ _color[row][col] = color;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Table::set (int row, int col, const Color color)
+{
+ if (color.nontrivial ())
+ _color[row][col] = color;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string Table::render ()
+{
+ // Piped output disables color, unless overridden.
+ if (! _forceColor &&
+ ! isatty (STDOUT_FILENO))
+ {
+ _header = Color ("");
+ _odd = Color ("");
+ _even = Color ("");
+ _intra_odd = Color ("");
+ _intra_even = Color ("");
+ _extra_odd = Color ("");
+ _extra_even = Color ("");
+
+ for (auto& row : _color)
+ for (auto& col : row)
+ col = Color ("");
+
+ _underline_headers = true;
+ }
+
+ // Determine minimal, ideal column widths.
+ std::vector <int> minimal;
+ std::vector <int> ideal;
+ for (unsigned int col = 0; col < _columns.size (); ++col)
+ {
+ // Headers factor in to width calculations.
+ unsigned int global_min = utf8_text_width (_columns[col]);
+ unsigned int global_ideal = global_min;
+
+ for (unsigned int row = 0; row < _data.size (); ++row)
+ {
+ // Determine minimum and ideal width for this column.
+ unsigned int min = 0;
+ unsigned int ideal = 0;
+ measureCell (_data[row][col], min, ideal);
+
+ if (min > global_min) global_min = min;
+ if (ideal > global_ideal) global_ideal = ideal;
+ }
+
+ minimal.push_back (global_min);
+ ideal.push_back (global_ideal);
+ }
+
+ // Sum the minimal widths.
+ int sum_minimal = 0;
+ for (const auto& c : minimal)
+ sum_minimal += c;
+
+ // Sum the ideal widths.
+ int sum_ideal = 0;
+ for (const auto& c : ideal)
+ sum_ideal += c;
+
+ // Calculate final column widths.
+ int overage = _width
+ - _left_margin
+ - (2 * _extra_padding)
+ - ((_columns.size () - 1) * _intra_padding);
+
+ std::vector <int> widths;
+ if (sum_ideal <= overage)
+ widths = ideal;
+ else if (sum_minimal > overage || overage < 0)
+ widths = minimal;
+ else if (overage > 0)
+ {
+ widths = minimal;
+ overage -= sum_minimal;
+
+ // Spread 'overage' among columns where width[i] < ideal[i]
+ while (overage)
+ {
+ for (unsigned int i = 0; i < _columns.size () && overage; ++i)
+ {
+ if (widths[i] < ideal[i])
+ {
+ ++widths[i];
+ --overage;
+ }
+ }
+ }
+ }
+
+ // Compose column headers.
+ unsigned int max_lines = 0;
+ std::vector <std::vector <std::string>> headers;
+ for (unsigned int c = 0; c < _columns.size (); ++c)
+ {
+ headers.push_back ({});
+ renderCell (headers[c], _columns[c], widths[c], _align[c], _wrap[c], _header);
+
+ if (headers[c].size () > max_lines)
+ max_lines = headers[c].size ();
+ }
+
+ // Output string.
+ std::string out;
+ _lines = 0;
+
+ // Render column headers.
+ std::string left_margin = std::string (_left_margin, ' ');
+ std::string extra = std::string (_extra_padding, ' ');
+ std::string intra = std::string (_intra_padding, ' ');
+
+ std::string extra_odd = _extra_odd.colorize (extra);
+ std::string extra_even = _extra_even.colorize (extra);
+ std::string intra_odd = _intra_odd.colorize (intra);
+ std::string intra_even = _intra_even.colorize (intra);
+
+ for (unsigned int i = 0; i < max_lines; ++i)
+ {
+ out += left_margin + extra;
+
+ for (unsigned int c = 0; c < _columns.size (); ++c)
+ {
+ if (c)
+ out += intra;
+
+ if (headers[c].size () < max_lines - i)
+ out += _header.colorize (std::string (widths[c], ' '));
+ else
+ out += headers[c][i];
+ }
+
+ out += extra;
+
+ // Trim right.
+ out.erase (out.find_last_not_of (" ") + 1);
+ out += '\n';
+
+ // Stop if the line limit is exceeded.
+ if (++_lines >= _truncate_lines && _truncate_lines != 0)
+ return out;
+ }
+
+ // Underline headers with ------ if necessary.
+ if (_underline_headers)
+ {
+ out += left_margin + extra;
+ for (unsigned int c = 0; c < _columns.size (); ++c)
+ {
+ if (c)
+ out += intra;
+
+ out += _header.colorize (std::string (widths[c], '-'));
+ }
+
+ out += '\n';
+ }
+
+ // Compose, render columns, in sequence.
+ _rows = 0;
+ std::vector <std::vector <std::string>> cells;
+ for (unsigned int row = 0; row < _data.size (); ++row)
+ {
+ max_lines = 0;
+
+ // Alternate rows based on |s % 2|
+ auto oddness = _oddness[row];
+ Color row_color = oddness ? _odd : _even;
+
+ // TODO row_color.blend (provided color);
+ // TODO Problem: colors for columns are specified, not rows,
+ // therefore there are only cell colors, not intra colors.
+
+ Color cell_color;
+ for (unsigned int col = 0; col < _columns.size (); ++col)
+ {
+ cell_color = row_color;
+ cell_color.blend (_color[row][col]);
+
+ cells.push_back (std::vector <std::string> ());
+ renderCell (cells[col], _data[row][col], widths[col], _align[col], _wrap[col], cell_color);
+
+ if (cells[col].size () > max_lines)
+ max_lines = cells[col].size ();
+
+ if (_obfuscate)
+ for (auto& value : cells[col])
+ value = obfuscateText (value);
+ }
+
+ for (unsigned int i = 0; i < max_lines; ++i)
+ {
+ out += left_margin + (oddness ? extra_odd : extra_even);
+
+ for (unsigned int col = 0; col < _columns.size (); ++col)
+ {
+ if (col)
+ {
+ if (row_color.nontrivial ())
+ out += row_color.colorize (intra);
+ else
+ out += (oddness ? intra_odd : intra_even);
+ }
+
+ if (i < cells[col].size ())
+ out += cells[col][i];
+ else
+ {
+ cell_color = row_color;
+ cell_color.blend (_color[row][col]);
+ out += cell_color.colorize (std::string (widths[col], ' '));
+ }
+ }
+
+ out += (oddness ? extra_odd : extra_even);
+
+ // Trim right.
+ out.erase (out.find_last_not_of (" ") + 1);
+ out += '\n';
+
+ // Stop if the line limit is exceeded.
+ if (++_lines >= _truncate_lines && _truncate_lines != 0)
+ return out;
+ }
+
+ cells.clear ();
+
+ // Stop if the row limit is exceeded.
+ if (++_rows >= _truncate_rows && _truncate_rows != 0)
+ return out;
+ }
+
+ return out;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Table::measureCell (
+ const std::string& data,
+ unsigned int& minimum,
+ unsigned int& maximum) const
+{
+ std::string stripped = Color::strip (data);
+ maximum = longestLine (stripped);
+ minimum = longestWord (stripped);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Table::renderCell (
+ std::vector <std::string>& lines,
+ const std::string& value,
+ int width,
+ bool alignLeft,
+ bool wrap,
+ const Color& color) const
+{
+ if (wrap)
+ {
+ std::vector <std::string> raw;
+ wrapText (raw, value, width, false);
+
+ for (const auto& line : raw)
+ if (alignLeft)
+ lines.push_back (
+ color.colorize (
+ leftJustify (line, width)));
+ else
+ lines.push_back (
+ color.colorize (
+ rightJustify (line, width)));
+ }
+ else
+ {
+ if (alignLeft)
+ lines.push_back (
+ color.colorize (
+ leftJustify (value, width)));
+ else
+ lines.push_back (
+ color.colorize (
+ rightJustify (value, width)));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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_TABLE
+#define INCLUDED_TABLE
+
+#include <string>
+#include <vector>
+#include <Color.h>
+
+class Table
+{
+public:
+ // View specifications.
+ void add (const std::string& col, bool alignLeft = true, bool wrap = true);
+ void width (int width) { _width = width; }
+ void leftMargin (int margin) { _left_margin = margin; }
+ void colorHeader (const Color& c) { _header = c; }
+ void colorOdd (const Color& c) { _odd = c; }
+ void colorEven (const Color& c) { _even = c; }
+ void intraPadding (int padding) { _intra_padding = padding; }
+ void intraColorOdd (const Color& c) { _intra_odd = c; }
+ void intraColorEven (const Color& c) { _intra_even = c; }
+ void extraPadding (int padding) { _extra_padding = padding; }
+ void extraColorOdd (const Color& c) { _extra_odd = c; }
+ void extraColorEven (const Color& c) { _extra_even = c; }
+ void truncateLines (int n) { _truncate_lines = n; }
+ void truncateRows (int n) { _truncate_rows = n; }
+ void forceColor () { _forceColor = true; }
+ void obfuscate () { _obfuscate = true; }
+ void underlineHeaders () { _underline_headers = true; }
+ int lines () { return _lines; }
+ int rows () { return (int) _data.size (); }
+
+ // Data provision.
+ int addRow ();
+ int addRowOdd ();
+ int addRowEven ();
+ void set (int, int, const std::string&, const Color color = Color::nocolor);
+ void set (int, int, int, const Color color = Color::nocolor);
+ void set (int, int, const Color);
+
+ // View rendering.
+ std::string render ();
+
+private:
+ void measureCell (const std::string&, unsigned int&, unsigned int&) const;
+ void renderCell (std::vector <std::string>&, const std::string&, int, bool, bool, const Color&) const;
+
+private:
+ std::vector <std::vector <std::string>> _data;
+ std::vector <std::vector <Color>> _color;
+ std::vector <std::string> _columns;
+ std::vector <bool> _align;
+ std::vector <bool> _wrap;
+ std::vector <bool> _oddness;
+ int _width {0};
+ int _left_margin {0};
+ Color _header {0};
+ Color _odd {0};
+ Color _even {0};
+ int _intra_padding {1};
+ Color _intra_odd {0};
+ Color _intra_even {0};
+ int _extra_padding {0};
+ Color _extra_odd {0};
+ Color _extra_even {0};
+ int _truncate_lines {0};
+ int _truncate_rows {0};
+ bool _forceColor {false};
+ bool _obfuscate {false};
+ bool _underline_headers {false};
+ int _lines {0};
+ int _rows {0};
+};
+
+#endif
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <Timer.h>
+
+////////////////////////////////////////////////////////////////////////////////
+Timer::Timer ()
+{
+ start ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Timer::start ()
+{
+ _start = std::chrono::high_resolution_clock::now ();
+ _end = {};
+ _running = true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Timer::stop ()
+{
+ if (_running)
+ {
+ _end = std::chrono::high_resolution_clock::now ();
+ _running = false;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+double Timer::total_s () const
+{
+ auto endpoint = _end;
+ if (_running)
+ endpoint = std::chrono::high_resolution_clock::now ();
+
+ return std::chrono::duration_cast<std::chrono::seconds>(endpoint - _start).count();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+double Timer::total_ms () const
+{
+ auto endpoint = _end;
+ if (_running)
+ endpoint = std::chrono::high_resolution_clock::now ();
+
+ return std::chrono::duration_cast<std::chrono::milliseconds>(endpoint - _start).count();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+double Timer::total_us () const
+{
+ auto endpoint = _end;
+ if (_running)
+ endpoint = std::chrono::high_resolution_clock::now ();
+
+ return std::chrono::duration_cast<std::chrono::microseconds>(endpoint - _start).count();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+double Timer::total_ns () const
+{
+ auto endpoint = _end;
+ if (_running)
+ endpoint = std::chrono::high_resolution_clock::now ();
+
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(endpoint - _start).count();
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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_TIMER
+#define INCLUDED_TIMER
+
+#include <string>
+#include <chrono>
+
+class Timer
+{
+public:
+ Timer ();
+ ~Timer () = default;
+
+ void start ();
+ void stop ();
+
+ double total_s () const;
+ double total_ms () const;
+ double total_us () const;
+ double total_ns () const;
+
+private:
+ std::chrono::time_point<std::chrono::high_resolution_clock> _start {};
+ std::chrono::time_point<std::chrono::high_resolution_clock> _end {};
+ bool _running {false};
+};
+
+#endif
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// Copyright 2010 - 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 <cmake.h>
+#include <Tree.h>
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include <shared.h>
+#include <format.h>
+
+////////////////////////////////////////////////////////////////////////////////
+// - Tree, Branch and Node are synonymous.
+// - A Tree may contain any number of branches.
+// - A Branch may contain any number of name/value pairs, unique by name.
+// - The destructor will delete all branches recursively.
+// - Tree::enumerate is a snapshot, and is invalidated by modification.
+// - Branch sequence is preserved.
+void Tree::addBranch (std::shared_ptr <Tree> branch)
+{
+ if (! branch)
+ throw "Failed to allocate memory for parse tree.";
+
+ _branches.push_back (branch);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Tree::removeBranch (std::shared_ptr <Tree> branch)
+{
+ for (auto i = _branches.begin (); i != _branches.end (); ++i)
+ {
+ if (branch == *i)
+ {
+ _branches.erase (i);
+ return;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Tree::removeAllBranches ()
+{
+ _branches.erase (_branches.begin (), _branches.end ());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Tree::replaceBranch (std::shared_ptr <Tree> from, std::shared_ptr <Tree> to)
+{
+ for (unsigned int i = 0; i < _branches.size (); ++i)
+ {
+ if (_branches[i] == from)
+ {
+ _branches[i] = to;
+ return;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessor for attributes.
+void Tree::attribute (const std::string& name, const std::string& value)
+{
+ _attributes[name] = value;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessor for attributes.
+void Tree::attribute (const std::string& name, const int value)
+{
+ _attributes[name] = format (value);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessor for attributes.
+void Tree::attribute (const std::string& name, const double value)
+{
+ _attributes[name] = format (value, 1, 8);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessor for attributes.
+std::string Tree::attribute (const std::string& name)
+{
+ // Prevent autovivification.
+ auto i = _attributes.find (name);
+ if (i != _attributes.end ())
+ return i->second;
+
+ return "";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Tree::removeAttribute (const std::string& name)
+{
+ _attributes.erase (name);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Recursively builds a list of std::shared_ptr <Tree> objects, left to right,
+// depth first. The reason for the depth-first enumeration is that a client may
+// wish to traverse the tree and delete nodes. With a depth-first iteration,
+// this is a safe mechanism, and a node pointer will never be dereferenced after
+// it has been deleted.
+void Tree::enumerate (std::vector <std::shared_ptr <Tree>>& all) const
+{
+ for (auto& i : _branches)
+ {
+ i->enumerate (all);
+ all.push_back (i);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool Tree::hasTag (const std::string& tag) const
+{
+ return std::find (_tags.begin (), _tags.end (), tag) != _tags.end ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Tree::tag (const std::string& tag)
+{
+ if (! hasTag (tag))
+ _tags.push_back (tag);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void Tree::unTag (const std::string& tag)
+{
+ auto i = std::find (_tags.begin (), _tags.end (), tag);
+ if (i != _tags.end ())
+ _tags.erase (i);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Tree::countTags () const
+{
+ return _tags.size ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int Tree::count () const
+{
+ // This branch.
+ int total = 1;
+
+ // Recurse and count the branches.
+ for (auto& i : _branches)
+ total += i->count ();
+
+ return total;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::shared_ptr <Tree> Tree::find (const std::string& path)
+{
+ std::vector <std::string> elements = split (path, '/');
+
+ // Must start at the trunk.
+ auto cursor = std::make_shared <Tree> (*this);
+ auto it = elements.begin ();
+ if (cursor->_name != *it)
+ return nullptr;
+
+ // Perhaps the trunk is what is needed?
+ if (elements.size () == 1)
+ return cursor;
+
+ // Now look for the next branch.
+ for (++it; it != elements.end (); ++it)
+ {
+ bool found = false;
+
+ // If the cursor has a branch that matches *it, proceed.
+ for (auto i = cursor->_branches.begin (); i != cursor->_branches.end (); ++i)
+ {
+ if ((*i)->_name == *it)
+ {
+ cursor = *i;
+ found = true;
+ break;
+ }
+ }
+
+ if (! found)
+ return nullptr;
+ }
+
+ return cursor;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string Tree::dumpNode (
+ const std::shared_ptr <Tree> t,
+ int depth) const
+{
+ std::stringstream out;
+
+ // Dump node
+ for (int i = 0; i < depth; ++i)
+ out << " ";
+
+ out
+ // Useful for debugging tree node new/delete errors.
+ // << std::hex << t << " "
+ << "\033[1m" << t->_name << "\033[0m";
+
+ // Dump attributes.
+ std::string atts;
+ for (auto& a : t->_attributes)
+ {
+ if (atts != "")
+ atts += ' ';
+
+ atts += a.first + "='\033[33m" + a.second + "\033[0m'";
+ }
+
+ if (atts.length ())
+ out << ' ' << atts;
+
+ // Dump tags.
+ std::string tags;
+ for (auto& tag : t->_tags)
+ {
+ if (tags.length ())
+ tags += ' ';
+
+ tags += "\033[32m" + tag + "\033[0m";
+ }
+
+ if (tags.length ())
+ out << ' ' << tags;
+ out << '\n';
+
+ // Recurse for branches.
+ for (auto& b : t->_branches)
+ out << dumpNode (b, depth + 1);
+
+ return out.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string Tree::dump () const
+{
+ std::stringstream out;
+ out << "Tree (" << count () << " nodes)\n"
+ << dumpNode (std::make_shared <Tree> (*this), 1);
+
+ return out.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// Copyright 2010 - 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_TREE
+#define INCLUDED_TREE
+
+#include <map>
+#include <vector>
+#include <string>
+#include <memory>
+
+class Tree;
+
+class Tree
+{
+public:
+ void addBranch (std::shared_ptr <Tree>);
+ void removeBranch (std::shared_ptr <Tree>);
+ void removeAllBranches ();
+ void replaceBranch (std::shared_ptr <Tree>, std::shared_ptr <Tree>);
+
+ void attribute (const std::string&, const std::string&);
+ void attribute (const std::string&, const int);
+ void attribute (const std::string&, const double);
+ std::string attribute (const std::string&);
+ void removeAttribute (const std::string&);
+
+ void enumerate (std::vector <std::shared_ptr <Tree>>& all) const;
+
+ bool hasTag (const std::string&) const;
+ void tag (const std::string&);
+ void unTag (const std::string&);
+ int countTags () const;
+
+ int count () const;
+
+ std::shared_ptr <Tree> find (const std::string&);
+
+ std::string dump () const;
+
+private:
+ std::string dumpNode (const std::shared_ptr <Tree>, int) const;
+
+public:
+ std::string _name {"Unknown"}; // Name.
+ std::vector <std::shared_ptr <Tree>> _branches {}; // Children.
+ std::map <std::string, std::string> _attributes {}; // Attributes (name->value).
+ std::vector <std::string> _tags {}; // Tags (tag, tag ...).
+};
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <format.h>
+#include <utf8.h>
+#include <algorithm>
+#include <sstream>
+#include <iostream>
+#include <iomanip>
+#include <cctype>
+#include <strings.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/select.h>
+#include <time.h>
+#include <csignal>
+#include <cmath>
+
+////////////////////////////////////////////////////////////////////////////////
+const std::string format (std::string& value)
+{
+ return value;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+const std::string format (const char* value)
+{
+ std::string s (value);
+ return s;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+const std::string formatHex (int value)
+{
+ std::stringstream s;
+ s.setf (std::ios::hex, std::ios::basefield);
+ s << value;
+ return s.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+const std::string format (float value, int width, int precision)
+{
+ std::stringstream s;
+ s.width (width);
+ s.precision (precision);
+ if (0 < value && value < 1)
+ {
+ // For value close to zero, width - 2 (2 accounts for the first zero and
+ // the dot) is the number of digits after zero that are significant
+ double factor = 1;
+ for (int i = 2; i < width; i++)
+ factor *= 10;
+ value = roundf (value * factor) / factor;
+ }
+ s << value;
+ return s.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+const std::string format (double value, int width, int precision)
+{
+ std::stringstream s;
+ s.width (width);
+ s.precision (precision);
+ if (0 < value && value < 1)
+ {
+ // For value close to zero, width - 2 (2 accounts for the first zero and
+ // the dot) is the number of digits after zero that are significant
+ double factor = 1;
+ for (int i = 2; i < width; i++)
+ factor *= 10;
+ value = round (value * factor) / factor;
+ }
+ s << value;
+ return s.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+const std::string format (double value)
+{
+ std::stringstream s;
+ s << std::fixed << value;
+ return s.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void replace_positional (
+ std::string& fmt,
+ const std::string& from,
+ const std::string& to)
+{
+ std::string::size_type pos = 0;
+ while ((pos = fmt.find (from, pos)) != std::string::npos)
+ {
+ fmt.replace (pos, from.length (), to);
+ pos += to.length ();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string leftJustify (const int input, const int width)
+{
+ std::stringstream s;
+ s << input;
+ std::string output = s.str ();
+ return output + std::string (width - output.length (), ' ');
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string leftJustify (const std::string& input, const int width)
+{
+ return input + std::string (width - utf8_text_width (input), ' ');
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string rightJustifyZero (const int input, const int width)
+{
+ std::stringstream s;
+ s << std::setw (width) << std::setfill ('0') << input;
+ return s.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string rightJustify (const int input, const int width)
+{
+ std::stringstream s;
+ s << std::setw (width) << std::setfill (' ') << input;
+ return s.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string rightJustify (const std::string& input, const int width)
+{
+ unsigned int len = utf8_text_width (input);
+ return (((unsigned int) width > len)
+ ? std::string (width - len, ' ')
+ : "")
+ + input;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string commify (const std::string& data)
+{
+ // First scan for decimal point and end of digits.
+ int decimalPoint = -1;
+ int end = -1;
+
+ int i;
+ for (int i = 0; i < (int) data.length (); ++i)
+ {
+ if (isdigit (data[i]))
+ end = i;
+
+ if (data[i] == '.')
+ decimalPoint = i;
+ }
+
+ std::string result;
+ if (decimalPoint != -1)
+ {
+ // In reverse order, transfer all digits up to, and including the decimal
+ // point.
+ for (i = (int) data.length () - 1; i >= decimalPoint; --i)
+ result += data[i];
+
+ int consecutiveDigits = 0;
+ for (; i >= 0; --i)
+ {
+ if (isdigit (data[i]))
+ {
+ result += data[i];
+
+ if (++consecutiveDigits == 3 && i && isdigit (data[i - 1]))
+ {
+ result += ',';
+ consecutiveDigits = 0;
+ }
+ }
+ else
+ result += data[i];
+ }
+ }
+ else
+ {
+ // In reverse order, transfer all digits up to, but not including the last
+ // digit.
+ for (i = (int) data.length () - 1; i > end; --i)
+ result += data[i];
+
+ int consecutiveDigits = 0;
+ for (; i >= 0; --i)
+ {
+ if (isdigit (data[i]))
+ {
+ result += data[i];
+
+ if (++consecutiveDigits == 3 && i && isdigit (data[i - 1]))
+ {
+ result += ',';
+ consecutiveDigits = 0;
+ }
+ }
+ else
+ result += data[i];
+ }
+ }
+
+ // reverse result into data.
+ std::string done;
+ for (int i = (int) result.length () - 1; i >= 0; --i)
+ done += result[i];
+
+ return done;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Convert a quantity in bytes to a more readable format.
+std::string formatBytes (size_t bytes)
+{
+ char formatted[24];
+
+ if (bytes >= 995000000) sprintf (formatted, "%.1f GiB", bytes / 1000000000.0);
+ else if (bytes >= 995000) sprintf (formatted, "%.1f MiB", bytes / 1000000.0);
+ else if (bytes >= 995) sprintf (formatted, "%.1f KiB", bytes / 1000.0);
+ else sprintf (formatted, "%d B", (int)bytes);
+
+ return commify (formatted);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Convert a quantity in seconds to a more readable format.
+std::string formatTime (time_t seconds)
+{
+ char formatted[24];
+ float days = (float) seconds / 86400.0;
+
+ if (seconds >= 86400 * 365) sprintf (formatted, "%.1f y", (days / 365.0));
+ else if (seconds >= 86400 * 84) sprintf (formatted, "%1d mo", (int) (days / 30));
+ else if (seconds >= 86400 * 13) sprintf (formatted, "%d wk", (int) (float) (days / 7.0));
+ else if (seconds >= 86400) sprintf (formatted, "%d d", (int) days);
+ else if (seconds >= 3600) sprintf (formatted, "%d h", (int) (seconds / 3600));
+ else if (seconds >= 60) sprintf (formatted, "%d m", (int) (seconds / 60));
+ else if (seconds >= 1) sprintf (formatted, "%d s", (int) seconds);
+ else strcpy (formatted, "-");
+
+ return std::string (formatted);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string printable (const std::string& input)
+{
+ // Sanitize 'message'.
+ std::string sanitized = input;
+ std::string::size_type bad;
+ while ((bad = sanitized.find ("\r")) != std::string::npos)
+ sanitized.replace (bad, 1, "\\r");
+
+ while ((bad = sanitized.find ("\n")) != std::string::npos)
+ sanitized.replace (bad, 1, "\\n");
+
+ while ((bad = sanitized.find ("\f")) != std::string::npos)
+ sanitized.replace (bad, 1, "\\f");
+
+ while ((bad = sanitized.find ("\t")) != std::string::npos)
+ sanitized.replace (bad, 1, "\\t");
+
+ while ((bad = sanitized.find ("\v")) != std::string::npos)
+ sanitized.replace (bad, 1, "\\v");
+
+ return sanitized;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string printable (char input)
+{
+ // Sanitize 'message'.
+ char stringized[2] = {0};
+ stringized[0] = input;
+
+ std::string sanitized = stringized;
+ switch (input)
+ {
+ case '\r': sanitized = "\\r"; break;
+ case '\n': sanitized = "\\n"; break;
+ case '\f': sanitized = "\\f"; break;
+ case '\t': sanitized = "\\t"; break;
+ case '\v': sanitized = "\\v"; break;
+ default: sanitized = input; break;
+ }
+
+ return sanitized;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Iterate over the input, converting text to 'x'.
+// Does not modify color codes.
+std::string obfuscateText (const std::string& input)
+{
+ std::stringstream output;
+ std::string::size_type i = 0;
+ int character;
+ bool inside = false;
+
+ while ((character = utf8_next_char (input, i)))
+ {
+ if (inside)
+ {
+ output << (char) character;
+
+ if (character == 'm')
+ inside = false;
+ }
+ else
+ {
+ if (character == 033)
+ inside = true;
+
+ if (inside || character == ' ')
+ output << (char) character;
+ else
+ output << 'x';
+ }
+ }
+
+ return output.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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_FORMAT
+#define INCLUDED_FORMAT
+
+#include <sstream>
+#include <algorithm>
+#include <string>
+#include <vector>
+
+const std::string format (std::string&);
+const std::string format (const char*);
+const std::string formatHex (int);
+const std::string format (float, int, int);
+const std::string format (double, int, int);
+const std::string format (double);
+
+void replace_positional (std::string&, const std::string&, const std::string&);
+
+template<typename T>
+const std::string format (T value)
+{
+ std::stringstream s;
+ s << value;
+ return s.str ();
+}
+
+template<typename T>
+const std::string format (int fmt_num, const std::string& fmt, T arg)
+{
+ std::string output = fmt;
+ replace_positional (output, '{' + format (fmt_num) + '}', format (arg));
+ return output;
+}
+
+template<typename T, typename... Args>
+const std::string format (int fmt_num, const std::string& fmt, T arg, Args... args)
+{
+ const std::string fmt_replaced (format (fmt_num, fmt, arg));
+ return format (fmt_num+1, fmt_replaced, args...);
+}
+
+template<typename... Args>
+const std::string format (const std::string& fmt, Args... args)
+{
+ return format (1, fmt, args...);
+}
+
+std::string leftJustify (const int, const int);
+std::string leftJustify (const std::string&, const int);
+std::string rightJustifyZero (const int, const int);
+std::string rightJustify (const int, const int);
+std::string rightJustify (const std::string&, const int);
+
+std::string commify (const std::string&);
+std::string formatBytes (size_t);
+std::string formatTime (time_t);
+std::string printable (const std::string&);
+std::string printable (char);
+
+std::string obfuscateText (const std::string&);
+
+#endif
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <shared.h>
+#include <cctype>
+
+static bool isPort (const std::string&, unsigned int&);
+static bool isChar (const std::string&, char, unsigned int&);
+static bool isEOS (const std::string&, unsigned int&);
+static bool isIPv4Block (const std::string&, unsigned int&);
+static bool isIPv4BlockSet (const std::string&, unsigned int&);
+static bool isIPv6Block (const std::string&, unsigned int&);
+static bool isIPv6BlockSet (const std::string&, unsigned int&);
+
+////////////////////////////////////////////////////////////////////////////////
+static bool isPort (const std::string& input, unsigned int& c)
+{
+ auto start = c;
+ while (std::isdigit (input[c]))
+ ++c;
+
+ return c - start > 0 &&
+ c - start < 6;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+static bool isChar (const std::string& input, char character, unsigned int& c)
+{
+ if (input[c] == character)
+ {
+ ++c;
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+static bool isEOS (const std::string& input, unsigned int& c)
+{
+ return c >= input.length ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+static bool isIPv4Block (const std::string& input, unsigned int& c)
+{
+ auto start = c;
+ while (std::isdigit (input[c]))
+ ++c;
+
+ if (c - start > 0 &&
+ c - start < 4)
+ {
+ auto byte = std::stoi (input.substr (start, c - start));
+ if (byte < 256)
+ return true;
+ }
+
+ c = start;
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+static bool isIPv4BlockSet (const std::string& input, unsigned int& c)
+{
+ auto start = c;
+
+ if (isIPv4Block (input, c) &&
+ isChar (input, '.', c) &&
+ isIPv4Block (input, c) &&
+ isChar (input, '.', c) &&
+ isIPv4Block (input, c) &&
+ isChar (input, '.', c) &&
+ isIPv4Block (input, c))
+ {
+
+ return true;
+ }
+
+ c = start;
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+static bool isIPv6Block (const std::string& input, unsigned int& c)
+{
+ auto start = c;
+ while (std::isxdigit (input[c]))
+ ++c;
+
+ if (c - start > 0 &&
+ c - start < 5)
+ {
+ return true;
+ }
+
+ c = start;
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// at least one non-empty block, or '::'
+// at least two colons
+// 8 or less blocks
+static bool isIPv6BlockSet (const std::string& input, unsigned int& c)
+{
+ auto start = c;
+ int count_colons {0};
+ int count_blocks {0};
+
+ while (1)
+ {
+ if (isEOS (input, c))
+ {
+ if (c == start)
+ return false;
+
+ break;
+ }
+
+ else if (isChar (input, ':', c))
+ ++count_colons;
+
+ else if (isIPv4BlockSet (input, c))
+ ++count_blocks;
+
+ else if (isIPv6Block (input, c))
+ ++count_blocks;
+
+ else if (isChar (input, '.', c) ||
+ isChar (input, ']', c))
+ {
+ --c;
+ break;
+ }
+
+ else
+ break;
+ }
+
+ if (count_colons >= 2 &&
+ count_colons <= 7 &&
+ ((count_blocks == 0 && input.substr (start, c) == "::") || count_blocks >= 1) &&
+ count_blocks <= 8)
+ {
+ return true;
+ }
+
+ c = start;
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// <address>:<port>
+// <address>
+bool isIPv4Address (const std::string& input, std::string& address, int& port)
+{
+ unsigned int c = 0;
+ if (isIPv4BlockSet (input, c))
+ {
+ auto colon = c;
+ if (isChar (input, ':', c))
+ if (! isPort (input, c))
+ return false;
+
+ if (isEOS (input, c))
+ {
+ address = input.substr (0, std::min (c, colon));
+ if (! isEOS (input, colon))
+ port = std::stoi (input.substr (colon + 1));
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// [<address>]:<port>
+// <address>
+bool isIPv6Address (const std::string& input, std::string& address, int& port)
+{
+ unsigned int c = 0;
+
+ if (isChar (input, '[', c) &&
+ isIPv6BlockSet (input, c) &&
+ isChar (input, ']', c))
+ {
+ auto colon = c;
+ if (isChar (input, ':', c) &&
+ isPort (input, c) &&
+ isEOS (input, c))
+ {
+ address = input.substr (1, colon - 2);
+ port = std::stoi (input.substr (colon + 1));
+ return true;
+ }
+ }
+
+ c = 0;
+ if (isIPv6BlockSet (input, c) &&
+ isEOS (input, c))
+ {
+ address = input;
+ port = 0;
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+
+#include <iostream>
+#include <Lexer.h>
+
+int main (int argc, char** argv)
+{
+ for (auto i = 1; i < argc; i++)
+ {
+ std::cout << "argument '" << argv[i] << "'\n";
+
+ Lexer l (argv[i]);
+ std::string token;
+ Lexer::Type type;
+ while (l.token (token, type))
+ std::cout << " token '" << token << "' " << Lexer::typeToString (type) << "\n";
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <shared.h>
+#include <utf8.h>
+#include <algorithm>
+#include <sstream>
+#include <iostream>
+#include <iomanip>
+#include <cctype>
+#include <strings.h>
+#include <unistd.h>
+#include <sys/select.h>
+#include <cerrno>
+#include <csignal>
+#include <cmath>
+#include <cstring>
+#include <sys/wait.h>
+#include <format.h>
+
+///////////////////////////////////////////////////////////////////////////////
+void wrapText (
+ std::vector <std::string>& lines,
+ const std::string& text,
+ const int width,
+ bool hyphenate)
+{
+ std::string line;
+ unsigned int offset = 0;
+ while (extractLine (line, text, width, hyphenate, offset))
+ lines.push_back (line);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Split in a separator. Two adjacent separators means empty token.
+std::vector <std::string> split (const std::string& input, const char delimiter)
+{
+ std::vector <std::string> results;
+ std::string::size_type start = 0;
+ std::string::size_type i;
+ while ((i = input.find (delimiter, start)) != std::string::npos)
+ {
+ results.push_back (input.substr (start, i - start));
+ start = i + 1;
+ }
+
+ if (input.length ())
+ results.push_back (input.substr (start));
+
+ return results;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Split on words. Adjacent separators collapsed.
+std::vector <std::string> split (const std::string& input)
+{
+ static std::string delims = " \t\n\f\r";
+ std::vector <std::string> results;
+
+ std::string::size_type start = 0;
+ std::string::size_type end;
+ while ((start = input.find_first_not_of (delims, start)) != std::string::npos)
+ {
+ if ((end = input.find_first_of (delims, start)) != std::string::npos)
+ {
+ results.push_back (input.substr (start, end - start));
+ start = end;
+ }
+ else
+ {
+ results.push_back (input.substr (start));
+ start = std::string::npos;
+ }
+ }
+
+ return results;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string join (
+ const std::string& separator,
+ const std::vector<int>& items)
+{
+ std::stringstream s;
+ auto size = items.size ();
+ for (unsigned int i = 0; i < size; ++i)
+ {
+ if (i)
+ s << separator;
+
+ s << items[i];
+ }
+
+ return s.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string join (
+ const std::string& separator,
+ const std::vector<std::string>& items)
+{
+ std::stringstream s;
+ auto size = items.size ();
+ for (unsigned int i = 0; i < size; ++i)
+ {
+ if (i)
+ s << separator;
+
+ s << items[i];
+ }
+
+ return s.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string str_replace (
+ const std::string &str,
+ const std::string& search,
+ const std::string& replacement)
+{
+ std::string modified {str};
+ std::string::size_type pos = 0;
+ while ((pos = modified.find (search, pos)) != std::string::npos)
+ {
+ modified.replace (pos, search.length (), replacement);
+ pos += replacement.length ();
+ }
+
+ return modified;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string trim (const std::string& input, const std::string& edible)
+{
+ auto start = input.find_first_not_of (edible);
+ auto end = input.find_last_not_of (edible);
+
+ if (start == std::string::npos)
+ return "";
+
+ if (end == std::string::npos)
+ return input.substr (start);
+
+ return input.substr (start, end - start + 1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string ltrim (const std::string& input, const std::string& edible)
+{
+ auto start = input.find_first_not_of (edible);
+ if (start == std::string::npos)
+ return "";
+
+ return input.substr (start);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string rtrim (const std::string& input, const std::string& edible)
+{
+ if (input.find_first_not_of (edible) == std::string::npos)
+ return "";
+
+ auto end = input.find_last_not_of (edible);
+ if (end == std::string::npos)
+ return input;
+
+ return input.substr (0, end + 1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int longestWord (const std::string& input)
+{
+ int longest = 0;
+ int length = 0;
+ std::string::size_type i = 0;
+ int character;
+
+ while ((character = utf8_next_char (input, i)))
+ {
+ if (character == ' ')
+ {
+ if (length > longest)
+ longest = length;
+
+ length = 0;
+ }
+ else
+ length += mk_wcwidth (character);
+ }
+
+ if (length > longest)
+ longest = length;
+
+ return longest;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int longestLine (const std::string& input)
+{
+ int longest = 0;
+ int length = 0;
+ std::string::size_type i = 0;
+ int character;
+
+ while ((character = utf8_next_char (input, i)))
+ {
+ if (character == '\n')
+ {
+ if (length > longest)
+ longest = length;
+
+ length = 0;
+ }
+ else
+ length += mk_wcwidth (character);
+ }
+
+ if (length > longest)
+ longest = length;
+
+ return longest;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Walk the input text looking for a break point. A break point is one of:
+// - EOS
+// - \n
+// - last space before 'length' characters
+// - last punctuation (, ; . :) before 'length' characters, even if not
+// followed by a space
+// - first 'length' characters
+//
+// text "one two three\n four"
+// bytes 0123456789012 3456789
+// characters 1234567890a23 4567890
+//
+// leading_ws
+// ws ^ ^ ^^
+// punct
+// break ^
+bool extractLine (
+ std::string& line,
+ const std::string& text,
+ int width,
+ bool hyphenate,
+ unsigned int& offset)
+{
+ // Terminate processing.
+ // Note: bytes vs bytes.
+ if (offset >= text.length ())
+ return false;
+
+ std::string::size_type last_last_bytes = offset;
+ std::string::size_type last_bytes = offset;
+ std::string::size_type bytes = offset;
+ unsigned int last_ws = 0;
+ int character;
+ int char_width = 0;
+ int line_width = 0;
+ while (1)
+ {
+ last_last_bytes = last_bytes;
+ last_bytes = bytes;
+ character = utf8_next_char (text, bytes);
+
+ if (character == 0 ||
+ character == '\n')
+ {
+ line = text.substr (offset, last_bytes - offset);
+ offset = bytes;
+ break;
+ }
+ else if (character == ' ')
+ last_ws = last_bytes;
+
+ char_width = mk_wcwidth (character);
+ if (line_width + char_width > width)
+ {
+ int last_last_character = text[last_last_bytes];
+ int last_character = text[last_bytes];
+
+ // [case 1] one| two --> last_last != 32, last == 32, ws == 0
+ if (last_last_character != ' ' &&
+ last_character == ' ')
+ {
+ line = text.substr (offset, last_bytes - offset);
+ offset = last_bytes + 1;
+ break;
+ }
+
+ // [case 2] one |two --> last_last == 32, last != 32, ws != 0
+ else if (last_last_character == ' ' &&
+ last_character != ' ' &&
+ last_ws != 0)
+ {
+ line = text.substr (offset, last_bytes - offset - 1);
+ offset = last_bytes;
+ break;
+ }
+
+ else if (last_last_character != ' ' &&
+ last_character != ' ')
+ {
+ // [case 3] one t|wo --> last_last != 32, last != 32, ws != 0
+ if (last_ws != 0)
+ {
+ line = text.substr (offset, last_ws - offset);
+ offset = last_ws + 1;
+ break;
+ }
+ // [case 4] on|e two --> last_last != 32, last != 32, ws == 0
+ else
+ {
+ if (hyphenate)
+ {
+ line = text.substr (offset, last_bytes - offset - 1) + '-';
+ offset = last_last_bytes;
+ }
+ else
+ {
+ line = text.substr (offset, last_bytes - offset);
+ offset = last_bytes;
+ }
+ }
+
+ break;
+ }
+ }
+
+ line_width += char_width;
+ }
+
+ return true;
+}
+/*
+
+TODO Resolve above against below, which is from Taskwarrior 2.6.0, and known to
+ be wrong.
+////////////////////////////////////////////////////////////////////////////////
+// Break UTF8 text into chunks no more than width characters.
+bool extractLine (
+ std::string& line,
+ const std::string& text,
+ int width,
+ bool hyphenate,
+ unsigned int& offset)
+{
+ // Terminate processing.
+ if (offset >= text.length ())
+ return false;
+
+ int line_length {0};
+ int character {0};
+ std::string::size_type lastWordEnd {std::string::npos};
+ bool something {false};
+ std::string::size_type cursor {offset};
+ std::string::size_type prior_cursor {offset};
+ while ((character = utf8_next_char (text, cursor)))
+ {
+ // Premature EOL.
+ if (character == '\n')
+ {
+ line = text.substr (offset, line_length);
+ offset = cursor;
+ return true;
+ }
+
+ if (! Lexer::isWhitespace (character))
+ {
+ something = true;
+ if (! text[cursor] || Lexer::isWhitespace (text[cursor]))
+ lastWordEnd = prior_cursor;
+ }
+
+ line_length += mk_wcwidth (character);
+
+ if (line_length >= width)
+ {
+ // Backtrack to previous word end.
+ if (lastWordEnd != std::string::npos)
+ {
+ // Eat one WS after lastWordEnd.
+ std::string::size_type lastBreak = lastWordEnd;
+ utf8_next_char (text, lastBreak);
+
+ // Position offset at following char.
+ std::string::size_type nextStart = lastBreak;
+ utf8_next_char (text, nextStart);
+
+ line = text.substr (offset, lastBreak - offset);
+ offset = nextStart;
+ return true;
+ }
+
+ // No backtrack, possible hyphenation.
+ else if (hyphenate)
+ {
+ line = text.substr (offset, prior_cursor - offset) + '-';
+ offset = prior_cursor;
+ return true;
+ }
+
+ // No hyphenation, just truncation.
+ else
+ {
+ line = text.substr (offset, cursor - offset);
+ offset = cursor;
+ return true;
+ }
+ }
+
+ // Hindsight.
+ prior_cursor = cursor;
+ }
+
+ // Residual text.
+ if (something)
+ {
+ line = text.substr (offset, cursor - offset);
+ offset = cursor;
+ return true;
+ }
+
+ return false;
+}
+*/
+
+////////////////////////////////////////////////////////////////////////////////
+bool compare (
+ const std::string& left,
+ const std::string& right,
+ bool sensitive /*= true*/)
+{
+ // Use strcasecmp if required.
+ if (! sensitive)
+ return strcasecmp (left.c_str (), right.c_str ()) == 0 ? true : false;
+
+ // Otherwise, just use std::string::operator==.
+ return left == right;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool closeEnough (
+ const std::string& reference,
+ const std::string& attempt,
+ unsigned int minLength /* = 0 */)
+{
+ // An exact match is accepted first.
+ if (compare (reference, attempt, false))
+ return true;
+
+ // A partial match will suffice.
+ if (attempt.length () < reference.length () &&
+ attempt.length () >= minLength)
+ return compare (reference.substr (0, attempt.length ()), attempt, false);
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int matchLength (
+ const std::string& left,
+ const std::string& right)
+{
+ int pos = 0;
+ while (left[pos] &&
+ right[pos] &&
+ left[pos] == right[pos])
+ ++pos;
+
+ return pos;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string::size_type find (
+ const std::string& text,
+ const std::string& pattern,
+ bool sensitive)
+{
+ return find (text, pattern, 0, sensitive);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string::size_type find (
+ const std::string& text,
+ const std::string& pattern,
+ std::string::size_type begin,
+ bool sensitive)
+{
+ // Implement a sensitive find, which is really just a loop withing a loop,
+ // comparing lower-case versions of each character in turn.
+ if (!sensitive)
+ {
+ // Handle empty pattern.
+ const char* p = pattern.c_str ();
+ size_t len = pattern.length ();
+ if (len == 0)
+ return 0;
+
+ // Handle bad begin.
+ if (begin >= text.length ())
+ return std::string::npos;
+
+ // Evaluate these once, for performance reasons.
+ const char* start = text.c_str ();
+ const char* t = start + begin;
+ const char* end = start + text.size ();
+
+ for (; t <= end - len; ++t)
+ {
+ int diff = 0;
+ for (size_t i = 0; i < len; ++i)
+ if ((diff = tolower (t[i]) - tolower (p[i])))
+ break;
+
+ // diff == 0 means there was no break from the loop, which only occurs
+ // when a difference is detected. Therefore, the loop terminated, and
+ // diff is zero.
+ if (diff == 0)
+ return t - start;
+ }
+
+ return std::string::npos;
+ }
+
+ // Otherwise, just use std::string::find.
+ return text.find (pattern, begin);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string lowerCase (const std::string& input)
+{
+ std::string output {input};
+ std::transform (output.begin (), output.end (), output.begin (), tolower);
+ return output;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string upperCase (const std::string& input)
+{
+ std::string output {input};
+ std::transform (output.begin (), output.end (), output.begin (), toupper);
+ return output;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string upperCaseFirst (const std::string& input)
+{
+ std::string output {input};
+ output[0] = toupper (output[0]);
+ return output;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int autoComplete (
+ const std::string& partial,
+ const std::vector<std::string>& list,
+ std::vector<std::string>& matches,
+ int minimum/* = 1*/)
+{
+ matches.clear ();
+
+ // Handle trivial case.
+ unsigned int length = partial.length ();
+ if (length)
+ {
+ for (auto& item : list)
+ {
+ // An exact match is a special case. Assume there is only one exact match
+ // and return immediately.
+ if (partial == item)
+ {
+ matches.clear ();
+ matches.push_back (item);
+ return 1;
+ }
+
+ // Maintain a list of partial matches.
+ else if (length >= (unsigned) minimum &&
+ length <= item.length () &&
+ partial == item.substr (0, length))
+ matches.push_back (item);
+ }
+ }
+
+ return matches.size ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Uses std::getline, because std::cin eats leading whitespace, and that means
+// that if a newline is entered, std::cin eats it and never returns from the
+// "std::cin >> answer;" line, but it does display the newline. This way, with
+// std::getline, the newline can be detected, and the prompt re-written.
+static void signal_handler (int s)
+{
+ if (s == SIGINT)
+ {
+ std::cout << "\n\nInterrupted: No changes made.\n";
+ exit (1);
+ }
+}
+
+bool confirm (const std::string& question)
+{
+ std::vector <std::string> options {"yes", "no"};
+ std::vector <std::string> matches;
+
+ signal (SIGINT, signal_handler);
+
+ do
+ {
+ std::cout << question
+ << " (yes/no) ";
+
+ std::string answer {""};
+ std::getline (std::cin, answer);
+ answer = std::cin.eof () ? "no" : lowerCase (trim (answer));
+
+ autoComplete (answer, options, matches, 1); // Hard-coded 1.
+ }
+ while (! std::cin.eof () && matches.size () != 1);
+
+ signal (SIGINT, SIG_DFL);
+ return matches.size () == 1 && matches[0] == "yes" ? true : false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Run a binary with args, capturing output.
+int execute (
+ const std::string& executable,
+ const std::vector <std::string>& args,
+ const std::string& input,
+ std::string& output)
+{
+ pid_t pid;
+ int pin[2], pout[2];
+ fd_set rfds, wfds;
+ struct timeval tv;
+ int select_retval, read_retval, write_retval;
+ char buf[16384];
+ unsigned int written;
+ const char* input_cstr = input.c_str ();
+
+ if (signal (SIGPIPE, SIG_IGN) == SIG_ERR) // Handled locally with EPIPE.
+ throw std::string (std::strerror (errno));
+
+ if (pipe (pin) == -1)
+ throw std::string (std::strerror (errno));
+
+ if (pipe (pout) == -1)
+ throw std::string (std::strerror (errno));
+
+ if ((pid = fork ()) == -1)
+ throw std::string (std::strerror (errno));
+
+ if (pid == 0)
+ {
+ // This is only reached in the child
+ close (pin[1]); // Close the write end of the input pipe.
+ close (pout[0]); // Close the read end of the output pipe.
+
+ // Parent writes to pin[1]. Set read end pin[0] as STDIN for child.
+ if (dup2 (pin[0], STDIN_FILENO) == -1)
+ throw std::string (std::strerror (errno));
+ close (pin[0]);
+
+ // Parent reads from pout[0]. Set write end pout[1] as STDOUT for child.
+ if (dup2 (pout[1], STDOUT_FILENO) == -1)
+ throw std::string (std::strerror (errno));
+ close (pout[1]);
+
+ // Add executable as argv[0] and NULL-terminate the array for execvp().
+ char** argv = new char* [args.size () + 2];
+ argv[0] = (char*) executable.c_str ();
+ for (unsigned int i = 0; i < args.size (); ++i)
+ argv[i+1] = (char*) args[i].c_str ();
+
+ argv[args.size () + 1] = NULL;
+ _exit (execvp (executable.c_str (), argv));
+ }
+
+ // This is only reached in the parent
+ close (pin[0]); // Close the read end of the input pipe.
+ close (pout[1]); // Close the write end of the output pipe.
+
+ if (input.size () == 0)
+ {
+ // Nothing to send to the child, close the pipe early.
+ close (pin[1]);
+ }
+
+ output = "";
+ read_retval = -1;
+ written = 0;
+ while (read_retval != 0 || input.size () != written)
+ {
+ FD_ZERO (&rfds);
+ if (read_retval != 0)
+ FD_SET (pout[0], &rfds);
+
+ FD_ZERO (&wfds);
+ if (input.size () != written)
+ FD_SET (pin[1], &wfds);
+
+ // On Linux, tv may be overwritten by select(). Reset it each time.
+ // NOTE: Timeout chosen arbitrarily - we don't time out execute() calls.
+ // select() is run over and over again unless the child exits or closes
+ // its pipes.
+ tv.tv_sec = 5;
+ tv.tv_usec = 0;
+
+ select_retval = select (std::max (pout[0], pin[1]) + 1, &rfds, &wfds, NULL, &tv);
+
+ if (select_retval == -1)
+ throw std::string (std::strerror (errno));
+
+ // Write data to child's STDIN
+ if (FD_ISSET (pin[1], &wfds))
+ {
+ write_retval = write (pin[1], input_cstr + written, input.size () - written);
+ if (write_retval == -1)
+ {
+ if (errno == EPIPE)
+ {
+ // Child died (or closed the pipe) before reading all input.
+ // We don't really care; pretend we wrote it all.
+ write_retval = input.size () - written;
+ }
+ else
+ {
+ throw std::string (std::strerror (errno));
+ }
+ }
+ written += write_retval;
+
+ if (written == input.size ())
+ {
+ // Let the child know that no more input is coming by closing the pipe.
+ close (pin[1]);
+ }
+ }
+
+ // Read data from child's STDOUT
+ if (FD_ISSET (pout[0], &rfds))
+ {
+ read_retval = read (pout[0], &buf, sizeof (buf) - 1);
+ if (read_retval == -1)
+ throw std::string (std::strerror (errno));
+
+ buf[read_retval] = '\0';
+ output += buf;
+ }
+ }
+
+ close (pout[0]); // Close the read end of the output pipe.
+
+ int status = -1;
+ if (wait (&status) == -1)
+ throw std::string (std::strerror (errno));
+
+ if (WIFEXITED (status))
+ {
+ status = WEXITSTATUS (status);
+ }
+ else
+ {
+ throw std::string ("Error: Could not get Hook exit status!");
+ }
+
+ if (signal (SIGPIPE, SIG_DFL) == SIG_ERR) // We're done, return to default.
+ throw std::string (std::strerror (errno));
+
+ return status;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string osName ()
+{
+#if defined (DARWIN)
+ return "Darwin";
+#elif defined (SOLARIS)
+ return "Solaris";
+#elif defined (CYGWIN)
+ return "Cygwin";
+#elif defined (HAIKU)
+ return "Haiku";
+#elif defined (OPENBSD)
+ return "OpenBSD";
+#elif defined (FREEBSD)
+ return "FreeBSD";
+#elif defined (NETBSD)
+ return "NetBSD";
+#elif defined (LINUX)
+ return "Linux";
+#elif defined (KFREEBSD)
+ return "GNU/kFreeBSD";
+#elif defined (GNUHURD)
+ return "GNU/Hurd";
+#else
+ return "<unknown>";
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 16.8 Predefined macro names [cpp.predefined]
+//
+// The following macro names shall be defined by the implementation:
+//
+// __cplusplus
+// The name __cplusplus is defined to the value 201402L when compiling a C++
+// translation unit.156
+//
+// ---
+// 156) It is intended that future versions of this standard will replace the
+// value of this macro with a greater value. Non-conforming compilers should
+// use a value with at most five decimal digits.
+std::string cppCompliance ()
+{
+#ifdef __cplusplus
+ auto level = __cplusplus;
+
+ if (level == 199711) return "C++98/03";
+ else if (level == 201103) return "C++11";
+ else if (level == 201402) return "C++14";
+
+ // This is a hack. Replace with correct value on standard publication.
+ else if (level > 201700) return "C++17";
+
+ // Unknown, just show the value.
+ else if (level > 99999) return format (__cplusplus);
+#endif
+
+ // No C++.
+ return "non-compliant";
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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_SHARED
+#define INCLUDED_SHARED
+
+#include <sstream>
+#include <algorithm>
+#include <string>
+#include <vector>
+
+// shared.cpp, Non-UTF-8 aware.
+void wrapText (std::vector <std::string>&, const std::string&, const int, bool);
+int longestWord (const std::string&);
+int longestLine (const std::string&);
+bool extractLine (std::string&, const std::string&, int, bool, unsigned int&);
+std::vector <std::string> split (const std::string&, const char);
+std::vector <std::string> split (const std::string&);
+std::string join (const std::string&, const std::vector<int>&);
+std::string join (const std::string&, const std::vector<std::string>&);
+std::string str_replace (const std::string&, const std::string&, const std::string&);
+std::string trim (const std::string&, const std::string& edible = " \t\n\f\r");
+std::string ltrim (const std::string&, const std::string& edible = " \t\n\f\r");
+std::string rtrim (const std::string&, const std::string& edible = " \t\n\f\r");
+bool compare (const std::string&, const std::string&, bool sensitive = true);
+bool closeEnough (const std::string&, const std::string&, unsigned int minLength = 0);
+int matchLength (const std::string&, const std::string&);
+std::string::size_type find (const std::string&, const std::string&, bool sensitive = true);
+std::string::size_type find (const std::string&, const std::string&, std::string::size_type, bool sensitive = true);
+
+// List operations.
+template <class T> void listDiff (
+ const T& left, const T& right, T& leftOnly, T& rightOnly)
+{
+ leftOnly.clear ();
+ for (auto& l : left)
+ if (std::find (right.begin (), right.end (), l) == right.end ())
+ leftOnly.push_back (l);
+
+ rightOnly.clear ();
+ for (auto& r : right)
+ if (std::find (left.begin (), left.end (), r) == left.end ())
+ rightOnly.push_back (r);
+}
+
+template <class T> T listIntersect (
+ const T& left, const T& right)
+{
+ T intersection;
+ for (auto& l : left)
+ if (std::find (right.begin (), right.end (), l) != right.end ())
+ intersection.push_back (l);
+
+ return intersection;
+}
+
+std::string lowerCase (const std::string&);
+std::string upperCase (const std::string&);
+std::string upperCaseFirst (const std::string&);
+
+int autoComplete (const std::string&, const std::vector<std::string>&, std::vector<std::string>&, int minimum = 1);
+bool confirm (const std::string&);
+
+int execute (const std::string&, const std::vector <std::string>&, const std::string&, std::string&);
+std::string osName ();
+std::string cppCompliance ();
+
+// ip.cpp
+bool isIPv4Address (const std::string&, std::string&, int&);
+bool isIPv6Address (const std::string&, std::string&, int&);
+
+#endif
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <unicode.h>
+#include <cwctype>
+
+////////////////////////////////////////////////////////////////////////////////
+// Complete Unicode whitespace list.
+bool unicodeWhitespace (unsigned int c)
+{
+ return unicodeHorizontalWhitespace (c) ||
+ unicodeVerticalWhitespace (c);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Complete Unicode whitespace list.
+//
+// http://en.wikipedia.org/wiki/Whitespace_character
+// Updated 2015-09-13
+// Static
+//
+// TODO This list should be derived from the Unicode database.
+bool unicodeHorizontalWhitespace (unsigned int c)
+{
+ return (c == 0x0020 || // space Common Separator, space
+ c == 0x0009 || // Common Other, control HT, Horizontal Tab
+ c == 0x00A0 || // no-break space Common Separator, space
+ c == 0x1680 || // ogham space mark Ogham Separator, space
+ c == 0x180E || // mongolian vowel separator Mongolian Separator, space
+ c == 0x2000 || // en quad Common Separator, space
+ c == 0x2001 || // em quad Common Separator, space
+ c == 0x2002 || // en space Common Separator, space
+ c == 0x2003 || // em space Common Separator, space
+ c == 0x2004 || // three-per-em space Common Separator, space
+ c == 0x2005 || // four-per-em space Common Separator, space
+ c == 0x2006 || // six-per-em space Common Separator, space
+ c == 0x2007 || // figure space Common Separator, space
+ c == 0x2008 || // punctuation space Common Separator, space
+ c == 0x2009 || // thin space Common Separator, space
+ c == 0x200A || // hair space Common Separator, space
+ c == 0x200B || // zero width space
+ c == 0x200C || // zero width non-joiner
+ c == 0x200D || // zero width joiner
+ c == 0x202F || // narrow no-break space Common Separator, space
+ c == 0x205F || // medium mathematical space Common Separator, space
+ c == 0x2060 || // word joiner
+ c == 0x3000); // ideographic space Common Separator, space
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Complete Unicode whitespace list.
+//
+// http://en.wikipedia.org/wiki/Whitespace_character
+// Updated 2015-09-13
+// Static
+//
+// TODO This list should be derived from the Unicode database.
+bool unicodeVerticalWhitespace (unsigned int c)
+{
+ return (c == 0x000A || // Common Other, control LF, Line feed
+ c == 0x000B || // Common Other, control VT, Vertical Tab
+ c == 0x000C || // Common Other, control FF, Form feed
+ c == 0x000D || // Common Other, control CR, Carriage return
+ c == 0x0085 || // Common Other, control NEL, Next line
+ c == 0x2028 || // line separator Common Separator, line
+ c == 0x2029); // paragraph separator Common Separator, paragraph
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool unicodePunctuation (unsigned int c)
+{
+ return iswpunct (c) ? true : false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool unicodeAlpha (unsigned int c)
+{
+ return iswprint (c) &&
+ ! iswpunct (c) &&
+ ! unicodeWhitespace (c);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TODO Needs better definition.
+bool unicodeLatinAlpha (unsigned int c)
+{
+ return (c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z');
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Digits 0-9.
+//
+// TODO This list should be derived from the Unicode database.
+bool unicodeLatinDigit (unsigned int c)
+{
+ return c >= 0x30 && c <= 0x39;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Digits 0-9 a-f A-F.
+bool unicodeHexDigit (unsigned int c)
+{
+ return (c >= '0' && c <= '9') ||
+ (c >= 'a' && c <= 'f') ||
+ (c >= 'A' && c <= 'F');
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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_UNICODE
+#define INCLUDED_UNICODE
+
+bool unicodeWhitespace (unsigned int);
+bool unicodeHorizontalWhitespace (unsigned int);
+bool unicodeVerticalWhitespace (unsigned int);
+bool unicodePunctuation (unsigned int);
+bool unicodeAlpha (unsigned int);
+bool unicodeLatinAlpha (unsigned int);
+bool unicodeLatinDigit (unsigned int);
+bool unicodeHexDigit (unsigned int);
+
+#endif
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// Copyright 2013 - 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 <cmake.h>
+#include <utf8.h>
+
+////////////////////////////////////////////////////////////////////////////////
+// Converts '0' -> 0
+// '9' -> 9
+// 'a'/'A' -> 10
+// 'f'/'F' -> 15
+#define XDIGIT(x) ((x) >= '0' && (x) <= '9' ? ((x) - '0') : \
+ (x) >= 'a' && (x) <= 'f' ? ((x) + 10 - 'a') : \
+ (x) >= 'A' && (x) <= 'F' ? ((x) + 10 - 'A') : 0)
+
+////////////////////////////////////////////////////////////////////////////////
+// Note: Assumes 4-digit hex codepoints:
+// xxxx
+// \uxxxx
+// U+xxxx
+unsigned int utf8_codepoint (const std::string& input)
+{
+ unsigned int codepoint = 0;
+ int length = input.length ();
+
+ // U+xxxx, \uxxxx
+ if (length >= 6 &&
+ ((input[0] == 'U' && input[1] == '+') ||
+ (input[0] == '\\' && input[1] == 'u')))
+ {
+ codepoint = XDIGIT (input[2]) << 12 |
+ XDIGIT (input[3]) << 8 |
+ XDIGIT (input[4]) << 4 |
+ XDIGIT (input[5]);
+ }
+ else if (length >= 4)
+ {
+ codepoint = XDIGIT (input[0]) << 12 |
+ XDIGIT (input[1]) << 8 |
+ XDIGIT (input[2]) << 4 |
+ XDIGIT (input[3]);
+ }
+
+ return codepoint;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Iterates along a UTF8 string.
+// - argument i counts bytes advanced through the string
+// - returns the next character
+unsigned int utf8_next_char (const std::string& input, std::string::size_type& i)
+{
+ if (input[i] == '\0')
+ return 0;
+
+ // How many bytes in the sequence?
+ int length = utf8_sequence (input[i]);
+ i += length;
+
+ // 0xxxxxxx -> 0xxxxxxx
+ if (length == 1)
+ return input[i - 1];
+
+ // 110yyyyy 10xxxxxx -> 00000yyy yyxxxxxx
+ if (length == 2)
+ return ((input[i - 2] & 0x1F) << 6) +
+ (input[i - 1] & 0x3F);
+
+ // 1110zzzz 10yyyyyy 10xxxxxx -> zzzzyyyy yyxxxxxx
+ if (length == 3)
+ return ((input[i - 3] & 0xF) << 12) +
+ ((input[i - 2] & 0x3F) << 6) +
+ (input[i - 1] & 0x3F);
+
+ // 11110www 10zzzzzz 10yyyyyy 10xxxxxx -> 000wwwzz zzzzyyyy yyxxxxxx
+ if (length == 4)
+ return ((input[i - 4] & 0x7) << 18) +
+ ((input[i - 3] & 0x3F) << 12) +
+ ((input[i - 2] & 0x3F) << 6) +
+ (input[i - 1] & 0x3F);
+
+ // Default: pretend as though it's a single character.
+ // TODO Or should this throw?
+ return input[i - 1];
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// http://en.wikipedia.org/wiki/UTF-8
+std::string utf8_character (unsigned int codepoint)
+{
+ char sequence[5] {};
+
+ // 0xxxxxxx -> 0xxxxxxx
+ if (codepoint < 0x80)
+ {
+ sequence[0] = codepoint;
+ }
+
+ // 00000yyy yyxxxxxx -> 110yyyyy 10xxxxxx
+ else if (codepoint < 0x800)
+ {
+ sequence[0] = 0xC0 | (codepoint & 0x7C0) >> 6;
+ sequence[1] = 0x80 | (codepoint & 0x3F);
+ }
+
+ // zzzzyyyy yyxxxxxx -> 1110zzzz 10yyyyyy 10xxxxxx
+ else if (codepoint < 0x10000)
+ {
+ sequence[0] = 0xE0 | (codepoint & 0xF000) >> 12;
+ sequence[1] = 0x80 | (codepoint & 0xFC0) >> 6;
+ sequence[2] = 0x80 | (codepoint & 0x3F);
+ }
+
+ // 000wwwzz zzzzyyyy yyxxxxxx -> 11110www 10zzzzzz 10yyyyyy 10xxxxxx
+ else if (codepoint < 0x110000)
+ {
+ sequence[0] = 0xF0 | (codepoint & 0x1C0000) >> 18;
+ sequence[1] = 0x80 | (codepoint & 0x03F000) >> 12;
+ sequence[2] = 0x80 | (codepoint & 0x0FC0) >> 6;
+ sequence[3] = 0x80 | (codepoint & 0x3F);
+ }
+
+ return std::string (sequence);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int utf8_sequence (unsigned int character)
+{
+ if ((character & 0xE0) == 0xC0)
+ return 2;
+
+ if ((character & 0xF0) == 0xE0)
+ return 3;
+
+ if ((character & 0xF8) == 0xF0)
+ return 4;
+
+ return 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Length of a string in characters.
+unsigned int utf8_length (const std::string& str)
+{
+ int byteLength = str.length ();
+ int charLength = byteLength;
+ const char* data = str.data ();
+
+ // Decrement the number of bytes for each byte that matches 0b10??????
+ // this way only the first byte of any utf8 sequence is counted.
+ for (int i = 0; i < byteLength; i++)
+ {
+ // Extract the first two bits and check whether they are 10
+ if ((data[i] & 0xC0) == 0x80)
+ charLength--;
+ }
+
+ return charLength;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Width of a string in character cells.
+unsigned int utf8_width (const std::string& str)
+{
+ unsigned int length = 0;
+ std::string::size_type i = 0;
+ unsigned int c;
+ while ((c = utf8_next_char (str, i)))
+ {
+ // Control characters, and more especially newline characters, make
+ // mk_wcwidth() return -1. Ignore that, thereby "adding zero" to length.
+ // Since control characters are not displayed in reports, this is a valid
+ // choice.
+ int l = mk_wcwidth (c);
+ if (l != -1)
+ length += l;
+ }
+
+ return length;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+unsigned int utf8_text_length (const std::string& str)
+{
+ int byteLength = str.length ();
+ int charLength = byteLength;
+ const char* data = str.data ();
+ bool in_color = false;
+
+ // Decrement the number of bytes for each byte that matches 0b10??????
+ // this way only the first byte of any utf8 sequence is counted.
+ for (int i = 0; i < byteLength; i++)
+ {
+ if (in_color)
+ {
+ if (data[i] == 'm')
+ in_color = false;
+
+ --charLength;
+ }
+ else
+ {
+ if (data[i] == 033)
+ {
+ in_color = true;
+ --charLength;
+ }
+ else
+ {
+ // Extract the first two bits and check whether they are 10
+ if ((data[i] & 0xC0) == 0x80)
+ --charLength;
+ }
+ }
+ }
+
+ return charLength;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+unsigned int utf8_text_width (const std::string& str)
+{
+ bool in_color = false;
+
+ unsigned int length = 0;
+ std::string::size_type i = 0;
+ unsigned int c;
+ while ((c = utf8_next_char (str, i)))
+ {
+ if (in_color)
+ {
+ if (c == 'm')
+ in_color = false;
+ }
+ else if (c == 033)
+ {
+ in_color = true;
+ }
+ else
+ length += mk_wcwidth (c);
+ }
+
+ return length;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+const std::string utf8_substr (
+ const std::string& input,
+ unsigned int start,
+ unsigned int length /* = 0 */)
+{
+ // Find the starting index.
+ std::string::size_type index_start = 0;
+ for (unsigned int i = 0; i < start; i++)
+ utf8_next_char (input, index_start);
+
+ std::string result;
+ if (length)
+ {
+ std::string::size_type index_end = index_start;
+ for (unsigned int i = 0; i < length; i++)
+ utf8_next_char (input, index_end);
+
+ result = input.substr (index_start, index_end - index_start);
+ }
+ else
+ result = input.substr (index_start);
+
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// Copyright 2013 - 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_UTF8
+#define INCLUDED_UTF8
+
+#include <string>
+
+unsigned int utf8_codepoint (const std::string&);
+unsigned int utf8_next_char (const std::string&, std::string::size_type&);
+std::string utf8_character (unsigned int);
+int utf8_sequence (unsigned int);
+unsigned int utf8_length (const std::string&);
+unsigned int utf8_text_length (const std::string&);
+unsigned int utf8_width (const std::string& str);
+unsigned int utf8_text_width (const std::string&);
+const std::string utf8_substr (const std::string&, unsigned int, unsigned int length = 0);
+
+int mk_wcwidth (wchar_t);
+
+#endif
--- /dev/null
+/*
+ * This is an implementation of wcwidth() and wcswidth() (defined in
+ * IEEE Std 1002.1-2001) for Unicode.
+ *
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
+ *
+ * In fixed-width output devices, Latin characters all occupy a single
+ * "cell" position of equal width, whereas ideographic CJK characters
+ * occupy two such cells. Interoperability between terminal-line
+ * applications and (teletype-style) character terminals using the
+ * UTF-8 encoding requires agreement on which character should advance
+ * the cursor by how many cell positions. No established formal
+ * standards exist at present on which Unicode character shall occupy
+ * how many cell positions on character terminals. These routines are
+ * a first attempt of defining such behavior based on simple rules
+ * applied to data provided by the Unicode Consortium.
+ *
+ * For some graphical characters, the Unicode standard explicitly
+ * defines a character-cell width via the definition of the East Asian
+ * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
+ * In all these cases, there is no ambiguity about which width a
+ * terminal shall use. For characters in the East Asian Ambiguous (A)
+ * class, the width choice depends purely on a preference of backward
+ * compatibility with either historic CJK or Western practice.
+ * Choosing single-width for these characters is easy to justify as
+ * the appropriate long-term solution, as the CJK practice of
+ * displaying these characters as double-width comes from historic
+ * implementation simplicity (8-bit encoded characters were displayed
+ * single-width and 16-bit ones double-width, even for Greek,
+ * Cyrillic, etc.) and not any typographic considerations.
+ *
+ * Much less clear is the choice of width for the Not East Asian
+ * (Neutral) class. Existing practice does not dictate a width for any
+ * of these characters. It would nevertheless make sense
+ * typographically to allocate two character cells to characters such
+ * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
+ * represented adequately with a single-width glyph. The following
+ * routines at present merely assign a single-cell width to all
+ * neutral characters, in the interest of simplicity. This is not
+ * entirely satisfactory and should be reconsidered before
+ * establishing a formal standard in this area. At the moment, the
+ * decision which Not East Asian (Neutral) characters should be
+ * represented by double-width glyphs cannot yet be answered by
+ * applying a simple rule from the Unicode database content. Setting
+ * up a proper standard for the behavior of UTF-8 character terminals
+ * will require a careful analysis not only of each Unicode character,
+ * but also of each presentation form, something the author of these
+ * routines has avoided to do so far.
+ *
+ * http://www.unicode.org/unicode/reports/tr11/
+ *
+ * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * for any purpose and without fee is hereby granted. The author
+ * disclaims all warranties with regard to this software.
+ *
+ * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
+ */
+
+#include <cmake.h>
+#include <wchar.h>
+
+struct interval {
+ int first;
+ int last;
+};
+
+/* auxiliary function for binary search in interval table */
+static int bisearch(wchar_t ucs, const struct interval *table, int max) {
+ int min = 0;
+ int mid;
+
+ if (ucs < table[0].first || ucs > table[max].last)
+ return 0;
+ while (max >= min) {
+ mid = (min + max) / 2;
+ if (ucs > table[mid].last)
+ min = mid + 1;
+ else if (ucs < table[mid].first)
+ max = mid - 1;
+ else
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* The following two functions define the column width of an ISO 10646
+ * character as follows:
+ *
+ * - The null character (U+0000) has a column width of 0.
+ *
+ * - Other C0/C1 control characters and DEL will lead to a return
+ * value of -1.
+ *
+ * - Non-spacing and enclosing combining characters (general
+ * category code Mn or Me in the Unicode database) have a
+ * column width of 0.
+ *
+ * - SOFT HYPHEN (U+00AD) has a column width of 1.
+ *
+ * - Other format characters (general category code Cf in the Unicode
+ * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
+ *
+ * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
+ * have a column width of 0.
+ *
+ * - Spacing characters in the East Asian Wide (W) or East Asian
+ * Full-width (F) category as defined in Unicode Technical
+ * Report #11 have a column width of 2.
+ *
+ * - All remaining characters (including all printable
+ * ISO 8859-1 and WGL4 characters, Unicode control characters,
+ * etc.) have a column width of 1.
+ *
+ * This implementation assumes that wchar_t characters are encoded
+ * in ISO 10646.
+ */
+
+int mk_wcwidth(wchar_t ucs)
+{
+ /* sorted list of non-overlapping intervals of non-spacing characters */
+ /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
+ static const struct interval combining[] = {
+ { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
+ { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
+ { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
+ { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
+ { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
+ { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
+ { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
+ { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
+ { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
+ { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
+ { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
+ { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
+ { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
+ { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
+ { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
+ { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
+ { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
+ { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
+ { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
+ { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
+ { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
+ { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
+ { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
+ { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
+ { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
+ { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
+ { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
+ { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
+ { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
+ { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
+ { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
+ { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
+ { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
+ { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
+ { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
+ { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
+ { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
+ { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
+ { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
+ { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
+ { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
+ { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
+ { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB },
+ { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F },
+ { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 },
+ { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },
+ { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F },
+ { 0xE0100, 0xE01EF }
+ };
+
+ /* test for 8-bit control characters */
+ if (ucs == 0)
+ return 0;
+ if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
+ return -1;
+
+ /* binary search in table of non-spacing characters */
+ if (bisearch(ucs, combining,
+ sizeof(combining) / sizeof(struct interval) - 1))
+ return 0;
+
+ /* if we arrive here, ucs is not a combining or C0/C1 control character */
+
+ return 1 +
+ (ucs >= 0x1100 &&
+ (ucs <= 0x115f || /* Hangul Jamo init. consonants */
+ ucs == 0x2329 || ucs == 0x232a ||
+ (ucs >= 0x2e80 && ucs <= 0xa4cf &&
+ ucs != 0x303f) || /* CJK ... Yi */
+ (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
+ (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
+ (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */
+ (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
+ (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
+ (ucs >= 0xffe0 && ucs <= 0xffe6)
+#ifndef CYGWIN
+ ||
+ (ucs >= 0x20000 && ucs <= 0x2fffd) ||
+ (ucs >= 0x30000 && ucs <= 0x3fffd)
+#endif
+ )
+ );
+}
+
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <iostream>
+#include <vector>
+#include <string>
+#include <cstring>
+#include <cstdio>
+#include <stdlib.h>
+#include <unistd.h>
+#include <shared.h>
+
+#ifdef HAVE_READLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
+
+// TODO These conflict with tw commands. This needs to be resolved.
+// Perhaps an escape, such as '-- help' could invoke local help, or using
+// a 'task' prefix could disambiguate.
+
+// tasksh commands.
+int cmdHelp ();
+int cmdDiagnostics ();
+int cmdReview (const std::vector <std::string>&, bool);
+int cmdShell (const std::vector <std::string>&);
+std::string promptCompose ();
+std::string findTaskwarrior ();
+
+////////////////////////////////////////////////////////////////////////////////
+static void welcome ()
+{
+ std::cout << PACKAGE_STRING << "\n";
+ cmdHelp ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+const std::string getResponse (const std::string& prompt)
+{
+ std::string response {""};
+
+ // Display prompt, get input.
+#ifdef HAVE_READLINE
+ char *line_read = readline (prompt.c_str ());
+ if (! line_read)
+ {
+ std::cout << "\n";
+ response = "<EOF>";
+ }
+ else
+ {
+ // Save history.
+ if (*line_read)
+ add_history (line_read);
+
+ response = std::string (line_read);
+ free (line_read);
+ }
+#else
+ std::cout << prompt;
+ std::getline (std::cin, response);
+ if (std::cin.eof () == 1)
+ {
+ std::cout << "\n";
+ response = "<EOF>";
+ }
+#endif
+
+ return response;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+static int commandLoop (bool autoClear)
+{
+ // Compose the prompt.
+ auto prompt = promptCompose ();
+
+ // Display prompt, get input.
+ auto command = getResponse (prompt);
+
+ // Obey Taskwarrior's rc.tasksh.autoclear.
+ if (autoClear)
+ std::cout << "\033[2J\033[0;0H";
+
+ int status = 0;
+ if (! isatty (fileno (stdin)) && command == "")
+ {
+ status = -1;
+ }
+ else if (command != "")
+ {
+ auto args = split (command, ' ');
+
+ // Dispatch command.
+ if (args[0] == "<EOF>") status = -1;
+ else if (closeEnough ("exit", args[0], 3)) status = -1;
+ else if (closeEnough ("quit", args[0], 3)) status = -1;
+ else if (closeEnough ("help", args[0], 3)) status = cmdHelp ();
+ else if (closeEnough ("diagnostics", args[0], 3)) status = cmdDiagnostics ();
+ else if (closeEnough ("review", args[0], 3)) status = cmdReview (args, autoClear);
+ else if (closeEnough ("exec", args[0], 3) ||
+ args[0][0] == '!') status = cmdShell (args);
+ else if (command != "")
+ {
+ command = "task " + command;
+ std::cout << "[" << command << "]\n";
+ system (command.c_str ());
+
+ // Deliberately ignoreѕ taskwarrior exit status, otherwise empty filters
+ // cause the shell to terminate.
+ }
+ }
+
+ return status;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int main (int argc, const char** argv)
+{
+ int status = 0;
+
+ // Lightweight version checking that doesn't require initialization or any I/O.
+ if (argc == 2 && !strcmp (argv[1], "--version"))
+ {
+ std::cout << VERSION << "\n";
+ }
+ else
+ {
+ try
+ {
+ // Get the Taskwarrior rc.tasksh.autoclear Boolean setting.
+ bool autoClear = false;
+ std::string input;
+ std::string output;
+ execute ("task", {"_get", "rc.tasksh.autoclear"}, input, output);
+ output = lowerCase (output);
+ autoClear = (output == "true\n" ||
+ output == "1\n" ||
+ output == "y\n" ||
+ output == "yes\n" ||
+ output == "on\n");
+
+ if (isatty (fileno (stdin)))
+ welcome ();
+
+ while ((status = commandLoop (autoClear)) == 0)
+ ;
+ }
+
+ catch (const std::string& error)
+ {
+ std::cerr << error << "\n";
+ status = -1;
+ }
+
+ catch (...)
+ {
+ std::cerr << "Unknown error." << "\n";
+ status = -2;
+ }
+ }
+
+ // Returning -1 drops out of the command loop, but gets translated to 0 here,
+ // so that there is a clean way to exit.
+ return status == -1 ? 0 : status;
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <vector>
+#include <string>
+#include <Color.h>
+
+static std::vector <std::string> contextColors = {
+ "bold white on red",
+ "bold white on blue",
+ "bold white on green",
+ "bold white on magenta",
+ "black on cyan",
+ "black on yellow",
+ "black on white",
+};
+
+static std::vector <std::string> contexts;
+
+std::string composeContexts (bool pretty = false);
+
+////////////////////////////////////////////////////////////////////////////////
+int promptClear ()
+{
+ contexts.clear ();
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int promptRemove ()
+{
+ if (contexts.size ())
+ contexts.pop_back ();
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int promptAdd (const std::string& context)
+{
+ contexts.push_back (context);
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string composeContexts (bool pretty /* = false */)
+{
+ std::string combined;
+ for (unsigned int i = 0; i < contexts.size (); i++)
+ if (pretty)
+ combined += (combined != "" ? " " : "")
+ + std::string ("\001")
+ + Color::colorize ("\002 " + contexts[i] + " \001", contextColors[i % contextColors.size ()])
+ + "\002";
+ else
+ combined += (combined != "" ? " " : "") + contexts[i];
+
+ if (combined != "")
+ combined += ' ';
+
+ return combined;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string promptCompose ()
+{
+ // TODO The prompt may be composed of different elements:
+ // TODO - The configurable text
+ // TODO - The accumulated context, as colored tokens.
+ // TODO - sync status
+ // TODO - time
+ auto decoration = composeContexts (true);
+ if (decoration.length ())
+ return "task " + decoration + "> ";
+
+ return "tasksh> ";
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <iostream>
+#include <sstream>
+#include <vector>
+#include <string>
+#include <algorithm>
+#include <stdlib.h>
+
+#ifdef HAVE_READLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
+
+#include <unistd.h>
+#include <sys/ioctl.h>
+
+#ifdef SOLARIS
+#include <sys/termios.h>
+#endif
+
+#include <Color.h>
+#include <Lexer.h>
+#include <shared.h>
+#include <format.h>
+
+std::string getResponse (const std::string&);
+
+////////////////////////////////////////////////////////////////////////////////
+static unsigned int getWidth ()
+{
+ // Determine window size.
+// int width = config.getInteger ("defaultwidth");
+ static auto width = 0;
+
+ if (width == 0)
+ {
+ unsigned short buff[4];
+ if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &buff) != -1)
+ width = buff[1];
+ }
+
+ return width;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+static void editTask (const std::string& uuid)
+{
+ std::string command = "task rc.confirmation:no rc.verbose:nothing " + uuid + " edit";
+ system (command.c_str ());
+
+ command = "task rc.confirmation:no rc.verbose:nothing " + uuid + " modify reviewed:now";
+ system (command.c_str ());
+ std::cout << "Modified.\n\n\n\n";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+static void modifyTask (const std::string& uuid)
+{
+ Color text ("color15 on gray6");
+ std::string modifications;
+ do
+ {
+ modifications = getResponse (text.colorize (" Enter modification args [example: +tag -tag /teh/the/ project:X] ") + " ");
+ }
+ while (modifications == "");
+
+ std::string command = "task rc.confirmation:no rc.verbose:nothing " + uuid + " modify " + modifications;
+ system (command.c_str ());
+
+ std::cout << "Modified.\n\n\n\n";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+static void reviewTask (const std::string& uuid)
+{
+ std::string command = "task rc.confirmation:no rc.verbose:nothing " + uuid + " modify reviewed:now";
+ system (command.c_str ());
+ std::cout << "Marked as reviewed.\n\n\n\n";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+static void completeTask (const std::string& uuid)
+{
+ std::string command = "task rc.confirmation:no rc.verbose:nothing " + uuid + " done";
+ system (command.c_str ());
+ std::cout << "Completed.\n\n\n\n";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+static void deleteTask (const std::string& uuid)
+{
+ std::string command = "task rc.confirmation:no rc.verbose:nothing " + uuid + " delete";
+ system (command.c_str ());
+ std::cout << "Deleted.\n\n\n\n";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+static const std::string reviewNothing ()
+{
+ return "\nThere are no tasks needing review.\n\n";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+static const std::string reviewStart (
+ unsigned int width)
+{
+ std::string welcome = "The review process is important for keeping your list "
+ "accurate, so you are working on the right tasks.\n"
+ "\n"
+ "For each task you are shown, look at the metadata. "
+ "Determine whether the task needs to be changed (enter "
+ "'e' to edit), or whether it is accurate ('enter' or "
+ "'r' to mark as reviewed). You may skip a task ('s') "
+ "but a skipped task is not considered reviewed.\n"
+ "\n"
+ "You may stop at any time, and resume later right "
+ "where you left off. See 'man tasksh' for more details.";
+
+ std::vector <std::string> lines;
+ wrapText (lines, welcome, width, false);
+ welcome = join ("\n", lines);
+
+ return "\n" + welcome + "\n\n";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+static const std::string banner (
+ unsigned int current,
+ unsigned int total,
+ unsigned int width,
+ const std::string& message)
+{
+ std::stringstream progress;
+ progress << " ["
+ << current
+ << " of "
+ << total
+ << "] ";
+
+ Color progressColor ("color15 on color9");
+ Color descColor ("color15 on gray6");
+
+ std::string composed;
+ if (progress.str ().length () + message.length () + 1 < width)
+ composed = progressColor.colorize (progress.str ()) +
+ descColor.colorize (" " + message +
+ std::string (width - progress.str ().length () - message.length () - 1, ' '));
+ else
+ composed = progressColor.colorize (progress.str ()) +
+ descColor.colorize (" " + message.substr (0, message.length () - 3) + "...");
+
+ return composed + "\n";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+static const std::string menu ()
+{
+ return Color ("color15 on gray6").colorize (" (Enter) Mark as reviewed, (s)kip, (e)dit, (m)odify, (c)omplete, (d)elete, (q)uit ") + " ";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+static void reviewLoop (const std::vector <std::string>& uuids, unsigned int limit, bool autoClear)
+{
+ auto width = getWidth ();
+ unsigned int reviewed = 0;
+
+ // If a limit was specified ('review 10'), then it should override the data
+ // set size, if it is smaller.
+ unsigned int total = uuids.size ();
+ if (limit)
+ total = std::min (total, limit);
+
+ if (total == 0)
+ {
+ std::cout << reviewNothing ();
+ return;
+ }
+
+ std::cout << reviewStart (width);
+
+ unsigned int current = 0;
+ while (current < total &&
+ (limit == 0 || reviewed < limit))
+ {
+ // Run 'info' report for task.
+ auto uuid = uuids[current];
+
+ // Display banner for this task.
+ std::string dummy;
+ std::string description;
+ execute ("task",
+ {"_get", uuid + ".description"},
+ dummy,
+ description);
+
+ std::string response;
+ bool repeat;
+ do
+ {
+ repeat = false;
+ std::cout << banner (current + 1, total, width, Lexer::trimRight (description, "\n"));
+
+ // Use 'system' to run the command and show the output.
+ std::string command = "task " + uuid + " information";
+ system (command.c_str ());
+
+ // Display prompt, get input.
+ response = getResponse (menu ());
+
+ if (response == "e") { editTask (uuid); }
+ else if (response == "m") { modifyTask (uuid); repeat = true; }
+ else if (response == "s") { std::cout << "Skipped\n\n"; ++current; }
+ else if (response == "c") { completeTask (uuid); ++current; ++reviewed; }
+ else if (response == "d") { deleteTask (uuid); ++current; ++reviewed; }
+ else if (response == "") { reviewTask (uuid); ++current; ++reviewed; }
+ else if (response == "r") { reviewTask (uuid); ++current; ++reviewed; }
+ else if (response == "q") { break; }
+
+ else
+ {
+ std::cout << format ("Command '{1}' is not recognized.", response) << "\n";
+ }
+
+ // Note that just hitting <Enter> yields an empty command, which does
+ // nothing but advance to the next task.
+
+ if (autoClear)
+ std::cout << "\033[2J\033[0;0H";
+ }
+ while (repeat);
+
+ if (response == "q")
+ break;
+ }
+
+ std::cout << "\n"
+ << format ("End of review. {1} out of {2} tasks reviewed.", reviewed, total)
+ << "\n\n";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int cmdReview (const std::vector <std::string>& args, bool autoClear)
+{
+ // Is there a specified limit?
+ unsigned int limit = 0;
+ if (args.size () == 2)
+ limit = strtol (args[1].c_str (), NULL, 10);
+
+ // Configure 'reviewed' UDA, but only if necessary.
+ std::string input;
+ std::string output;
+ auto status = execute ("task", {"_get", "rc.uda.reviewed.type"}, input, output);
+ if (status || output != "date\n")
+ {
+ if (confirm ("Tasksh needs to define a 'reviewed' UDA of type 'date' for all tasks. Ok to proceed?"))
+ {
+ execute ("task", {"rc.confirmation:no", "rc.verbose:nothing", "config", "uda.reviewed.type", "date"}, input, output);
+ execute ("task", {"rc.confirmation:no", "rc.verbose:nothing", "config", "uda.reviewed.label", "Reviewed"}, input, output);
+ }
+ }
+
+ // Configure '_reviewed' report, but only if necessary.
+ status = execute ("task", {"_get", "rc.report._reviewed.columns"}, input, output);
+ if (status || output != "uuid\n")
+ {
+ if (confirm ("Tasksh needs to define a '_reviewed' report to identify tasks needing review. Ok to proceed?"))
+ {
+ execute ("task", {"rc.confirmation:no", "rc.verbose:nothing", "config", "report._reviewed.description",
+ "Tasksh review report. Adjust the filter to your needs." }, input, output);
+ execute ("task", {"rc.confirmation:no", "rc.verbose:nothing", "config", "report._reviewed.columns", "uuid" }, input, output);
+ execute ("task", {"rc.confirmation:no", "rc.verbose:nothing", "config", "report._reviewed.sort", "reviewed+,modified+"}, input, output);
+ execute ("task", {"rc.confirmation:no", "rc.verbose:nothing", "config", "report._reviewed.filter",
+ "( reviewed.none: or reviewed.before:now-6days ) and ( +PENDING or +WAITING )" }, input, output);
+ }
+ }
+
+ // Obtain a list of UUIDs to review.
+ status = execute ("task",
+ {
+ "rc.color=off",
+ "rc.detection=off",
+ "rc._forcecolor=off",
+ "rc.verbose=nothing",
+ "_reviewed"
+ },
+ input, output);
+
+ // Review the set of UUIDs.
+ auto uuids = split (Lexer::trimRight (output, "\n"), '\n');
+ reviewLoop (uuids, limit, autoClear);
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 <cmake.h>
+#include <vector>
+#include <string>
+#include <stdlib.h>
+#include <shared.h>
+
+////////////////////////////////////////////////////////////////////////////////
+int cmdShell (const std::vector <std::string>& args)
+{
+ auto combined = join (" ", args);
+
+ // Support '!ls' as well as '! ls'.
+ if (combined[0] == '!')
+ combined = combined.substr (1);
+
+ system (combined.c_str ());
+ return 0; // Ignore system return code.
+}
+
+////////////////////////////////////////////////////////////////////////////////