From 527b17d9643d2674a567eab1bdd4a419b1fe290e Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 4 Jul 2015 20:45:37 -0400 Subject: [PATCH 01/61] Update submodules --- depends/jsonxx | 2 +- library/xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depends/jsonxx b/depends/jsonxx index 1d1adf4ea..9a0b18acb 160000 --- a/depends/jsonxx +++ b/depends/jsonxx @@ -1 +1 @@ -Subproject commit 1d1adf4ea438fdcc0da108f6c9bd2a250fbd3f58 +Subproject commit 9a0b18acb0967bfc523dca7af2cb34030d97c02b diff --git a/library/xml b/library/xml index 53c424e7d..0cdcec939 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 53c424e7d891aa6828c3690ad0a3d8d9248dbc3f +Subproject commit 0cdcec939d7c41b5a0f9dda766cd742ccaa0c70b From a8bea3684ae2965029c8b5a36ab2aa380c440890 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 4 Jul 2015 23:39:06 -0400 Subject: [PATCH 02/61] Update NEWS --- NEWS | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index 13911f39c..4dd16cada 100644 --- a/NEWS +++ b/NEWS @@ -3,9 +3,12 @@ DFHack Future A method for caching screen output is now available to Lua (and C++) Developer plugins can be ignored on startup by setting the DFHACK_NO_DEV_PLUGINS environment variable The console on Linux and OS X now recognizes keyboard input between prompts + JSON libraries available (C++ and Lua) + More build information available in plugins Lua Scripts can be enabled with the built-in enable/disable commands A new function, reqscript(), is available as a safer alternative to script_environment() + Lua viewscreens can choose not to intercept the OPTIONS keybinding New internal commands kill-lua: Interrupt running Lua scripts New plugins @@ -13,7 +16,6 @@ DFHack Future burial: sets all unowned coffins to allow burial ("-pets" to allow pets too) fix-ster: changes fertility/sterility of animals or dwarves view-item-info: adds information and customisable descriptions to item viewscreens - item-descriptions: holds a default description for every item type and subtype warn-starving: check for starving, thirsty, or very drowsy units and pause with warning if any are found New tweaks kitchen-keys: Fixes DF kitchen meal keybindings @@ -23,23 +25,42 @@ DFHack Future Plugins with vmethod hooks can now be reloaded on OS X Lua's os.system() now works on OS X Fixed default arguments in Lua gametype detection functions + Circular lua dependencies (reqscript/script_environment) fixed + Prevented crash in Items::createItem() gui/hack-wish now properly assigns quality to items. gui/gm-editor handles lua tables properly search: fixed crash in unit list after cancelling a job - steam-engine: fixed a crash on arena load + steam-engine: + - fixed a crash on arena load + - number keys (e.g. 2/8) take priority over cursor keys when applicable tweak fps-min fixed workflow: Fixed some issues with stuck jobs - - Note: Existing stuck jobs must be cancelled and re-added + - Note: Existing stuck jobs must be cancelled and re-added + zone: Fixed a crash in the unit list after cancelling a job (and several other potential crashes) Misc Improvements - dwarfmonitor widgets' positions, formats, etc. are now customizable (see Readme) - dwarfmonitor weather display now separated from the date display - dwarfmonitor: New mouse cursor widget + autolabor: + - Stopped modification of labors that shouldn't be modified for brokers/diplomats + - Prioritize skilled dwarves more efficiently + - Prevent dwarves from running away with tools from previous jobs + dwarfmonitor: + - widgets' positions, formats, etc. are now customizable (see Readme) + - weather display now separated from the date display + - New mouse cursor widget + full-heal: "-r" option removes corpses gui/gm-editor: Pointers can now be displaced gui/hack-wish: renamed to gui/create-item "keybinding list" accepts a context + memview: Fixed display issue nyan: Can now be stopped with dfhack-run quicksave: Restricted to fortress mode - search: Now supports the noble suggestion screen (e.g. suggesting a baron) + remotefortressreader: Exposes more information + search: + - Supports noble suggestion screen (e.g. suggesting a baron) + - Supports fortress mode loo[k] menu + - Recognizes ? and ; keys + teleport: Fixed cursor recognition + tweak: debug output now logged to stderr.log instead of console + workflow: Improved handling of plant reactions Removed DFHack 0.40.24-r3 From 237ce2c539cdbc5ee51caecff311bd5fbcb69767 Mon Sep 17 00:00:00 2001 From: Michon van Dooren Date: Wed, 15 Jul 2015 21:40:14 +0200 Subject: [PATCH 03/61] Extended lever.rb a bit Added names to the lever list, and `lever pull` without any arguments now pulls the lever under the cursor. --- scripts/lever.rb | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/scripts/lever.rb b/scripts/lever.rb index 18e4484cd..e679eaf31 100644 --- a/scripts/lever.rb +++ b/scripts/lever.rb @@ -32,7 +32,7 @@ def lever_descr(bld, idx=nil) # lever description descr = '' descr << "#{idx}: " if idx - descr << "lever ##{bld.id} @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}" + descr << "lever ##{bld.id} (#{bld.name}) @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}" bld.jobs.each { |j| if j.job_type == :PullLever flags = '' @@ -82,10 +82,15 @@ case $script_args[0] when 'pull' cheat = $script_args.delete('--cheat') || $script_args.delete('--now') - id = $script_args[1].to_i - id = @lever_list[id] || id - bld = df.building_find(id) - raise 'invalid lever id' if not bld + if $script_args[1].nil? + bld = df.building_find(:selected) if not bld + raise 'no lever under cursor and no lever id given' if not bld + else + id = $script_args[1].to_i + id = @lever_list[id] || id + bld = df.building_find(id) + raise 'invalid lever id' if not bld + end if cheat lever_pull_cheat(bld) @@ -111,7 +116,10 @@ Lever control from the dfhack console Usage: lever list - shows the list of levers in the fortress, with their id and links + shows the list of levers in the fortress, with their id, name and links + +lever pull + order the dwarves to pull the lever under the cursor lever pull 42 order the dwarves to pull lever 42 From 4a6bde35ab82ef23ee8903e473bc6ddbdab4c071 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 15 Jul 2015 16:19:58 -0400 Subject: [PATCH 04/61] Only display names of levers with names --- scripts/lever.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/lever.rb b/scripts/lever.rb index e679eaf31..6cbf38e6f 100644 --- a/scripts/lever.rb +++ b/scripts/lever.rb @@ -32,7 +32,9 @@ def lever_descr(bld, idx=nil) # lever description descr = '' descr << "#{idx}: " if idx - descr << "lever ##{bld.id} (#{bld.name}) @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}" + descr << "lever ##{bld.id} " + descr << "(#{bld.name}) " if bld.name.length != 0 + descr << "@[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}" bld.jobs.each { |j| if j.job_type == :PullLever flags = '' From 929e15f66adc19ff09fefcae55745bd277c934c0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 15 Jul 2015 16:21:22 -0400 Subject: [PATCH 05/61] Add MaienM to Contributors.rst See #652 --- Contributors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Contributors.rst b/Contributors.rst index 647f0554e..a3225fe5c 100644 --- a/Contributors.rst +++ b/Contributors.rst @@ -79,6 +79,7 @@ James Logsdon jlogsdon melkor217 melkor217 acwatkins acwatkins Wes Malone wesQ3 +Michon van Dooren MaienM ======================= ==================== =========================== And these are the cool people who made **Stonesense**. From 2afc1c6d3f3bbebd5db36cfdb1efb3a07e28358d Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 17 Jul 2015 13:48:49 -0400 Subject: [PATCH 06/61] Update xml viewscreen_choose_start_sitest fixes --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 0cdcec939..a56692ae6 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 0cdcec939d7c41b5a0f9dda766cd742ccaa0c70b +Subproject commit a56692ae635a1e58db8a2cad64bb03fe75122ddd From 797e9b44853f1eb48e39954743b2c855c68360e4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 17 Jul 2015 13:49:17 -0400 Subject: [PATCH 07/61] OS X: Prevent DF window from receiving input when not focused This partially fixes DFHack/stonesense#32, in the sense that input to Stonesense is no longer received by DF, but does not cause Stonesense to receive input either. --- library/Hooks-darwin.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/library/Hooks-darwin.cpp b/library/Hooks-darwin.cpp index fe5ca9c9e..e72372fd4 100644 --- a/library/Hooks-darwin.cpp +++ b/library/Hooks-darwin.cpp @@ -71,6 +71,16 @@ DYLD_INTERPOSE(DFH_SDL_NumJoysticks,SDL_NumJoysticks); /******************************************************************************* * SDL part starts here * *******************************************************************************/ + +#define SDL_APPMOUSEFOCUS 0x01 /**< The app has mouse coverage */ +#define SDL_APPINPUTFOCUS 0x02 /**< The app has input focus */ +#define SDL_APPACTIVE 0x04 /**< The application is active */ +static uint8_t (*_SDL_GetAppState)(void) = 0; +DFhackCExport uint8_t SDL_GetAppState(void) +{ + return _SDL_GetAppState(); +} + // hook - called for each game tick (or more often) DFhackCExport int DFH_SDL_NumJoysticks(void) { @@ -95,7 +105,7 @@ DFhackCExport int DFH_SDL_PollEvent(SDL::Event* event) pollevent_again: // if SDL returns 0 here, it means there are no more events. return 0 int orig_return = SDL_PollEvent(event); - if(!orig_return) + if(!orig_return || !(SDL_GetAppState() & SDL_APPINPUTFOCUS)) return 0; // otherwise we have an event to filter else if( event != 0 ) @@ -265,7 +275,6 @@ DFhackCExport int SDL_SemPost(vPtr sem) return _SDL_SemPost(sem); } - // hook - called at program start, initialize some stuffs we'll use later static int (*_SDL_Init)(uint32_t flags) = 0; DFhackCExport int DFH_SDL_Init(uint32_t flags) @@ -297,6 +306,7 @@ DFhackCExport int DFH_SDL_Init(uint32_t flags) bind(SDL_SemWait); bind(SDL_SemPost); + bind(SDL_GetAppState); #undef bind fprintf(stderr, "dfhack: saved real SDL functions\n"); From 74404c62e753165ca1c86a3d33201174fcd925bd Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 17 Jul 2015 16:00:41 -0400 Subject: [PATCH 08/61] Fix deadlock when aborting plugin load --- library/PluginManager.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 688c851fb..0d6442699 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -264,7 +264,6 @@ bool Plugin::load(color_ostream &con) return false; } *plug_self = this; - RefAutolock lock(access); plugin_init = (command_result (*)(color_ostream &, std::vector &)) LookupPlugin(plug, "plugin_init"); std::vector* plugin_globals = *((std::vector**) LookupPlugin(plug, "plugin_globals")); if (plugin_globals->size()) @@ -298,6 +297,7 @@ bool Plugin::load(color_ostream &con) commands.clear(); if(plugin_init(con,commands) == CR_OK) { + RefAutolock lock(access); state = PS_LOADED; parent->registerCommands(this); if ((plugin_onupdate || plugin_enable) && !plugin_is_enabled) @@ -311,8 +311,7 @@ bool Plugin::load(color_ostream &con) plugin_is_enabled = 0; plugin_onupdate = 0; reset_lua(); - ClosePlugin(plugin_lib); - state = PS_BROKEN; + plugin_abort_load; return false; } } From 22ea68e68ac6fcec430d4e7bf2550206ee2d391b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 18 Jul 2015 12:54:07 -0400 Subject: [PATCH 09/61] Use is_valid_enum_item in FOR_ENUM_ITEMS This is part of the workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=43680 Fixes DFHack/dfhack#654 Fixes JapaMala/armok-vision#21 --- library/include/DataDefs.h | 2 +- library/xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index 513ee5ff2..bccc9b3f5 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -700,7 +700,7 @@ namespace DFHack { #define ENUM_NEXT_ITEM(enum,val) \ (DFHack::next_enum_item(val)) #define FOR_ENUM_ITEMS(enum,iter) \ - for(df::enum iter = ENUM_FIRST_ITEM(enum); iter <= ENUM_LAST_ITEM(enum); iter = df::enum(1+int(iter))) + for(df::enum iter = ENUM_FIRST_ITEM(enum); is_valid_enum_item(iter); iter = df::enum(1+int(iter))) /* * Include mandatory generated headers. diff --git a/library/xml b/library/xml index a56692ae6..ce779e70c 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit a56692ae635a1e58db8a2cad64bb03fe75122ddd +Subproject commit ce779e70cc51d843b85e4a31a8bef1024748b4af From 83f89480aabe05926195dd0703485d54e6a057bf Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 18 Jul 2015 14:10:53 -0400 Subject: [PATCH 10/61] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index ce779e70c..5c4d627d7 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit ce779e70cc51d843b85e4a31a8bef1024748b4af +Subproject commit 5c4d627d7306b8425493f8ebbfe66c0f347f6871 From fc094e2dbca2c352cf42a38048aed17db1f15b7e Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 21 Jul 2015 16:26:41 -0400 Subject: [PATCH 11/61] OS X: Check for existence of SDL.framework before building --- library/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 845f8dd07..16cf03d1a 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -298,6 +298,9 @@ SET_TARGET_PROPERTIES(dfhack PROPERTIES DEBUG_POSTFIX "-debug" ) IF(APPLE) SET(SDL_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/SDL.framework) + IF(NOT EXISTS ${SDL_LIBRARY}) + MESSAGE(FATAL_ERROR "SDL framework not found. Make sure CMAKE_INSTALL_PREFIX is specified and correct.") + ENDIF() SET(CXX_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/libstdc++.6.dylib) SET(ZIP_LIBRARY /usr/lib/libz.dylib) TARGET_LINK_LIBRARIES(dfhack ${SDL_LIBRARY}) From c34e9bb657c9fdefa7a6e46dd4c9c2c13705da20 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 22 Jul 2015 17:27:57 -0400 Subject: [PATCH 12/61] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 5c4d627d7..5850f66fa 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 5c4d627d7306b8425493f8ebbfe66c0f347f6871 +Subproject commit 5850f66fa5e37401b1536b574a48d645993520e2 From ccb3b34c475598208c3016037144f97d6b9382c4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 23 Jul 2015 16:16:59 -0400 Subject: [PATCH 13/61] Update NEWS --- NEWS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NEWS b/NEWS index 4dd16cada..fdcf9fb51 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ DFHack Future The console on Linux and OS X now recognizes keyboard input between prompts JSON libraries available (C++ and Lua) More build information available in plugins + Fixed a rare overflow issue that could cause crashes on Linux and OS X + Stopped DF window from receiving input when unfocused on OS X Lua Scripts can be enabled with the built-in enable/disable commands A new function, reqscript(), is available as a safer alternative to script_environment() @@ -50,6 +52,9 @@ DFHack Future gui/gm-editor: Pointers can now be displaced gui/hack-wish: renamed to gui/create-item "keybinding list" accepts a context + lever: + - Lists lever names + - "lever pull" can be used to pull the currently-selected lever memview: Fixed display issue nyan: Can now be stopped with dfhack-run quicksave: Restricted to fortress mode From 27295a9b6ff4b90134f2f6710fc10c8b56f1ae8a Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 23 Jul 2015 23:24:00 -0400 Subject: [PATCH 14/61] Log DFHack and save-specific information on world (un)load events --- library/Core.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/library/Core.cpp b/library/Core.cpp index 11af466ce..29644f535 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -73,6 +73,7 @@ using namespace DFHack; #include #include #include "tinythread.h" +#include "md5wrapper.h" #include "SDL_events.h" @@ -1755,6 +1756,68 @@ void Core::handleLoadAndUnloadScripts(color_ostream& out, state_change_event eve void Core::onStateChange(color_ostream &out, state_change_event event) { + using df::global::gametype; + static md5wrapper md5w; + static std::string ostype = ""; + + if (!ostype.size()) + { + ostype = "unknown OS"; + if (vinfo) { + switch (vinfo->getOS()) + { + case OS_WINDOWS: + ostype = "Windows"; + break; + case OS_APPLE: + ostype = "OS X"; + break; + case OS_LINUX: + ostype = "Linux"; + break; + default: + break; + } + } + } + + switch (event) + { + case SC_WORLD_LOADED: + case SC_WORLD_UNLOADED: + case SC_MAP_LOADED: + case SC_MAP_UNLOADED: + if (world && world->cur_savegame.save_dir.size()) + { + std::string evtlogpath = "data/save/" + world->cur_savegame.save_dir + "/events-dfhack.log"; + std::ofstream evtlog; + evtlog.open(evtlogpath, std::ios_base::app); // append + if (evtlog.fail()) + { + out.printerr("Could not append to %s\n", evtlogpath.c_str()); + } + else + { + char timebuf[30]; + time_t rawtime = time(NULL); + struct tm * timeinfo = localtime(&rawtime); + strftime(timebuf, sizeof(timebuf), "[%Y-%m-%dT%H:%M:%S%z] ", timeinfo); + evtlog << timebuf; + evtlog << "DFHack " << Version::git_description() << " on " << ostype << "; "; + evtlog << "cwd md5: " << md5w.getHashFromString(getHackPath()).substr(0, 10) << "; "; + evtlog << "save: " << world->cur_savegame.save_dir << "; "; + evtlog << sc_event_name(event) << "; "; + if (gametype) + evtlog << "game type " << ENUM_KEY_STR(game_type, *gametype) << " (" << *gametype << ")"; + else + evtlog << "game type unavailable"; + evtlog << std::endl; + } + } + default: + break; + } + EventManager::onStateChange(out, event); buildings_onStateChange(out, event); From 03aa2399a0bf0a0078c6c4fd9447ad504a1282c8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 23 Jul 2015 23:25:01 -0400 Subject: [PATCH 15/61] Add environment variables to skip global and vtable initialization --- library/VersionInfoFactory.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/VersionInfoFactory.cpp b/library/VersionInfoFactory.cpp index 1b66b583a..2336b8d6c 100644 --- a/library/VersionInfoFactory.cpp +++ b/library/VersionInfoFactory.cpp @@ -81,6 +81,8 @@ VersionInfo * VersionInfoFactory::getVersionInfoByPETimestamp(uint32_t timestamp void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) { + bool no_vtables = getenv("DFHACK_NO_VTABLES"); + bool no_globals = getenv("DFHACK_NO_GLOBALS"); TiXmlElement* pMemEntry; const char *cstr_name = entry->Attribute("name"); if (!cstr_name) @@ -136,6 +138,8 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) cerr << "Dummy symbol table entry: " << cstr_key << endl; continue; } + if ((is_vtable && no_vtables) || (!is_vtable && no_globals)) + continue; uint32_t addr = strtol(cstr_value, 0, 0); if (is_vtable) mem->setVTable(cstr_key, addr); From bbe2002a28266505758cbd3db48156d62aad4397 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 24 Jul 2015 10:49:17 -0400 Subject: [PATCH 16/61] Update Compile.rst --- Compile.rst | 113 ++++++++++++++++++++++++++-------------------------- 1 file changed, 56 insertions(+), 57 deletions(-) diff --git a/Compile.rst b/Compile.rst index ce4449524..260af3b13 100644 --- a/Compile.rst +++ b/Compile.rst @@ -4,28 +4,43 @@ Building DFHACK .. contents:: - - -===== -Linux -===== -On Linux, DFHack acts as a library that shadows parts of the SDL API using LD_PRELOAD. - +=================== How to get the code =================== DFHack doesn't have any kind of system of code snapshots in place, so you will have to get code from the github repository using git. -Having a 'git' package installed is the minimal requirement, but some sort of git gui or git integration for your favorite text editor/IDE will certainly help. +The code resides here: https://github.com/DFHack/dfhack + +On Linux and OS X, having a 'git' package installed is the minimal requirement (see below for OS X instructions), +but some sort of git gui or git integration for your favorite text editor/IDE will certainly help. + +On Windows, you will need some sort of Windows port of git, or a GUI. Some examples: + + * http://msysgit.github.io/ - this is a command line version of git for windows. Most tutorials on git usage will apply. + * http://code.google.com/p/tortoisegit/ - this puts a pretty, graphical face on top of msysgit The code resides here: https://github.com/DFHack/dfhack -If you just want to compile DFHack or work on it by contributing patches, it's quite enough to clone from the read-only address:: +To get the code:: + + git clone --recursive https://github.com/DFHack/dfhack + cd dfhack + +If your version of git does not support the ``--recursive`` flag, you will need to omit it and run +``git submodule update --init`` after entering the dfhack directory. + +If you just want to compile DFHack or work on it by contributing patches, it's quite enough to clone from the read-only address instead:: - git clone git://github.com/DFHack/dfhack.git + git clone --recursive git://github.com/DFHack/dfhack.git cd dfhack - git submodule init - git submodule update -If you want to get really involved with the development, create an account on github, make a clone there and then use that as your remote repository instead. Detailed instructions are beyond the scope of this document. If you need help, join us on IRC (#dfhack channel on freenode). +The tortoisegit GUI should have the equivalent options included. + +If you want to get really involved with the development, create an account on Github, make a clone there and then use that as your remote repository instead. Detailed instructions are beyond the scope of this document. If you need help, join us on IRC (#dfhack channel on freenode). + +===== +Linux +===== +On Linux, DFHack acts as a library that shadows parts of the SDL API using LD_PRELOAD. Dependencies ============ @@ -46,7 +61,7 @@ To build Stonesense, you'll also need OpenGL headers. Build ===== -Building is fairly straightforward. Enter the ``build`` folder and start the build like this:: +Building is fairly straightforward. Enter the ``build`` folder (or create an empty folder in the DFHack directory to use instead) and start the build like this:: cd build cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=/home/user/DF @@ -72,8 +87,8 @@ Fixing the libstdc++ version bug When compiling dfhack yourself, it builds against your system libc. When Dwarf Fortress runs, it uses a libstdc++ shipped with the binary, which -is usually way older, and incompatible with your dfhack. This manifests with -the error message:: +comes from GCC 4.5 and is incompatible with code compiled with newer GCC versions. +This manifests itself with the error message:: ./libs/Dwarf_Fortress: /pathToDF/libs/libstdc++.so.6: version `GLIBCXX_3.4.15' not found (required by ./hack/libdfhack.so) @@ -84,18 +99,33 @@ to your system lib and everything will work fine:: cd /path/to/DF/ rm libs/libstdc++.so.6 +Alternatively, this issue can be avoided by compiling DFHack with GCC 4.5. + ======== Mac OS X ======== -If you are building on 10.6, please read the subsection below titled "Snow Leopard Changes" FIRST. +DFHack functions similarly on OS X and Linux, and the majority of the +information above regarding the build process (cmake and make) applies here +as well. + +* If you are building on 10.6, please read the subsection below titled "Snow Leopard Changes" FIRST. +* If you are building on 10.10+, read the "Yosemite Changes" subsection before building. 1. Download and unpack a copy of the latest DF 2. Install Xcode from Mac App Store 3. Open Xcode, go to Preferences > Downloads, and install the Command Line Tools. 4. Install dependencies - Option 1: Using MacPorts: + Option 1: Using Homebrew: + + * `Install Homebrew `_ and run: + * ``brew tap homebrew/versions`` + * ``brew install git`` + * ``brew install cmake`` + * ``brew install gcc45`` + + Option 2: Using MacPorts: * `Install MacPorts `_ * Run ``sudo port install gcc45 +universal cmake +universal git-core +universal`` @@ -103,14 +133,6 @@ If you are building on 10.6, please read the subsection below titled "Snow Leopa At some point during this process, it may ask you to install a Java environment; let it do so. - Option 2: Using Homebrew: - - * `Install Homebrew `_ and run: - * ``brew tap homebrew/versions`` - * ``brew install git`` - * ``brew install cmake`` - * ``brew install gcc45 --enable-multilib`` - 5. Install perl dependencies 1. ``sudo cpan`` @@ -123,23 +145,21 @@ If you are building on 10.6, please read the subsection below titled "Snow Leopa 6. Get the dfhack source:: - git clone git://github.com/DFHack/dfhack.git + git clone --recursive https://github.com/DFHack/dfhack.git cd dfhack - git submodule init - git submodule update 7. Set environment variables: + Homebrew (if installed elsewhere, replace /usr/local with ``$(brew --prefix)``):: + + export CC=/usr/local/bin/gcc-4.5 + export CXX=/usr/local/bin/g++-4.5 + Macports:: export CC=/opt/local/bin/gcc-mp-4.5 export CXX=/opt/local/bin/g++-mp-4.5 - Homebrew:: - - export CC=/usr/local/bin/gcc-4.5 - export CXX=/usr/local/bin/g++-4.5 - 8. Build dfhack:: mkdir build-osx @@ -172,27 +192,6 @@ Windows ======= On Windows, DFHack replaces the SDL library distributed with DF. -How to get the code -=================== -DFHack doesn't have any kind of system of code snapshots in place, so you will have to get code from the github repository using git. -You will need some sort of Windows port of git, or a GUI. Some examples: - - * http://msysgit.github.io/ - this is a command line version of git for windows. Most tutorials on git usage will apply. - * http://code.google.com/p/tortoisegit/ - this puts a pretty, graphical face on top of msysgit :) - -The code resides here: https://github.com/DFHack/dfhack - -If you just want to compile DFHack or work on it by contributing patches, it's quite enough to clone from the read-only address:: - - git clone git://github.com/DFHack/dfhack.git - cd dfhack - git submodule init - git submodule update - -The tortoisegit GUI should have the equivalent options included. - -If you want to get really involved with the development, create an account on github, make a clone there and then use that as your remote repository instead. Detailed instructions are beyond the scope of this document. If you need help, join us on IRC (#dfhack channel on freenode). - Dependencies ============ First, you need ``cmake``. Get the win32 installer version from the official @@ -264,7 +263,7 @@ The most important parts of DFHack are the Core, Console, Modules and Plugins. * Core acts as the centerpiece of DFHack - it acts as a filter between DF and SDL and synchronizes the various plugins with DF. * Console is a thread-safe console that can be used to invoke commands exported by Plugins. -* Modules actually describe the way to access information in DF's memory. You can get them from the Core. Most modules are split into two parts: high-level and low-level. Higl-level is mostly method calls, low-level publicly visible pointers to DF's data structures. +* Modules actually describe the way to access information in DF's memory. You can get them from the Core. Most modules are split into two parts: high-level and low-level. High-level is mostly method calls, low-level publicly visible pointers to DF's data structures. * Plugins are the tools that use all the other stuff to make things happen. A plugin can have a list of commands that it exports and an onupdate function that will be called each DF game tick. Rudimentary API documentation can be built using doxygen (see build options with ``ccmake`` or ``cmake-gui``). @@ -280,7 +279,7 @@ DF data structure definitions DFHack uses information about the game data structures, represented via xml files in the library/xml/ submodule. -Data structure layouts are described in files following the df.*.xml name pattern. This information is transformed by a perl script into C++ headers describing the structures, and associated metadata for the Lua wrapper. These headers and data are then compiled into the DFHack libraries, thus necessitating a compatibility break every time layouts change; in return it significantly boosts the efficiency and capabilities of DFHack code. +Data structure layouts are described in files following the df.\*.xml name pattern. This information is transformed by a perl script into C++ headers describing the structures, and associated metadata for the Lua wrapper. These headers and data are then compiled into the DFHack libraries, thus necessitating a compatibility break every time layouts change; in return it significantly boosts the efficiency and capabilities of DFHack code. Global object addresses are stored in symbols.xml, which is copied to the dfhack release package and loaded as data at runtime. From 6c55164be4c1d830f3df9a593fa6e7abd12eb19f Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 24 Jul 2015 10:54:59 -0400 Subject: [PATCH 17/61] Fix a potential crash in tweak farm-plot-select Related to #396 --- plugins/tweak/tweaks/farm-plot-select.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/tweak/tweaks/farm-plot-select.h b/plugins/tweak/tweaks/farm-plot-select.h index 063180c75..11d772450 100644 --- a/plugins/tweak/tweaks/farm-plot-select.h +++ b/plugins/tweak/tweaks/farm-plot-select.h @@ -53,9 +53,9 @@ struct farm_select_hook : df::viewscreen_dwarfmodest { DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) { df::building_farmplotst* farm_plot = getFarmPlot(); - if (farm_plot) + if (farm_plot && ui->selected_farm_crops.size() > 0) { - if (input->count(interface_key::SELECT_ALL) && ui->selected_farm_crops.size() > 0) + if (input->count(interface_key::SELECT_ALL)) { int32_t crop_id = getSelectedCropId(); for (int season = 0; season < 4; season++) @@ -80,7 +80,7 @@ struct farm_select_hook : df::viewscreen_dwarfmodest { DEFINE_VMETHOD_INTERPOSE(void, render, ()) { INTERPOSE_NEXT(render)(); - if (!getFarmPlot()) + if (!getFarmPlot() || !ui->selected_farm_crops.size()) return; auto dims = Gui::getDwarfmodeViewDims(); int x = dims.menu_x1 + 1, From f1a6e5fbe9890de7b5a2cb2b4a8b64575e61d107 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 24 Jul 2015 11:13:13 -0400 Subject: [PATCH 18/61] tweak farm-plot-select: Don't require crops to be discovered Crops whose seeds haven't been discovered are still listed in menus as of 0.40.10 --- NEWS | 4 +++- plugins/tweak/tweaks/farm-plot-select.h | 32 +++++++++++-------------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/NEWS b/NEWS index fdcf9fb51..358e6b19a 100644 --- a/NEWS +++ b/NEWS @@ -64,7 +64,9 @@ DFHack Future - Supports fortress mode loo[k] menu - Recognizes ? and ; keys teleport: Fixed cursor recognition - tweak: debug output now logged to stderr.log instead of console + tweak: + - debug output now logged to stderr.log instead of console - makes DFHack start faster + - farm-plot-select: Fixed issues with selecting undiscovered crops workflow: Improved handling of plant reactions Removed diff --git a/plugins/tweak/tweaks/farm-plot-select.h b/plugins/tweak/tweaks/farm-plot-select.h index 11d772450..38b0596bd 100644 --- a/plugins/tweak/tweaks/farm-plot-select.h +++ b/plugins/tweak/tweaks/farm-plot-select.h @@ -23,26 +23,22 @@ struct farm_select_hook : df::viewscreen_dwarfmodest { { // Adapted from autofarm using namespace df::enums::plant_raw_flags; - // Discovered? - if (ui->tasks.discovered_plants[crop_id]) + // Possible to plant? + df::plant_raw* raws = world->raws.plants.all[crop_id]; + if (raws->flags.is_set(SEED) && raws->flags.is_set((df::plant_raw_flags)season)) { - // Possible to plant? - df::plant_raw* raws = world->raws.plants.all[crop_id]; - if (raws->flags.is_set(SEED) && raws->flags.is_set((df::plant_raw_flags)season)) + // Right depth? + DFCoord cursor (farm_plot->centerx, farm_plot->centery, farm_plot->z); + MapExtras::MapCache mc; + MapExtras::Block * b = mc.BlockAt(cursor / 16); + if (!b || !b->is_valid()) + return false; + auto &block = *b->getRaw(); + df::tile_designation &des = + block.designation[farm_plot->centerx % 16][farm_plot->centery % 16]; + if ((raws->underground_depth_min == 0 || raws->underground_depth_max == 0) != des.bits.subterranean) { - // Right depth? - DFCoord cursor (farm_plot->centerx, farm_plot->centery, farm_plot->z); - MapExtras::MapCache mc; - MapExtras::Block * b = mc.BlockAt(cursor / 16); - if (!b || !b->is_valid()) - return false; - auto &block = *b->getRaw(); - df::tile_designation &des = - block.designation[farm_plot->centerx % 16][farm_plot->centery % 16]; - if ((raws->underground_depth_min == 0 || raws->underground_depth_max == 0) != des.bits.subterranean) - { - return true; - } + return true; } } return false; From 726ffd6417ac67b779adc5a1a390f7b8523a0b57 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 24 Jul 2015 13:58:09 -0400 Subject: [PATCH 19/61] gm-editor: Define some aliases for useful variables --- NEWS | 5 ++++- scripts/gui/gm-editor.lua | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index 358e6b19a..7a1b512e1 100644 --- a/NEWS +++ b/NEWS @@ -49,7 +49,10 @@ DFHack Future - weather display now separated from the date display - New mouse cursor widget full-heal: "-r" option removes corpses - gui/gm-editor: Pointers can now be displaced + gui/gm-editor + - Pointers can now be displaced + - Added some useful aliases: "item" for the selected item, "screen" for the current screen, etc. + - Now avoids errors with unrecognized types gui/hack-wish: renamed to gui/create-item "keybinding list" accepts a context lever: diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua index 344dd7e07..5a4bc5dab 100644 --- a/scripts/gui/gm-editor.lua +++ b/scripts/gui/gm-editor.lua @@ -228,6 +228,8 @@ function GmEditorUi:editSelected(index,choice) self:updateTarget(true) elseif trg_type == 'userdata' or trg_type == 'table' then self:pushTarget(trg.target[trg_key]) + elseif trg_type == 'nil' or trg_type == 'function' then + -- ignore else print("Unknown type:"..trg_type) pcall(function() print("Subtype:"..tostring(trg.target[trg_key]._kind)) end) @@ -379,21 +381,45 @@ function GmEditorUi:popTarget() self:updateTarget() end function show_editor(trg) + if not trg then + qerror('Target not found') + end local screen = GmEditorUi{target=trg} screen:show() end +eval_env = {} +setmetatable(eval_env, {__index = function(_, k) + if k == 'scr' or k == 'screen' then + return dfhack.gui.getCurViewscreen() + elseif k == 'bld' or k == 'building' then + return dfhack.gui.getSelectedBuilding() + elseif k == 'item' then + return dfhack.gui.getSelectedItem() + elseif k == 'job' then + return dfhack.gui.getSelectedJob() + elseif k == 'wsjob' or k == 'workshop_job' then + return dfhack.gui.getSelectedWorkshopJob() + elseif k == 'unit' then + return dfhack.gui.getSelectedUnit() + else + return _G[k] + end +end}) +function eval(s) + local f, err = load("return " .. s, "expression", "t", eval_env) + if err then qerror(err) end + return f() +end if #args~=0 then if args[1]=="dialog" then function thunk(entry) - local t=load("return "..entry)() - show_editor(t) + show_editor(eval(entry)) end dialog.showInputPrompt("Gm Editor", "Object to edit:", COLOR_GRAY, "",thunk) elseif args[1]=="free" then show_editor(df.reinterpret_cast(df[args[2]],args[3])) else - local t=load("return "..args[1])() - show_editor(t) + show_editor(eval(args[1])) end else show_editor(getTargetFromScreens()) From 175edf501aa2fe77d0a3893737b063343b8e5349 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 5 Jun 2015 21:49:22 -0400 Subject: [PATCH 20/61] Add "confirm" plugin - implements a few confirmation dialogs See #577 --- plugins/CMakeLists.txt | 1 + plugins/confirm.cpp | 255 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+) create mode 100644 plugins/confirm.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index f5a82e916..628c521de 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -105,6 +105,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(cleanowned cleanowned.cpp) DFHACK_PLUGIN(colonies colonies.cpp) DFHACK_PLUGIN(command-prompt command-prompt.cpp) + DFHACK_PLUGIN(confirm confirm.cpp) DFHACK_PLUGIN(createitem createitem.cpp) DFHACK_PLUGIN(cursecheck cursecheck.cpp) DFHACK_PLUGIN(deramp deramp.cpp) diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp new file mode 100644 index 000000000..3c1497c87 --- /dev/null +++ b/plugins/confirm.cpp @@ -0,0 +1,255 @@ +#include +#include +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" +#include "VTableInterpose.h" +#include "uicommon.h" +#include "df/viewscreen_tradegoodsst.h" + +using namespace DFHack; + +DFHACK_PLUGIN("confirm"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); +REQUIRE_GLOBAL(gps); + +typedef std::set ikey_set; +command_result df_confirm (color_ostream &out, std::vector & parameters); + +static std::multimap hooks; + +#define IMPLEMENT_CONFIRMATION_HOOKS(cls) \ +static cls cls##_instance; \ +struct cls##_hooks : cls::screen_type { \ + typedef cls::screen_type interpose_base; \ + DEFINE_VMETHOD_INTERPOSE(void, feed, (ikey_set *input)) \ + { \ + cls##_instance.screen = this; \ + if (!cls##_instance.feed(input)) \ + INTERPOSE_NEXT(feed)(input); \ + } \ + DEFINE_VMETHOD_INTERPOSE(void, render, ()) \ + { \ + cls##_instance.screen = this; \ + INTERPOSE_NEXT(render)(); \ + cls##_instance.render(); \ + } \ + DEFINE_VMETHOD_INTERPOSE(bool, key_conflict, (df::interface_key key)) \ + { \ + return cls##_instance.key_conflict(key) || INTERPOSE_NEXT(key_conflict)(key); \ + } \ +}; \ +IMPLEMENT_VMETHOD_INTERPOSE(cls##_hooks, feed); \ +IMPLEMENT_VMETHOD_INTERPOSE(cls##_hooks, render); \ +IMPLEMENT_VMETHOD_INTERPOSE(cls##_hooks, key_conflict); + +template +class confirmation { +public: + enum cstate { INACTIVE, ACTIVE, SELECTED }; + typedef T screen_type; + screen_type *screen; + bool feed (ikey_set *input) { + if (state == INACTIVE) + { + for (auto it = input->begin(); it != input->end(); ++it) + { + if (intercept_key(*it)) + { + last_key = *it; + state = ACTIVE; + return true; + } + } + return false; + } + else if (state == ACTIVE) + { + if (input->count(df::interface_key::LEAVESCREEN)) + state = INACTIVE; + else if (input->count(df::interface_key::SELECT)) + state = SELECTED; + return true; + } + return false; + } + bool key_conflict (df::interface_key key) + { + if (key == df::interface_key::SELECT || key == df::interface_key::LEAVESCREEN) + return false; + return state == ACTIVE; + } + void render() { + static std::vector lines; + Screen::Pen corner_ul = Screen::Pen((char)201, COLOR_GREY, COLOR_BLACK); + Screen::Pen corner_ur = Screen::Pen((char)187, COLOR_GREY, COLOR_BLACK); + Screen::Pen corner_dl = Screen::Pen((char)200, COLOR_GREY, COLOR_BLACK); + Screen::Pen corner_dr = Screen::Pen((char)188, COLOR_GREY, COLOR_BLACK); + Screen::Pen border_ud = Screen::Pen((char)205, COLOR_GREY, COLOR_BLACK); + Screen::Pen border_lr = Screen::Pen((char)186, COLOR_GREY, COLOR_BLACK); + if (state == ACTIVE) + { + split_string(&lines, get_message(), "\n"); + size_t max_length = 30; + for (auto it = lines.begin(); it != lines.end(); ++it) + max_length = std::max(max_length, it->size()); + int width = max_length + 4; + int height = lines.size() + 4; + int x1 = (gps->dimx / 2) - (width / 2); + int x2 = x1 + width - 1; + int y1 = (gps->dimy / 2) - (height / 2); + int y2 = y1 + height - 1; + for (int x = x1; x <= x2; x++) + { + Screen::paintTile(border_ud, x, y1); + Screen::paintTile(border_ud, x, y2); + } + for (int y = y1; y <= y2; y++) + { + Screen::paintTile(border_lr, x1, y); + Screen::paintTile(border_lr, x2, y); + } + Screen::paintTile(corner_ul, x1, y1); + Screen::paintTile(corner_ur, x2, y1); + Screen::paintTile(corner_dl, x1, y2); + Screen::paintTile(corner_dr, x2, y2); + std::string title = " " + get_title() + " "; + Screen::paintString(Screen::Pen(' ', COLOR_BLACK, COLOR_GREY), + (gps->dimx / 2) - (title.size() / 2), y1, title); + int x = x1 + 2; + OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::LEAVESCREEN)); + OutputString(COLOR_WHITE, x, y2, ": Cancel"); + x = x2 - 2 - 4 - Screen::getKeyDisplay(df::interface_key::SELECT).size(); + OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::SELECT)); + OutputString(COLOR_WHITE, x, y2, ": Ok"); + Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_BLACK), x1 + 1, y1 + 1, x2 - 1, y2 - 1); + for (size_t i = 0; i < lines.size(); i++) + { + Screen::paintString(Screen::Pen(' ', get_color(), COLOR_BLACK), x1 + 2, y1 + 2 + i, lines[i]); + } + } + else if (state == SELECTED) + { + ikey_set tmp; + tmp.insert(last_key); + screen->feed(&tmp); + state = INACTIVE; + } + } + virtual bool intercept_key (df::interface_key key) = 0; + virtual std::string get_title() { return "Confirm"; } + virtual std::string get_message() = 0; + virtual UIColor get_color() { return COLOR_YELLOW; } +protected: + cstate state; + df::interface_key last_key; +}; + +class trade_seize_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_SEIZE; } + virtual std::string get_id() { return "trade-seize"; } + virtual std::string get_title() { return "Confirm seize"; } + virtual std::string get_message() { return "Are you sure you want to sieze these goods?"; } +}; +IMPLEMENT_CONFIRMATION_HOOKS(trade_seize_confirmation); + +class trade_offer_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_OFFER; } + virtual std::string get_id() { return "trade-offer"; } + virtual std::string get_title() { return "Confirm offer"; } + virtual std::string get_message() { return "Are you sure you want to offer these goods?\nYou will receive no payment."; } +}; +IMPLEMENT_CONFIRMATION_HOOKS(trade_offer_confirmation); + +#define CHOOK(cls) \ + HOOK_ACTION(cls, render) \ + HOOK_ACTION(cls, feed) \ + HOOK_ACTION(cls, key_conflict) + +#define CHOOKS \ + CHOOK(trade_seize_confirmation) \ + CHOOK(trade_offer_confirmation) + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + std::vector hook_names; +#define HOOK_ACTION(cls, method) hooks.insert(std::pair(cls##_instance.get_id(), INTERPOSE_HOOK(cls##_hooks, method))); \ + if (std::find(hook_names.begin(), hook_names.end(), cls##_instance.get_id()) == hook_names.end()) hook_names.push_back(cls##_instance.get_id()); + CHOOKS +#undef HOOK_ACTION + std::string help = + " confirmation enable|disable option|all ...\n" + "Available options:\n " + join_strings(", ", hook_names); + commands.push_back(PluginCommand( + "confirm", + "Confirmation dialogs", + df_confirm, + false, //allow non-interactive use + help.c_str() + )); + return CR_OK; +} + +DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) +{ + if (is_enabled != enable) + { +#define HOOK_ACTION(cls, method) !INTERPOSE_HOOK(cls##_hooks, method).apply(enable) || + if (CHOOKS 0) + return CR_FAILURE; +#undef HOOK_ACTION + is_enabled = enable; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) +{ +#define HOOK_ACTION(cls, method) INTERPOSE_HOOK(cls##_hooks, method).remove(); + CHOOKS; +#undef HOOK_ACTION + return CR_OK; +} + +void enable_conf (color_ostream &out, std::string name, bool state) +{ + bool found = false; + for (auto it = hooks.begin(); it != hooks.end(); ++it) + { + if (it->first == name) + { + found = true; + it->second.apply(state); + } + } + if (!found) + out.printerr("Unrecognized option: %s\n", name.c_str()); +} + +command_result df_confirm (color_ostream &out, std::vector & parameters) +{ + CoreSuspender suspend; + bool state = true; + for (auto it = parameters.begin(); it != parameters.end(); ++it) + { + if (*it == "enable") + state = true; + else if (*it == "disable") + state = false; + else if (*it == "all") + { + for (auto it = hooks.begin(); it != hooks.end(); ++it) + { + it->second.apply(state); + } + } + else + enable_conf(out, *it, state); + } + return CR_OK; +} From 35f229c578ce3688ed8e4e7d2c696e423095b13a Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Jun 2015 10:31:12 -0400 Subject: [PATCH 21/61] Add confirmations for hauling route/stop deletion and depot removal --- plugins/confirm.cpp | 103 +++++++++++++++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 24 deletions(-) diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index 3c1497c87..91db60d81 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -7,18 +7,26 @@ #include "PluginManager.h" #include "VTableInterpose.h" #include "uicommon.h" +#include "modules/Gui.h" + +#include "df/building_tradedepotst.h" +#include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_tradegoodsst.h" using namespace DFHack; +using namespace df::enums; +using std::string; +using std::vector; DFHACK_PLUGIN("confirm"); DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(gps); +REQUIRE_GLOBAL(ui); typedef std::set ikey_set; -command_result df_confirm (color_ostream &out, std::vector & parameters); +command_result df_confirm (color_ostream &out, vector & parameters); -static std::multimap hooks; +static std::multimap hooks; #define IMPLEMENT_CONFIRMATION_HOOKS(cls) \ static cls cls##_instance; \ @@ -82,7 +90,7 @@ public: return state == ACTIVE; } void render() { - static std::vector lines; + static vector lines; Screen::Pen corner_ul = Screen::Pen((char)201, COLOR_GREY, COLOR_BLACK); Screen::Pen corner_ur = Screen::Pen((char)187, COLOR_GREY, COLOR_BLACK); Screen::Pen corner_dl = Screen::Pen((char)200, COLOR_GREY, COLOR_BLACK); @@ -115,7 +123,9 @@ public: Screen::paintTile(corner_ur, x2, y1); Screen::paintTile(corner_dl, x1, y2); Screen::paintTile(corner_dr, x2, y2); - std::string title = " " + get_title() + " "; + string title = " " + get_title() + " "; + Screen::paintString(Screen::Pen(' ', COLOR_DARKGREY, COLOR_BLACK), + x2 - 6, y1, "DFHack"); Screen::paintString(Screen::Pen(' ', COLOR_BLACK, COLOR_GREY), (gps->dimx / 2) - (title.size() / 2), y1, title); int x = x1 + 2; @@ -139,8 +149,8 @@ public: } } virtual bool intercept_key (df::interface_key key) = 0; - virtual std::string get_title() { return "Confirm"; } - virtual std::string get_message() = 0; + virtual string get_title() { return "Confirm"; } + virtual string get_message() = 0; virtual UIColor get_color() { return COLOR_YELLOW; } protected: cstate state; @@ -150,38 +160,83 @@ protected: class trade_seize_confirmation : public confirmation { public: virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_SEIZE; } - virtual std::string get_id() { return "trade-seize"; } - virtual std::string get_title() { return "Confirm seize"; } - virtual std::string get_message() { return "Are you sure you want to sieze these goods?"; } + virtual string get_id() { return "trade-seize"; } + virtual string get_title() { return "Confirm seize"; } + virtual string get_message() { return "Are you sure you want to sieze these goods?"; } }; IMPLEMENT_CONFIRMATION_HOOKS(trade_seize_confirmation); class trade_offer_confirmation : public confirmation { public: virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_OFFER; } - virtual std::string get_id() { return "trade-offer"; } - virtual std::string get_title() { return "Confirm offer"; } - virtual std::string get_message() { return "Are you sure you want to offer these goods?\nYou will receive no payment."; } + virtual string get_id() { return "trade-offer"; } + virtual string get_title() { return "Confirm offer"; } + virtual string get_message() { return "Are you sure you want to offer these goods?\nYou will receive no payment."; } }; IMPLEMENT_CONFIRMATION_HOOKS(trade_offer_confirmation); +class hauling_route_delete_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) + { + if (ui->main.mode == ui_sidebar_mode::Hauling && ui->hauling.view_routes.size()) + return key == df::interface_key::D_HAULING_REMOVE; + return false; + } + virtual string get_id() { return "haul-delete"; } + virtual string get_title() { return "Confirm deletion"; } + virtual string get_message() + { + std::string type = (ui->hauling.view_stops[ui->hauling.cursor_top]) ? "stop" : "route"; + return std::string("Are you sure you want to delete this ") + type + "?"; + } +}; +IMPLEMENT_CONFIRMATION_HOOKS(hauling_route_delete_confirmation); + +class depot_remove_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) + { + df::building_tradedepotst *depot = virtual_cast(Gui::getAnyBuilding(screen)); + if (depot && key == df::interface_key::DESTROYBUILDING) + { + for (auto it = ui->caravans.begin(); it != ui->caravans.end(); ++it) + { + if ((**it).time_remaining) + return true; + } + } + return false; + } + virtual string get_id() { return "depot-remove"; } + virtual string get_title() { return "Confirm depot removal"; } + virtual string get_message() + { + return "Are you sure you want to remove this depot?\n" + "Merchants are present and will lose profits."; + } +}; +IMPLEMENT_CONFIRMATION_HOOKS(depot_remove_confirmation); + #define CHOOK(cls) \ - HOOK_ACTION(cls, render) \ - HOOK_ACTION(cls, feed) \ - HOOK_ACTION(cls, key_conflict) + HOOK_ACTION(cls, cls##_hooks, render) \ + HOOK_ACTION(cls, cls##_hooks, feed) \ + HOOK_ACTION(cls, cls##_hooks, key_conflict) #define CHOOKS \ CHOOK(trade_seize_confirmation) \ - CHOOK(trade_offer_confirmation) + CHOOK(trade_offer_confirmation) \ + CHOOK(hauling_route_delete_confirmation) \ + CHOOK(depot_remove_confirmation) -DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +DFhackCExport command_result plugin_init (color_ostream &out, vector &commands) { - std::vector hook_names; -#define HOOK_ACTION(cls, method) hooks.insert(std::pair(cls##_instance.get_id(), INTERPOSE_HOOK(cls##_hooks, method))); \ + vector hook_names; +#define HOOK_ACTION(cls, hookcls, method) hooks.insert(std::pair(cls##_instance.get_id(), INTERPOSE_HOOK(hookcls, method))); \ if (std::find(hook_names.begin(), hook_names.end(), cls##_instance.get_id()) == hook_names.end()) hook_names.push_back(cls##_instance.get_id()); CHOOKS #undef HOOK_ACTION - std::string help = + string help = " confirmation enable|disable option|all ...\n" "Available options:\n " + join_strings(", ", hook_names); commands.push_back(PluginCommand( @@ -198,7 +253,7 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) { if (is_enabled != enable) { -#define HOOK_ACTION(cls, method) !INTERPOSE_HOOK(cls##_hooks, method).apply(enable) || +#define HOOK_ACTION(cls, hookcls, method) !INTERPOSE_HOOK(hookcls, method).apply(enable) || if (CHOOKS 0) return CR_FAILURE; #undef HOOK_ACTION @@ -210,13 +265,13 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) DFhackCExport command_result plugin_shutdown (color_ostream &out) { -#define HOOK_ACTION(cls, method) INTERPOSE_HOOK(cls##_hooks, method).remove(); +#define HOOK_ACTION(cls, hookcls, method) INTERPOSE_HOOK(hookcls, method).remove(); CHOOKS; #undef HOOK_ACTION return CR_OK; } -void enable_conf (color_ostream &out, std::string name, bool state) +void enable_conf (color_ostream &out, string name, bool state) { bool found = false; for (auto it = hooks.begin(); it != hooks.end(); ++it) @@ -231,7 +286,7 @@ void enable_conf (color_ostream &out, std::string name, bool state) out.printerr("Unrecognized option: %s\n", name.c_str()); } -command_result df_confirm (color_ostream &out, std::vector & parameters) +command_result df_confirm (color_ostream &out, vector & parameters) { CoreSuspender suspend; bool state = true; From ed3cbe2e605c011701b1297281481687d59aa3cb Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 9 Jun 2015 13:52:37 -0400 Subject: [PATCH 22/61] Add several trade-related confirmations --- plugins/confirm.cpp | 83 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index 91db60d81..419887af5 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -11,6 +11,7 @@ #include "df/building_tradedepotst.h" #include "df/viewscreen_dwarfmodest.h" +#include "df/viewscreen_layer_militaryst.h" #include "df/viewscreen_tradegoodsst.h" using namespace DFHack; @@ -28,7 +29,8 @@ command_result df_confirm (color_ostream &out, vector & parameters); static std::multimap hooks; -#define IMPLEMENT_CONFIRMATION_HOOKS(cls) \ +#define IMPLEMENT_CONFIRMATION_HOOKS(cls) IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, 0) +#define IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, prio) \ static cls cls##_instance; \ struct cls##_hooks : cls::screen_type { \ typedef cls::screen_type interpose_base; \ @@ -49,9 +51,9 @@ struct cls##_hooks : cls::screen_type { \ return cls##_instance.key_conflict(key) || INTERPOSE_NEXT(key_conflict)(key); \ } \ }; \ -IMPLEMENT_VMETHOD_INTERPOSE(cls##_hooks, feed); \ -IMPLEMENT_VMETHOD_INTERPOSE(cls##_hooks, render); \ -IMPLEMENT_VMETHOD_INTERPOSE(cls##_hooks, key_conflict); +IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, feed, prio); \ +IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, render, prio); \ +IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, key_conflict, prio); template class confirmation { @@ -131,7 +133,7 @@ public: int x = x1 + 2; OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::LEAVESCREEN)); OutputString(COLOR_WHITE, x, y2, ": Cancel"); - x = x2 - 2 - 4 - Screen::getKeyDisplay(df::interface_key::SELECT).size(); + x = x2 - 2 - 3 - Screen::getKeyDisplay(df::interface_key::SELECT).size(); OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::SELECT)); OutputString(COLOR_WHITE, x, y2, ": Ok"); Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_BLACK), x1 + 1, y1 + 1, x2 - 1, y2 - 1); @@ -157,6 +159,34 @@ protected: df::interface_key last_key; }; +class trade_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_TRADE; } + virtual string get_id() { return "trade"; } + virtual string get_title() { return "Confirm trade"; } + virtual string get_message() { return "Are you sure you want to trade the selected goods?"; } +}; +IMPLEMENT_CONFIRMATION_HOOKS(trade_confirmation); + +class trade_cancel_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) + { + if (key == df::interface_key::LEAVESCREEN) + { + #define check_list(list) for (auto it = screen->list.begin(); it != screen->list.end(); ++it) if (*it) return true + check_list(trader_selected); + check_list(broker_selected); + #undef check_list + } + return false; + } + virtual string get_id() { return "trade-cancel"; } + virtual string get_title() { return "Cancel trade"; } + virtual string get_message() { return "Are you sure you want leave this screen?\nSelected items will not be saved."; } +}; +IMPLEMENT_CONFIRMATION_HOOKS_PRIO(trade_cancel_confirmation, -1); + class trade_seize_confirmation : public confirmation { public: virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_SEIZE; } @@ -218,16 +248,57 @@ public: }; IMPLEMENT_CONFIRMATION_HOOKS(depot_remove_confirmation); +class squad_disband_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) + { + return screen->num_squads && key == df::interface_key::D_MILITARY_DISBAND_SQUAD; + } + virtual string get_id() { return "squad-disband"; } + virtual string get_title() { return "Disband squad"; } + virtual string get_message() { return "Are you sure you want to disband this squad?"; } +}; +IMPLEMENT_CONFIRMATION_HOOKS(squad_disband_confirmation); + +class note_delete_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) + { + return ui->main.mode == ui_sidebar_mode::NotesPoints && key == df::interface_key::D_NOTE_DELETE; + } + virtual string get_id() { return "note-delete"; } + virtual string get_title() { return "Delete note"; } + virtual string get_message() { return "Are you sure you want to delete this note?"; } +}; +IMPLEMENT_CONFIRMATION_HOOKS(note_delete_confirmation); + +class route_delete_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) + { + return ui->main.mode == ui_sidebar_mode::NotesRoutes && key == df::interface_key::D_NOTE_ROUTE_DELETE; + } + virtual string get_id() { return "route-delete"; } + virtual string get_title() { return "Delete route"; } + virtual string get_message() { return "Are you sure you want to delete this route?"; } +}; +IMPLEMENT_CONFIRMATION_HOOKS(route_delete_confirmation); + #define CHOOK(cls) \ HOOK_ACTION(cls, cls##_hooks, render) \ HOOK_ACTION(cls, cls##_hooks, feed) \ HOOK_ACTION(cls, cls##_hooks, key_conflict) #define CHOOKS \ + CHOOK(trade_confirmation) \ + CHOOK(trade_cancel_confirmation) \ CHOOK(trade_seize_confirmation) \ CHOOK(trade_offer_confirmation) \ CHOOK(hauling_route_delete_confirmation) \ - CHOOK(depot_remove_confirmation) + CHOOK(depot_remove_confirmation) \ + CHOOK(squad_disband_confirmation) \ + CHOOK(note_delete_confirmation) \ + CHOOK(route_delete_confirmation) DFhackCExport command_result plugin_init (color_ostream &out, vector &commands) { From 6352a6e266f226e4d4e7bbf8497e6ee974f09187 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 27 Jul 2015 12:03:29 -0400 Subject: [PATCH 23/61] Expand confirm plugin and improve safety of adding/removing hooks * New option: trade-select-all: Prompts when selecting all goods when some goods are already selected * Improved trade confirmation message depending on selected goods * Made seize and offer confirmations only display when goods in the appropriate columns are selected * States of each option are now listed by "confirm help" --- plugins/confirm.cpp | 241 +++++++++++++++++++++++++++++++++----------- 1 file changed, 182 insertions(+), 59 deletions(-) diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index 419887af5..73d92b894 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -10,6 +10,8 @@ #include "modules/Gui.h" #include "df/building_tradedepotst.h" +#include "df/general_ref.h" +#include "df/general_ref_contained_in_itemst.h" #include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_layer_militaryst.h" #include "df/viewscreen_tradegoodsst.h" @@ -27,33 +29,52 @@ REQUIRE_GLOBAL(ui); typedef std::set ikey_set; command_result df_confirm (color_ostream &out, vector & parameters); -static std::multimap hooks; +struct conf_wrapper; +static std::map confirmations; -#define IMPLEMENT_CONFIRMATION_HOOKS(cls) IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, 0) -#define IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, prio) \ -static cls cls##_instance; \ -struct cls##_hooks : cls::screen_type { \ - typedef cls::screen_type interpose_base; \ - DEFINE_VMETHOD_INTERPOSE(void, feed, (ikey_set *input)) \ - { \ - cls##_instance.screen = this; \ - if (!cls##_instance.feed(input)) \ - INTERPOSE_NEXT(feed)(input); \ - } \ - DEFINE_VMETHOD_INTERPOSE(void, render, ()) \ - { \ - cls##_instance.screen = this; \ - INTERPOSE_NEXT(render)(); \ - cls##_instance.render(); \ - } \ - DEFINE_VMETHOD_INTERPOSE(bool, key_conflict, (df::interface_key key)) \ +template +bool in_vector (std::vector &vec, FT item) +{ + return std::find(vec.begin(), vec.end(), item) != vec.end(); +} + +#define goods_selected_func(list) \ +static bool list##_goods_selected(df::viewscreen_tradegoodsst *screen) \ +{ \ + for (auto it = screen->list##_selected.begin(); it != screen->list##_selected.end(); ++it) \ + if (*it) return true; \ + return false; \ +} +goods_selected_func(trader); +goods_selected_func(broker); +#undef goods_selected_func + +#define goods_all_selected_func(list) \ +static bool list##_goods_all_selected(df::viewscreen_tradegoodsst *screen) \ +{ \ + for (size_t i = 0; i < screen->list##_selected.size(); ++i) \ { \ - return cls##_instance.key_conflict(key) || INTERPOSE_NEXT(key_conflict)(key); \ + if (!screen->list##_selected[i]) \ + { \ + std::vector *refs = &screen->list##_items[i]->general_refs; \ + bool in_container = false; \ + for (auto it = refs->begin(); it != refs->end(); ++it) \ + { \ + if (virtual_cast(*it)) \ + { \ + in_container = true; \ + break; \ + } \ + } \ + if (!in_container) \ + return false; \ + } \ } \ -}; \ -IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, feed, prio); \ -IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, render, prio); \ -IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, key_conflict, prio); + return true; \ +} +goods_all_selected_func(trader); +goods_all_selected_func(broker); +#undef goods_all_selected_func template class confirmation { @@ -159,12 +180,82 @@ protected: df::interface_key last_key; }; +struct conf_wrapper { + bool enabled; + std::set hooks; + + conf_wrapper() + :enabled(false) + {} + void add_hook(VMethodInterposeLinkBase *hook) + { + if (!hooks.count(hook)) + hooks.insert(hook); + } + bool apply (bool state) { + if (state == enabled) + return true; + for (auto h = hooks.begin(); h != hooks.end(); ++h) + { + if (!(**h).apply(state)) + return false; + } + enabled = state; + return true; + } +}; + +#define IMPLEMENT_CONFIRMATION_HOOKS(cls) IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, 0) +#define IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, prio) \ +static cls cls##_instance; \ +struct cls##_hooks : cls::screen_type { \ + typedef cls::screen_type interpose_base; \ + DEFINE_VMETHOD_INTERPOSE(void, feed, (ikey_set *input)) \ + { \ + cls##_instance.screen = this; \ + if (!cls##_instance.feed(input)) \ + INTERPOSE_NEXT(feed)(input); \ + } \ + DEFINE_VMETHOD_INTERPOSE(void, render, ()) \ + { \ + cls##_instance.screen = this; \ + INTERPOSE_NEXT(render)(); \ + cls##_instance.render(); \ + } \ + DEFINE_VMETHOD_INTERPOSE(bool, key_conflict, (df::interface_key key)) \ + { \ + return cls##_instance.key_conflict(key) || INTERPOSE_NEXT(key_conflict)(key); \ + } \ +}; \ +IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, feed, prio); \ +IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, render, prio); \ +IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, key_conflict, prio); + class trade_confirmation : public confirmation { public: - virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_TRADE; } + virtual bool intercept_key (df::interface_key key) + { + return key == df::interface_key::TRADE_TRADE; + } virtual string get_id() { return "trade"; } virtual string get_title() { return "Confirm trade"; } - virtual string get_message() { return "Are you sure you want to trade the selected goods?"; } + virtual string get_message() + { + if (trader_goods_selected(screen) && broker_goods_selected(screen)) + return "Are you sure you want to trade the selected goods?"; + else if (trader_goods_selected(screen)) + return "You are not giving any items. This is likely\n" + "to irritate the merchants.\n" + "Attempt to trade anyway?"; + else if (broker_goods_selected(screen)) + return "You are not receiving any items. You may want to\n" + "offer these items instead or choose items to receive.\n" + "Attempt to trade anyway?"; + else + return "No items are selected. This is likely\n" + "to irritate the merchants.\n" + "Attempt to trade anyway?"; + } }; IMPLEMENT_CONFIRMATION_HOOKS(trade_confirmation); @@ -172,14 +263,8 @@ class trade_cancel_confirmation : public confirmationlist.begin(); it != screen->list.end(); ++it) if (*it) return true - check_list(trader_selected); - check_list(broker_selected); - #undef check_list - } - return false; + return key == df::interface_key::LEAVESCREEN && + (trader_goods_selected(screen) || broker_goods_selected(screen)); } virtual string get_id() { return "trade-cancel"; } virtual string get_title() { return "Cancel trade"; } @@ -189,7 +274,10 @@ IMPLEMENT_CONFIRMATION_HOOKS_PRIO(trade_cancel_confirmation, -1); class trade_seize_confirmation : public confirmation { public: - virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_SEIZE; } + virtual bool intercept_key (df::interface_key key) + { + return trader_goods_selected(screen) && key == df::interface_key::TRADE_SEIZE; + } virtual string get_id() { return "trade-seize"; } virtual string get_title() { return "Confirm seize"; } virtual string get_message() { return "Are you sure you want to sieze these goods?"; } @@ -198,13 +286,39 @@ IMPLEMENT_CONFIRMATION_HOOKS(trade_seize_confirmation); class trade_offer_confirmation : public confirmation { public: - virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_OFFER; } + virtual bool intercept_key (df::interface_key key) + { + return broker_goods_selected(screen) && key == df::interface_key::TRADE_OFFER; + } virtual string get_id() { return "trade-offer"; } virtual string get_title() { return "Confirm offer"; } virtual string get_message() { return "Are you sure you want to offer these goods?\nYou will receive no payment."; } }; IMPLEMENT_CONFIRMATION_HOOKS(trade_offer_confirmation); +class trade_select_all_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) + { + if (key == df::interface_key::SEC_SELECT) + { + if (screen->in_right_pane && broker_goods_selected(screen) && !broker_goods_all_selected(screen)) + return true; + else if (!screen->in_right_pane && trader_goods_selected(screen) && !trader_goods_all_selected(screen)) + return true; + } + return false; + } + virtual string get_id() { return "trade-select-all"; } + virtual string get_title() { return "Confirm selection"; } + virtual string get_message() + { + return "Selecting all goods will overwrite your current selection\n" + "and cannot be undone. Continue?"; + } +}; +IMPLEMENT_CONFIRMATION_HOOKS(trade_select_all_confirmation); + class hauling_route_delete_confirmation : public confirmation { public: virtual bool intercept_key (df::interface_key key) @@ -285,15 +399,16 @@ public: IMPLEMENT_CONFIRMATION_HOOKS(route_delete_confirmation); #define CHOOK(cls) \ - HOOK_ACTION(cls, cls##_hooks, render) \ - HOOK_ACTION(cls, cls##_hooks, feed) \ - HOOK_ACTION(cls, cls##_hooks, key_conflict) + HOOK_ACTION(cls, render) \ + HOOK_ACTION(cls, feed) \ + HOOK_ACTION(cls, key_conflict) #define CHOOKS \ CHOOK(trade_confirmation) \ CHOOK(trade_cancel_confirmation) \ CHOOK(trade_seize_confirmation) \ CHOOK(trade_offer_confirmation) \ + CHOOK(trade_select_all_confirmation) \ CHOOK(hauling_route_delete_confirmation) \ CHOOK(depot_remove_confirmation) \ CHOOK(squad_disband_confirmation) \ @@ -302,55 +417,54 @@ IMPLEMENT_CONFIRMATION_HOOKS(route_delete_confirmation); DFhackCExport command_result plugin_init (color_ostream &out, vector &commands) { - vector hook_names; -#define HOOK_ACTION(cls, hookcls, method) hooks.insert(std::pair(cls##_instance.get_id(), INTERPOSE_HOOK(hookcls, method))); \ - if (std::find(hook_names.begin(), hook_names.end(), cls##_instance.get_id()) == hook_names.end()) hook_names.push_back(cls##_instance.get_id()); +#define HOOK_ACTION(cls, method) \ + if (confirmations.find(cls##_instance.get_id()) == confirmations.end()) \ + confirmations[cls##_instance.get_id()] = new conf_wrapper; \ + confirmations[cls##_instance.get_id()]->add_hook(&INTERPOSE_HOOK(cls##_hooks, method)); CHOOKS #undef HOOK_ACTION - string help = - " confirmation enable|disable option|all ...\n" - "Available options:\n " + join_strings(", ", hook_names); commands.push_back(PluginCommand( "confirm", "Confirmation dialogs", df_confirm, false, //allow non-interactive use - help.c_str() + + " confirmation enable|disable option|all ...\n" + " confirmation help|status\n" )); return CR_OK; } -DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) +DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) { if (is_enabled != enable) { -#define HOOK_ACTION(cls, hookcls, method) !INTERPOSE_HOOK(hookcls, method).apply(enable) || - if (CHOOKS 0) - return CR_FAILURE; -#undef HOOK_ACTION + for (auto c = confirmations.begin(); c != confirmations.end(); ++c) + { + if (!c->second->apply(enable)) + return CR_FAILURE; + } is_enabled = enable; } - return CR_OK; } DFhackCExport command_result plugin_shutdown (color_ostream &out) { -#define HOOK_ACTION(cls, hookcls, method) INTERPOSE_HOOK(hookcls, method).remove(); - CHOOKS; -#undef HOOK_ACTION + if (plugin_enable(out, false) != CR_OK) + return CR_FAILURE; return CR_OK; } void enable_conf (color_ostream &out, string name, bool state) { bool found = false; - for (auto it = hooks.begin(); it != hooks.end(); ++it) + for (auto it = confirmations.begin(); it != confirmations.end(); ++it) { if (it->first == name) { found = true; - it->second.apply(state); + it->second->apply(state); } } if (!found) @@ -361,6 +475,15 @@ command_result df_confirm (color_ostream &out, vector & parameters) { CoreSuspender suspend; bool state = true; + if (in_vector(parameters, "help") || in_vector(parameters, "status")) + { + out << "Available options: \n"; + for (auto it = confirmations.begin(); it != confirmations.end(); ++it) + { + out << " " << it->first << ": " << (it->second->enabled ? "enabled" : "disabled") << std::endl; + } + return CR_OK; + } for (auto it = parameters.begin(); it != parameters.end(); ++it) { if (*it == "enable") @@ -369,9 +492,9 @@ command_result df_confirm (color_ostream &out, vector & parameters) state = false; else if (*it == "all") { - for (auto it = hooks.begin(); it != hooks.end(); ++it) + for (auto it = confirmations.begin(); it != confirmations.end(); ++it) { - it->second.apply(state); + it->second->apply(state); } } else From 11027c2c75f58f10c670d0064cd4bbf91c7dd02f Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 27 Jul 2015 13:01:14 -0400 Subject: [PATCH 24/61] Document and enable confirm plugin --- NEWS | 1 + Readme.rst | 11 +++++++++++ dfhack.init-example | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 7a1b512e1..1581d47a3 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,7 @@ DFHack Future New internal commands kill-lua: Interrupt running Lua scripts New plugins + confirm: Adds confirmation dialogs for several potentially dangerous actions New scripts burial: sets all unowned coffins to allow burial ("-pets" to allow pets too) fix-ster: changes fertility/sterility of animals or dwarves diff --git a/Readme.rst b/Readme.rst index ca886907b..cd2c3717f 100644 --- a/Readme.rst +++ b/Readme.rst @@ -311,6 +311,17 @@ Controls speedydwarf and teledwarf. Speedydwarf makes dwarves move quickly and p Game interface ============== +confirm +------- + +Implements several confirmation dialogs for potentially destructive actions +(for example, seizing goods from traders or deleting hauling routes). + +Usage: + +* ``enable confirm`` or ``confirm enable all``: Enable all confirmations (replace with ``disable`` to disable) +* ``confirm enable option1 [option2...]``: Enable (or disable) specific confirmations. Run ``confirm help`` for a complete list of options. + follow ------ Makes the game view follow the currently highlighted unit after you exit from diff --git a/dfhack.init-example b/dfhack.init-example index daed81041..501ebb830 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -201,8 +201,8 @@ enable search enable automaterial # Other interface improvement tools -# enable dwarfmonitor mousequery automelt autotrade buildingplan resume trackstop zone stocks autochop stockpiles enable \ + confirm \ dwarfmonitor \ mousequery \ automelt \ From 889c76b1473bbb6be0f80b1f3c9846b78ae261b3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 27 Jul 2015 14:05:26 -0400 Subject: [PATCH 25/61] buildingplan: Don't assume building and item type names match There are a few building types (e.g. hatch covers) where this is not the case. Fixes #604 --- NEWS | 1 + plugins/buildingplan-lib.cpp | 89 +++++++++++++++++------------------- 2 files changed, 44 insertions(+), 46 deletions(-) diff --git a/NEWS b/NEWS index 1581d47a3..ec0b663fa 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,7 @@ DFHack Future Fixed default arguments in Lua gametype detection functions Circular lua dependencies (reqscript/script_environment) fixed Prevented crash in Items::createItem() + buildingplan: Now supports hatch covers gui/hack-wish now properly assigns quality to items. gui/gm-editor handles lua tables properly search: fixed crash in unit list after cancelling a job diff --git a/plugins/buildingplan-lib.cpp b/plugins/buildingplan-lib.cpp index a25ee7b93..c933ce11b 100644 --- a/plugins/buildingplan-lib.cpp +++ b/plugins/buildingplan-lib.cpp @@ -9,6 +9,7 @@ void debug(const string &msg) color_ostream_proxy out(Core::getInstance().getConsole()); out << "DEBUG (" << PLUGIN_VERSION << "): " << msg << endl; } +#define dbg Core::getInstance().getConsole() void enable_quickfort_fn(pair& pair) { pair.second = true; } @@ -521,51 +522,47 @@ void Planner::reset(color_ostream &out) void Planner::initialize() { - std::vector item_names; - typedef df::enum_traits item_types; - int size = item_types::last_item_value - item_types::first_item_value+1; - for (size_t i = 1; i < size; i++) - { - is_relevant_item_type[(df::item_type) (i-1)] = false; - std::string item_name = toLower(item_types::key_table[i]); - std::string item_name_clean; - for (auto c = item_name.begin(); c != item_name.end(); c++) - { - if (*c == '_') - continue; - item_name_clean += *c; - } - item_names.push_back(item_name_clean); - } - - typedef df::enum_traits building_types; - size = building_types::last_item_value - building_types::first_item_value+1; - for (size_t i = 1; i < size; i++) - { - auto building_type = (df::building_type) (i-1); - if (building_type == building_type::Weapon || building_type == building_type::Floodgate) - continue; - - std::string building_name = toLower(building_types::key_table[i]); - for (size_t j = 0; j < item_names.size(); j++) - { - if (building_name == item_names[j]) - { - auto btype = (df::building_type) (i-1); - auto itype = (df::item_type) j; - - item_for_building_type[btype] = itype; - default_item_filters[btype] = ItemFilter(); - available_item_vectors[itype] = std::vector(); - is_relevant_item_type[itype] = true; - - if (planmode_enabled.find(btype) == planmode_enabled.end()) - { - planmode_enabled[btype] = false; - } - } - } - } +#define add_building_type(btype, itype) \ + item_for_building_type[df::building_type::btype] = df::item_type::itype; \ + default_item_filters[df::building_type::btype] = ItemFilter(); \ + available_item_vectors[df::item_type::itype] = std::vector(); \ + is_relevant_item_type[df::item_type::itype] = true; \ + if (planmode_enabled.find(df::building_type::btype) == planmode_enabled.end()) \ + planmode_enabled[df::building_type::btype] = false + + FOR_ENUM_ITEMS(item_type, it) + is_relevant_item_type[it] = false; + + add_building_type(Armorstand, ARMORSTAND); + add_building_type(Bed, BED); + add_building_type(Chair, CHAIR); + add_building_type(Coffin, COFFIN); + add_building_type(Door, DOOR); + // add_building_type(Floodgate, FLOODGATE); not displayed before or after being built + add_building_type(Hatch, HATCH_COVER); + // not displayed before or after being built: + // add_building_type(GrateWall, GRATE); + // add_building_type(GrateFloor, GRATE); + // add_building_type(BarsVertical, BAR); + // add_building_type(BarsFloor, BAR); + add_building_type(Cabinet, CABINET); + add_building_type(Box, BOX); + // skip kennels, farm plot + add_building_type(Weaponrack, WEAPONRACK); + add_building_type(Statue, STATUE); + add_building_type(Slab, SLAB); + add_building_type(Table, TABLE); + // skip roads ... furnaces + add_building_type(WindowGlass, WINDOW); + // skip gem window ... support + add_building_type(AnimalTrap, ANIMALTRAP); + add_building_type(Chain, CHAIN); + add_building_type(Cage, CAGE); + // skip archery target + add_building_type(TractionBench, TRACTION_BENCH); + // skip nest box, hive (tools) + +#undef add_building_type } void Planner::doCycle() @@ -657,4 +654,4 @@ void Planner::cycleDefaultQuality(df::building_type type) *quality = static_cast(*quality + 1); if (*quality == item_quality::Artifact) (*quality) = item_quality::Ordinary; -} \ No newline at end of file +} From c3c625e7bd1bc56d062ed65824a26b16b6c62c1a Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 27 Jul 2015 18:22:18 -0400 Subject: [PATCH 26/61] Remove embark-tools nano (implemented in 0.40.24) --- NEWS | 1 + Readme.rst | 1 - plugins/embark-tools.cpp | 73 ---------------------------------------- 3 files changed, 1 insertion(+), 74 deletions(-) diff --git a/NEWS b/NEWS index ec0b663fa..406b2c6a3 100644 --- a/NEWS +++ b/NEWS @@ -74,6 +74,7 @@ DFHack Future - farm-plot-select: Fixed issues with selecting undiscovered crops workflow: Improved handling of plant reactions Removed + embark-tools nano: 1x1 embarks are now possible in vanilla 0.40.24 DFHack 0.40.24-r3 Internals diff --git a/Readme.rst b/Readme.rst index cd2c3717f..84fc475a5 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2155,7 +2155,6 @@ Tools: * ``anywhere``: Allows embarking anywhere (including sites, mountain-only biomes, and oceans). Use with caution. * ``mouse``: Implements mouse controls (currently in the local embark region only) -* ``nano``: An implementation of nano embark - allows resizing below 2x2 when enabled. * ``sand``: Displays an indicator when sand is present in the currently-selected area, similar to the default clay/stone indicators. * ``sticky``: Maintains the selected local area while navigating the world map diff --git a/plugins/embark-tools.cpp b/plugins/embark-tools.cpp index 13884dc2d..37794c875 100644 --- a/plugins/embark-tools.cpp +++ b/plugins/embark-tools.cpp @@ -61,37 +61,6 @@ void set_embark_pos (df::viewscreen_choose_start_sitest * screen, int a, b, c, d, e, f; \ get_embark_pos(screen, a, b, c, d, e, f); -void resize_embark (df::viewscreen_choose_start_sitest * screen, int dx, int dy) -{ - /* Reproduces DF's embark resizing functionality - * Local area resizes up and to the right, unless it's already touching the edge - */ - GET_EMBARK_POS(screen, x1, x2, y1, y2, width, height); - if (x1 == x2 && dx == -1) - dx = 0; - if (y1 == y2 && dy == -1) - dy = 0; - - x2 += dx; // Resize right - while (x2 > 15) - { - x2--; - x1--; - } - x1 = std::max(0, x1); - - y1 -= dy; // Resize up - while (y1 < 0) - { - y1++; - y2++; - } - y2 = std::min(15, y2); - - set_embark_pos(screen, x1, x2, y1, y2); - update_embark_sidebar(screen); -} - typedef df::viewscreen_choose_start_sitest start_sitest; typedef std::set ikey_set; @@ -160,47 +129,6 @@ public: }; }; -class NanoEmbark : public EmbarkTool -{ -public: - virtual std::string getId() { return "nano"; } - virtual std::string getName() { return "Nano embark"; } - virtual std::string getDesc() { return "Allows the embark size to be decreased below 2x2"; } - virtual df::interface_key getToggleKey() { return df::interface_key::CUSTOM_N; } - virtual void before_feed(start_sitest* screen, ikey_set* input, bool &cancel) - { - for (auto iter = input->begin(); iter != input->end(); iter++) - { - df::interface_key key = *iter; - bool is_resize = true; - int dx = 0, dy = 0; - switch (key) - { - case df::interface_key::SETUP_LOCAL_Y_UP: - dy = 1; - break; - case df::interface_key::SETUP_LOCAL_Y_DOWN: - dy = -1; - break; - case df::interface_key::SETUP_LOCAL_X_UP: - dx = 1; - break; - case df::interface_key::SETUP_LOCAL_X_DOWN: - dx = -1; - break; - default: - is_resize = false; - } - if (is_resize) - { - cancel = true; - resize_embark(screen, dx, dy); - return; - } - } - }; -}; - class SandIndicator : public EmbarkTool { protected: @@ -816,7 +744,6 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector Date: Mon, 27 Jul 2015 20:32:33 -0400 Subject: [PATCH 27/61] Cache result of getPath() on darwin --- library/Process-darwin.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index 230809cd3..a485e196d 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -259,18 +259,24 @@ uint32_t Process::getTickCount() string Process::getPath() { + static string cached_path = ""; + if (cached_path.size()) + return cached_path; char path[1024]; char *real_path; uint32_t size = sizeof(path); if (getcwd(path, size)) - return string(path); + { + cached_path = string(path); + return cached_path; + } if (_NSGetExecutablePath(path, &size) == 0) { real_path = realpath(path, NULL); } std::string path_string(real_path); int last_slash = path_string.find_last_of("/"); - std::string directory = path_string.substr(0,last_slash); - return directory; + cached_path = path_string.substr(0,last_slash); + return cached_path; } int Process::getPID() From 5ca5feb8558c162c557bd6adb8e345a0fee11bfa Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 27 Jul 2015 21:38:53 -0400 Subject: [PATCH 28/61] Stop embark-tools from crashing on unload Also enable sand indicator and mouse controls by default and refactor --- dfhack.init-example | 3 ++ plugins/embark-tools.cpp | 76 +++++++++++++++++++++++----------------- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/dfhack.init-example b/dfhack.init-example index 501ebb830..780655c0a 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -222,6 +222,9 @@ enable \ # allow the fortress bookkeeper to queue jobs through the manager enable stockflow +# enable mouse controls and sand indicator in embark screen +embark-tools enable sand mouse + ########### # Scripts # ########### diff --git a/plugins/embark-tools.cpp b/plugins/embark-tools.cpp index 37794c875..3ff38def5 100644 --- a/plugins/embark-tools.cpp +++ b/plugins/embark-tools.cpp @@ -8,6 +8,7 @@ #include "modules/Screen.h" #include "modules/Gui.h" #include +#include #include #include @@ -85,7 +86,7 @@ public: virtual void after_feed(start_sitest* screen, ikey_set* input) { }; virtual void after_mouse_event(start_sitest* screen) { }; }; -std::vector tools; +std::map tools; /* @@ -575,6 +576,8 @@ public: max_y = min_y + height; Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_DARKGREY), min_x, min_y, max_x, max_y); Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_BLACK), min_x + 1, min_y + 1, max_x - 1, max_y - 1); + std::string title = " Embark tools (DFHack) "; + Screen::paintString(Screen::Pen(' ', COLOR_BLACK, COLOR_GREY), (max_x - min_x + title.size()) / 2, min_y, title); x = min_x + 2; y = max_y - 2; OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::SELECT)); @@ -584,7 +587,7 @@ public: y = min_y + 2; FOR_ITER_TOOLS(iter) { - EmbarkTool* t = *iter; + EmbarkTool* t = iter->second; x = min_x + 2; OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(t->getToggleKey())); OutputString(COLOR_WHITE, x, y, ": " + t->getName() + ": "); @@ -605,7 +608,7 @@ public: df::interface_key key = *iter; FOR_ITER_TOOLS(iter) { - EmbarkTool* t = *iter; + EmbarkTool* t = iter->second; if (t->getToggleKey() == key) { t->toggleEnabled(); @@ -615,25 +618,20 @@ public: }; }; +void add_tool (EmbarkTool *t) +{ + tools[t->getId()] = t; +} + bool tool_exists (std::string tool_name) { - FOR_ITER_TOOLS(iter) - { - EmbarkTool* tool = *iter; - if (tool->getId() == tool_name) - return true; - } - return false; + return tools.find(tool_name) != tools.end(); } bool tool_enabled (std::string tool_name) { - FOR_ITER_TOOLS(iter) - { - EmbarkTool* tool = *iter; - if (tool->getId() == tool_name) - return tool->getEnabled(); - } + if (tool_exists(tool_name)) + return tools[tool_name]->getEnabled(); return false; } @@ -642,7 +640,7 @@ bool tool_enable (std::string tool_name, bool enable_state) int n = 0; FOR_ITER_TOOLS(iter) { - EmbarkTool* tool = *iter; + EmbarkTool* tool = iter->second; if (tool->getId() == tool_name || tool_name == "all") { tool->setEnabled(enable_state); @@ -666,8 +664,9 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest std::vector parts; FOR_ITER_TOOLS(it) { - if ((*it)->getEnabled()) - parts.push_back((*it)->getName()); + EmbarkTool *t = it->second; + if (t->getEnabled()) + parts.push_back(t->getName()); } if (parts.size()) { @@ -698,7 +697,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest bool cancel = false; FOR_ITER_TOOLS(iter) { - EmbarkTool* tool = *iter; + EmbarkTool* tool = iter->second; if (tool->getEnabled()) tool->before_feed(this, input, cancel); } @@ -709,7 +708,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest display_settings(); FOR_ITER_TOOLS(iter) { - EmbarkTool* tool = *iter; + EmbarkTool* tool = iter->second; if (tool->getEnabled()) tool->after_feed(this, input); } @@ -718,7 +717,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest { FOR_ITER_TOOLS(iter) { - EmbarkTool* tool = *iter; + EmbarkTool* tool = iter->second; if (tool->getEnabled()) tool->before_render(this); } @@ -726,7 +725,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest display_tool_status(); FOR_ITER_TOOLS(iter) { - EmbarkTool* tool = *iter; + EmbarkTool* tool = iter->second; if (tool->getEnabled()) tool->after_render(this); } @@ -742,16 +741,16 @@ command_result embark_tools_cmd (color_ostream &out, std::vector & DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { - tools.push_back(new EmbarkAnywhere); - tools.push_back(new MouseControl); - tools.push_back(new SandIndicator); - tools.push_back(new StablePosition); + add_tool(new EmbarkAnywhere); + add_tool(new MouseControl); + add_tool(new SandIndicator); + add_tool(new StablePosition); std::string help = ""; help += "embark-tools (enable/disable) tool [tool...]\n" "Tools:\n"; FOR_ITER_TOOLS(iter) { - help += (" " + (*iter)->getId() + ": " + (*iter)->getDesc() + "\n"); + help += (" " + iter->second->getId() + ": " + iter->second->getDesc() + "\n"); } commands.push_back(PluginCommand( "embark-tools", @@ -782,6 +781,19 @@ DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) return CR_OK; } +DFhackCExport command_result plugin_onstatechange (color_ostream &out, state_change_event evt) +{ + if (evt == SC_BEGIN_UNLOAD) + { + if (Gui::getCurFocus() == "dfhack/embark-tools/options") + { + out.printerr("Settings screen active.\n"); + return CR_NOT_FOUND; + } + } + return CR_OK; +} + DFhackCExport command_result plugin_onupdate (color_ostream &out) { static int8_t mask = 0; @@ -801,8 +813,8 @@ DFhackCExport command_result plugin_onupdate (color_ostream &out) { FOR_ITER_TOOLS(iter) { - if ((*iter)->getEnabled()) - (*iter)->after_mouse_event(screen); + if (iter->second->getEnabled()) + iter->second->after_mouse_event(screen); } } mask = new_mask; @@ -842,8 +854,8 @@ command_result embark_tools_cmd (color_ostream &out, std::vector & out << "Tool status:" << std::endl; FOR_ITER_TOOLS(iter) { - EmbarkTool* t = *iter; - out << t->getName() << " (" << t->getId() << "): " + EmbarkTool* t = iter->second; + out << " " << t->getName() << " (" << t->getId() << "): " << (t->getEnabled() ? "Enabled" : "Disabled") << std::endl; } } From e51779b08ccc5d03837e7b767a3660edd368d2d9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 27 Jul 2015 21:40:04 -0400 Subject: [PATCH 29/61] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 5850f66fa..ccca7b721 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 5850f66fa5e37401b1536b574a48d645993520e2 +Subproject commit ccca7b721bef0cbddb54694cf16927e5fe668b7f From b73e859a37dea80b32e90bc4667392687597f2f6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 28 Jul 2015 14:37:49 -0400 Subject: [PATCH 30/61] Reorganize stocks viewscreen to fit a 25-row resolution --- plugins/stocks.cpp | 49 +++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index b6bf68d05..bf7969f6c 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -698,7 +698,7 @@ public: hide_flags.bits.dump = !hide_flags.bits.dump; populateItems(); } - else if (input->count(interface_key::CUSTOM_CTRL_R)) + else if (input->count(interface_key::CUSTOM_CTRL_E)) { hide_flags.bits.on_fire = !hide_flags.bits.on_fire; populateItems(); @@ -897,30 +897,32 @@ public: y = 2; x = left_margin; - OutputString(COLOR_BROWN, x, y, "Filters", true, left_margin); - OutputString(COLOR_LIGHTRED, x, y, "Press Ctrl-Hotkey to toggle", true, left_margin); - OutputFilterString(x, y, "In Job", "J", !hide_flags.bits.in_job, true, left_margin, COLOR_LIGHTBLUE); + OutputString(COLOR_BROWN, x, y, "Filters ", false, left_margin); + OutputString(COLOR_LIGHTRED, x, y, "(Ctrl+Key toggles)", true, left_margin); + OutputFilterString(x, y, "In Job ", "J", !hide_flags.bits.in_job, false, left_margin, COLOR_LIGHTBLUE); OutputFilterString(x, y, "Rotten", "X", !hide_flags.bits.rotten, true, left_margin, COLOR_CYAN); - OutputFilterString(x, y, "Owned", "O", !hide_flags.bits.owned, true, left_margin, COLOR_GREEN); + OutputFilterString(x, y, "Owned ", "O", !hide_flags.bits.owned, false, left_margin, COLOR_GREEN); OutputFilterString(x, y, "Forbidden", "F", !hide_flags.bits.forbid, true, left_margin, COLOR_RED); - OutputFilterString(x, y, "Dump", "D", !hide_flags.bits.dump, true, left_margin, COLOR_LIGHTMAGENTA); - OutputFilterString(x, y, "On Fire", "R", !hide_flags.bits.on_fire, true, left_margin, COLOR_LIGHTRED); - OutputFilterString(x, y, "Melt", "M", !hide_flags.bits.melt, true, left_margin, COLOR_BLUE); + OutputFilterString(x, y, "Dump ", "D", !hide_flags.bits.dump, false, left_margin, COLOR_LIGHTMAGENTA); + OutputFilterString(x, y, "On Fire", "E", !hide_flags.bits.on_fire, true, left_margin, COLOR_LIGHTRED); + OutputFilterString(x, y, "Melt ", "M", !hide_flags.bits.melt, false, left_margin, COLOR_BLUE); OutputFilterString(x, y, "In Inventory", "I", !extra_hide_flags.hide_in_inventory, true, left_margin, COLOR_WHITE); - OutputFilterString(x, y, "Caged", "C", !extra_hide_flags.hide_in_cages, true, left_margin, COLOR_LIGHTRED); + OutputFilterString(x, y, "Caged ", "C", !extra_hide_flags.hide_in_cages, false, left_margin, COLOR_LIGHTRED); OutputFilterString(x, y, "Trade", "T", !extra_hide_flags.hide_trade_marked, true, left_margin, COLOR_LIGHTGREEN); OutputFilterString(x, y, "No Flags", "N", !hide_unflagged, true, left_margin, COLOR_GREY); - ++y; + if (gps->dimy > 26) + ++y; OutputHotkeyString(x, y, "Clear All", "Shift-C", true, left_margin); OutputHotkeyString(x, y, "Enable All", "Shift-E", true, left_margin); OutputHotkeyString(x, y, "Toggle Grouping", "TAB", true, left_margin); ++y; - OutputHotkeyString(x, y, "Min Qual: ", "-+"); - OutputString(COLOR_BROWN, x, y, get_quality_name(min_quality), true, left_margin); - OutputHotkeyString(x, y, "Max Qual: ", "/*"); - OutputString(COLOR_BROWN, x, y, get_quality_name(max_quality), true, left_margin); - ++y; + OutputString(COLOR_WHITE, x, y, "Qual: "); + OutputHotkeyString(x, y, "Min: ", "-+"); + OutputString(COLOR_BROWN, x, y, get_quality_name(min_quality), false, left_margin); + ++x; + OutputHotkeyString(x, y, "Max: ", "/*"); + OutputString(COLOR_BROWN, x, y, get_quality_name(max_quality), true, left_margin); OutputHotkeyString(x, y, "Min Wear: ", "Shift-W"); OutputString(COLOR_BROWN, x, y, int_to_string(min_wear), true, left_margin); @@ -928,16 +930,16 @@ public: OutputString(COLOR_BROWN, x, y, "Actions ("); OutputString(COLOR_LIGHTGREEN, x, y, int_to_string(items_column.getDisplayedListSize())); OutputString(COLOR_BROWN, x, y, " Items)", true, left_margin); - OutputHotkeyString(x, y, "Zoom", "Shift-Z", true, left_margin); + OutputHotkeyString(x, y, "Zoom ", "Shift-Z", false, left_margin); + OutputHotkeyString(x, y, "Dump", "-D", true, left_margin); + OutputHotkeyString(x, y, "Forbid ", "Shift-F", false, left_margin); + OutputHotkeyString(x, y, "Melt", "-M", true, left_margin); OutputHotkeyString(x, y, "Apply to: ", "Shift-A"); OutputString(COLOR_BROWN, x, y, (apply_to_all) ? "Listed" : "Selected", true, left_margin); - OutputHotkeyString(x, y, "Dump", "Shift-D", true, left_margin); - OutputHotkeyString(x, y, "Forbid", "Shift-F", true, left_margin); - OutputHotkeyString(x, y, "Melt", "Shift-M", true, left_margin); if (depot_info.canTrade()) OutputHotkeyString(x, y, "Mark for Trade", "Shift-T", true, left_margin); - y = gps->dimy - 6; + y = gps->dimy - 5; OutputString(COLOR_LIGHTRED, x, y, "Flag names can also", true, left_margin); OutputString(COLOR_LIGHTRED, x, y, "be searched for", true, left_margin); } @@ -1307,7 +1309,6 @@ struct stocks_hook : public df::viewscreen_storesst if (input->count(interface_key::CUSTOM_E)) { Screen::dismiss(this); - Screen::dismiss(Gui::getCurViewscreen(true)); Screen::show(new ViewscreenStocks()); return; } @@ -1320,7 +1321,7 @@ struct stocks_hook : public df::viewscreen_storesst auto dim = Screen::getWindowSize(); int x = 40; int y = dim.y - 2; - OutputHotkeyString(x, y, "Enhanced View", "e"); + OutputHotkeyString(x, y, "Enhanced View", "e", false, 0, COLOR_WHITE, COLOR_LIGHTRED); } }; @@ -1440,6 +1441,10 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan case SC_MAP_LOADED: ViewscreenStocks::reset(); break; + case SC_BEGIN_UNLOAD: + if (Gui::getCurFocus().find("dfhack/stocks") == 0) + return CR_FAILURE; + break; default: break; } From dd7c20c848cbee5a1e71446536e9ea65b3f22655 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 28 Jul 2015 18:34:29 -0400 Subject: [PATCH 31/61] Add safer helpers to get STRING_X keys from interface_key sets A few plugins were using input->rbegin() previously, which crashes if input is empty and isn't strictly guaranteed to return a STRING_X key. --- NEWS | 1 + plugins/buildingplan.cpp | 2 +- plugins/search.cpp | 14 +++----------- plugins/uicommon.h | 22 ++++++++++++++++++---- plugins/zone.cpp | 9 ++------- 5 files changed, 25 insertions(+), 23 deletions(-) diff --git a/NEWS b/NEWS index 406b2c6a3..0588784b9 100644 --- a/NEWS +++ b/NEWS @@ -34,6 +34,7 @@ DFHack Future gui/hack-wish now properly assigns quality to items. gui/gm-editor handles lua tables properly search: fixed crash in unit list after cancelling a job + stocks: fixed a crash when right-clicking steam-engine: - fixed a crash on arena load - number keys (e.g. 2/8) take priority over cursor keys when applicable diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index d3cdef923..ddc804897 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -189,7 +189,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest else if (isInNobleRoomQueryMode()) { auto np = getNoblePositionOfSelectedBuildingOwner(); - df::interface_key last_token = *input->rbegin(); + df::interface_key last_token = get_string_key(input); if (last_token >= interface_key::STRING_A048 && last_token <= interface_key::STRING_A058) { int selection = last_token - interface_key::STRING_A048; diff --git a/plugins/search.cpp b/plugins/search.cpp index 4c0773906..7877bc207 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -5,6 +5,8 @@ #include +#include "uicommon.h" + #include "df/ui_look_list.h" #include "df/viewscreen_announcelistst.h" #include "df/viewscreen_petst.h" @@ -65,12 +67,6 @@ to use. */ -void OutputString(int8_t color, int &x, int y, const std::string &text) -{ - Screen::paintString(Screen::Pen(' ', color, 0), x, y, text); - x += text.length(); -} - void make_text_dim(int x1, int x2, int y) { for (int x = x1; x <= x2; x++) @@ -225,11 +221,7 @@ public: { // Query typing mode - if (input->empty()) - { - return false; - } - df::interface_key last_token = *input->rbegin(); + df::interface_key last_token = get_string_key(input); int charcode = Screen::keyToChar(last_token); if (charcode >= 32 && charcode <= 126) { diff --git a/plugins/uicommon.h b/plugins/uicommon.h index 56519c260..5f5414b72 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -206,6 +206,20 @@ static string pad_string(string text, const int size, const bool front = true, c } } +static df::interface_key get_string_key(const std::set *input) +{ + for (auto it = input->begin(); it != input->end(); ++it) + { + if (DFHack::Screen::keyToChar(*it) >= 0) + return *it; + } + return df::interface_key::NONE; +} + +static char get_string_input(const std::set *input) +{ + return DFHack::Screen::keyToChar(get_string_key(input)); +} /* * Utility Functions @@ -370,7 +384,7 @@ protected: y2 = sp->room.y + sp->room.height; } -private: +protected: int x1, x2, y1, y2, z; }; @@ -414,7 +428,7 @@ public: DFHack::World::DeletePersistentData(config); } -private: +protected: PersistentDataItem config; string persistence_key; }; @@ -799,7 +813,7 @@ public: else if (allow_search) { // Search query typing mode always on - df::interface_key last_token = *input->rbegin(); + df::interface_key last_token = get_string_key(input); int charcode = Screen::keyToChar(last_token); if ((charcode >= 96 && charcode <= 123) || charcode == 32) { @@ -873,7 +887,7 @@ public: return display_list.size(); } -private: +protected: static void clear_fn(ListEntry &e) { e.selected = false; } static bool sort_fn(ListEntry const& a, ListEntry const& b) { return a.text.compare(b.text) < 0; } diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 10ffcd3c8..b9044b6fd 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -41,6 +41,7 @@ using namespace std; #include "Export.h" #include "PluginManager.h" #include "MiscUtils.h" +#include "uicommon.h" #include "LuaTools.h" #include "DataFuncs.h" @@ -3705,12 +3706,6 @@ DFHACK_PLUGIN_LUA_COMMANDS { //START zone filters -void OutputString(int8_t color, int &x, int y, const std::string &text) -{ - Screen::paintString(Screen::Pen(' ', color, 0), x, y, text); - x += text.length(); -} - class zone_filter { public: @@ -3856,7 +3851,7 @@ public: return false; } - df::interface_key last_token = *input->rbegin(); + df::interface_key last_token = get_string_key(input); int charcode = Screen::keyToChar(last_token); if (charcode >= 32 && charcode <= 126) { From 786086e5c5c55995b536b8194a024c186662ee06 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 28 Jul 2015 18:36:20 -0400 Subject: [PATCH 32/61] Make non-CR_OK return values for SC_BEGIN_UNLOAD events abort unload Returning CR_FAILURE, for example, is more consistent with plugin_shutdown(). --- library/PluginManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 0d6442699..418b5a57b 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -326,7 +326,7 @@ bool Plugin::unload(color_ostream &con) EventManager::unregisterAll(this); // notify the plugin about an attempt to shutdown if (plugin_onstatechange && - plugin_onstatechange(con, SC_BEGIN_UNLOAD) == CR_NOT_FOUND) + plugin_onstatechange(con, SC_BEGIN_UNLOAD) != CR_OK) { con.printerr("Plugin %s has refused to be unloaded.\n", name.c_str()); access->unlock(); From f8dd680a07284522e8fa30bae2f011dead70d0b8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 28 Jul 2015 18:48:13 -0400 Subject: [PATCH 33/61] Fix zone entry in NEWS --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 0588784b9..0b42a35e0 100644 --- a/NEWS +++ b/NEWS @@ -41,7 +41,7 @@ DFHack Future tweak fps-min fixed workflow: Fixed some issues with stuck jobs - Note: Existing stuck jobs must be cancelled and re-added - zone: Fixed a crash in the unit list after cancelling a job (and several other potential crashes) + zone: Fixed a crash when using "zone set" (and a few other potential crashes) Misc Improvements autolabor: - Stopped modification of labors that shouldn't be modified for brokers/diplomats From 825d46af51694fa45120a04532a2fd84313a898a Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 28 Jul 2015 21:48:00 -0400 Subject: [PATCH 34/61] Move ListColumn class to a separate header --- plugins/autochop.cpp | 1 + plugins/autodump.cpp | 4 +- plugins/automelt.cpp | 4 +- plugins/autotrade.cpp | 4 +- plugins/buildingplan-lib.h | 3 +- plugins/dwarfmonitor.cpp | 1 + plugins/hotkeys.cpp | 1 + plugins/listcolumn.h | 469 +++++++++++++++++++++++++++++ plugins/manipulator.cpp | 1 + plugins/mousequery.cpp | 5 +- plugins/remotefortressreader.cpp | 2 +- plugins/stockflow.cpp | 5 +- plugins/stockpiles/stockpiles.cpp | 1 + plugins/stocks.cpp | 1 + plugins/uicommon.h | 472 ------------------------------ 15 files changed, 484 insertions(+), 490 deletions(-) create mode 100644 plugins/listcolumn.h diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index 3009877f1..f7ba3d300 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -1,6 +1,7 @@ // automatically chop trees #include "uicommon.h" +#include "listcolumn.h" #include "Core.h" #include "Console.h" diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp index 944288001..3fbe1ae77 100644 --- a/plugins/autodump.cpp +++ b/plugins/autodump.cpp @@ -37,6 +37,7 @@ using MapExtras::MapCache; using df::building_stockpilest; DFHACK_PLUGIN("autodump"); +REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(world); // Stockpile interface START @@ -254,9 +255,6 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { - if (!gps) - return CR_FAILURE; - if (enable != is_enabled) { if (!INTERPOSE_HOOK(dump_hook, feed).apply(enable) || diff --git a/plugins/automelt.cpp b/plugins/automelt.cpp index 620ed622e..0aacce233 100644 --- a/plugins/automelt.cpp +++ b/plugins/automelt.cpp @@ -18,6 +18,7 @@ using df::building_stockpilest; DFHACK_PLUGIN("automelt"); #define PLUGIN_VERSION 0.3 +REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(cursor); REQUIRE_GLOBAL(ui); @@ -282,9 +283,6 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { - if (!gps) - return CR_FAILURE; - if (enable != is_enabled) { if (!INTERPOSE_HOOK(melt_hook, feed).apply(enable) || diff --git a/plugins/autotrade.cpp b/plugins/autotrade.cpp index aa7a35494..a645d579e 100644 --- a/plugins/autotrade.cpp +++ b/plugins/autotrade.cpp @@ -22,6 +22,7 @@ using df::building_stockpilest; DFHACK_PLUGIN("autotrade"); +REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(cursor); REQUIRE_GLOBAL(ui); @@ -465,9 +466,6 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { - if (!gps) - return CR_FAILURE; - if (enable != is_enabled) { depot_info.reset(); diff --git a/plugins/buildingplan-lib.h b/plugins/buildingplan-lib.h index 7b2be66cb..7ec0c41d4 100644 --- a/plugins/buildingplan-lib.h +++ b/plugins/buildingplan-lib.h @@ -2,6 +2,7 @@ #define BUILDINGPLAN_H #include "uicommon.h" +#include "listcolumn.h" #include @@ -492,4 +493,4 @@ static Planner planner; static RoomMonitor roomMonitor; -#endif \ No newline at end of file +#endif diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 798d4e36d..e70bb8399 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -1,4 +1,5 @@ #include "uicommon.h" +#include "listcolumn.h" #include "DataDefs.h" diff --git a/plugins/hotkeys.cpp b/plugins/hotkeys.cpp index 7709c0719..45ca07cbd 100644 --- a/plugins/hotkeys.cpp +++ b/plugins/hotkeys.cpp @@ -1,4 +1,5 @@ #include "uicommon.h" +#include "listcolumn.h" #include "df/viewscreen_dwarfmodest.h" #include "df/ui.h" diff --git a/plugins/listcolumn.h b/plugins/listcolumn.h new file mode 100644 index 000000000..e90f2f5fb --- /dev/null +++ b/plugins/listcolumn.h @@ -0,0 +1,469 @@ +#include "uicommon.h" + +using df::global::enabler; +using df::global::gps; + +/* + * List classes + */ +template +class ListEntry +{ +public: + T elem; + string text, keywords; + bool selected; + UIColor color; + + ListEntry(const string text, const T elem, const string keywords = "", const UIColor color = COLOR_UNSELECTED) : + elem(elem), text(text), selected(false), keywords(keywords), color(color) + { + } +}; + +template +class ListColumn +{ +public: + int highlighted_index; + int display_start_offset; + unsigned short text_clip_at; + int32_t bottom_margin, search_margin, left_margin; + bool multiselect; + bool allow_null; + bool auto_select; + bool allow_search; + bool feed_mouse_set_highlight; + bool feed_changed_highlight; + + ListColumn() + { + bottom_margin = 3; + clear(); + left_margin = 2; + search_margin = 63; + highlighted_index = 0; + text_clip_at = 0; + multiselect = false; + allow_null = true; + auto_select = false; + allow_search = true; + feed_mouse_set_highlight = false; + feed_changed_highlight = false; + } + + void clear() + { + list.clear(); + display_list.clear(); + display_start_offset = 0; + if (highlighted_index != -1) + highlighted_index = 0; + max_item_width = title.length(); + resize(); + } + + void resize() + { + display_max_rows = gps->dimy - 4 - bottom_margin; + } + + void add(ListEntry &entry) + { + list.push_back(entry); + if (entry.text.length() > max_item_width) + max_item_width = entry.text.length(); + } + + void add(const string &text, const T &elem) + { + list.push_back(ListEntry(text, elem)); + if (text.length() > max_item_width) + max_item_width = text.length(); + } + + int fixWidth() + { + if (text_clip_at > 0 && max_item_width > text_clip_at) + max_item_width = text_clip_at; + + for (auto it = list.begin(); it != list.end(); it++) + { + it->text = pad_string(it->text, max_item_width, false); + } + + return getMaxItemWidth(); + } + + int getMaxItemWidth() + { + return left_margin + max_item_width; + } + + virtual void display_extras(const T &elem, int32_t &x, int32_t &y) const {} + + void display(const bool is_selected_column) const + { + int32_t y = 2; + paint_text(COLOR_TITLE, left_margin, y, title); + + int last_index_able_to_display = display_start_offset + display_max_rows; + for (int i = display_start_offset; i < display_list.size() && i < last_index_able_to_display; i++) + { + ++y; + UIColor fg_color = (display_list[i]->selected) ? COLOR_SELECTED : display_list[i]->color; + UIColor bg_color = (is_selected_column && i == highlighted_index) ? COLOR_HIGHLIGHTED : COLOR_BLACK; + + string item_label = display_list[i]->text; + if (text_clip_at > 0 && item_label.length() > text_clip_at) + item_label.resize(text_clip_at); + + paint_text(fg_color, left_margin, y, item_label, bg_color); + int x = left_margin + display_list[i]->text.length() + 1; + display_extras(display_list[i]->elem, x, y); + } + + if (is_selected_column && allow_search) + { + y = gps->dimy - 3; + int32_t x = search_margin; + OutputHotkeyString(x, y, "Search" ,"S"); + OutputString(COLOR_WHITE, x, y, ": "); + OutputString(COLOR_WHITE, x, y, search_string); + OutputString(COLOR_LIGHTGREEN, x, y, "_"); + } + } + + void filterDisplay() + { + ListEntry *prev_selected = (getDisplayListSize() > 0) ? display_list[highlighted_index] : NULL; + display_list.clear(); + + search_string = toLower(search_string); + vector search_tokens; + if (!search_string.empty()) + split_string(&search_tokens, search_string, " "); + + for (size_t i = 0; i < list.size(); i++) + { + ListEntry *entry = &list[i]; + + bool include_item = true; + if (!search_string.empty()) + { + string item_string = toLower(list[i].text); + for (auto si = search_tokens.begin(); si != search_tokens.end(); si++) + { + if (!si->empty() && item_string.find(*si) == string::npos && + list[i].keywords.find(*si) == string::npos) + { + include_item = false; + break; + } + } + } + + if (include_item) + { + display_list.push_back(entry); + if (entry == prev_selected) + highlighted_index = display_list.size() - 1; + } + else if (auto_select) + { + entry->selected = false; + } + } + changeHighlight(0); + feed_changed_highlight = true; + } + + void selectDefaultEntry() + { + for (size_t i = 0; i < display_list.size(); i++) + { + if (display_list[i]->selected) + { + highlighted_index = i; + break; + } + } + } + + void centerSelection() + { + if (display_list.size() == 0) + return; + display_start_offset = highlighted_index - (display_max_rows / 2); + validateDisplayOffset(); + validateHighlight(); + } + + void validateHighlight() + { + set_to_limit(highlighted_index, display_list.size() - 1); + + if (highlighted_index < display_start_offset) + display_start_offset = highlighted_index; + else if (highlighted_index >= display_start_offset + display_max_rows) + display_start_offset = highlighted_index - display_max_rows + 1; + + if (auto_select || (!allow_null && list.size() == 1)) + display_list[highlighted_index]->selected = true; + + feed_changed_highlight = true; + } + + void changeHighlight(const int highlight_change, const int offset_shift = 0) + { + if (!initHighlightChange()) + return; + + highlighted_index += highlight_change + offset_shift * display_max_rows; + + display_start_offset += offset_shift * display_max_rows; + validateDisplayOffset(); + validateHighlight(); + } + + void validateDisplayOffset() + { + set_to_limit(display_start_offset, max(0, (int)(display_list.size())-display_max_rows)); + } + + void setHighlight(const int index) + { + if (!initHighlightChange()) + return; + + highlighted_index = index; + validateHighlight(); + } + + bool initHighlightChange() + { + if (display_list.size() == 0) + return false; + + if (auto_select && !multiselect) + { + for (auto it = list.begin(); it != list.end(); it++) + { + it->selected = false; + } + } + + return true; + } + + void toggleHighlighted() + { + if (display_list.size() == 0) + return; + + if (auto_select) + return; + + ListEntry *entry = display_list[highlighted_index]; + if (!multiselect || !allow_null) + { + int selected_count = 0; + for (size_t i = 0; i < list.size(); i++) + { + if (!multiselect && !entry->selected) + list[i].selected = false; + if (!allow_null && list[i].selected) + selected_count++; + } + + if (!allow_null && entry->selected && selected_count == 1) + return; + } + + entry->selected = !entry->selected; + } + + vector getSelectedElems(bool only_one = false) + { + vector results; + for (auto it = list.begin(); it != list.end(); it++) + { + if ((*it).selected) + { + results.push_back(it->elem); + if (only_one) + break; + } + } + + return results; + } + + T getFirstSelectedElem() + { + vector results = getSelectedElems(true); + if (results.size() == 0) + return (T)nullptr; + else + return results[0]; + } + + void clearSelection() + { + for_each_(list, clear_fn); + } + + void selectItem(const T elem) + { + int i = 0; + for (; i < display_list.size(); i++) + { + if (display_list[i]->elem == elem) + { + setHighlight(i); + break; + } + } + } + + void clearSearch() + { + search_string.clear(); + filterDisplay(); + } + + size_t getDisplayListSize() + { + return display_list.size(); + } + + vector*> &getDisplayList() + { + return display_list; + } + + size_t getBaseListSize() + { + return list.size(); + } + + bool feed(set *input) + { + feed_mouse_set_highlight = feed_changed_highlight = false; + if (input->count(interface_key::CURSOR_UP)) + { + changeHighlight(-1); + } + else if (input->count(interface_key::CURSOR_DOWN)) + { + changeHighlight(1); + } + else if (input->count(interface_key::STANDARDSCROLL_PAGEUP)) + { + changeHighlight(0, -1); + } + else if (input->count(interface_key::STANDARDSCROLL_PAGEDOWN)) + { + changeHighlight(0, 1); + } + else if (input->count(interface_key::SELECT) && !auto_select) + { + toggleHighlighted(); + } + else if (input->count(interface_key::CUSTOM_SHIFT_S)) + { + clearSearch(); + } + else if (enabler->tracking_on && gps->mouse_x != -1 && gps->mouse_y != -1 && enabler->mouse_lbut) + { + return setHighlightByMouse(); + } + else if (allow_search) + { + // Search query typing mode always on + df::interface_key last_token = get_string_key(input); + int charcode = Screen::keyToChar(last_token); + if ((charcode >= 96 && charcode <= 123) || charcode == 32) + { + // Standard character + search_string += char(charcode); + filterDisplay(); + centerSelection(); + } + else if (last_token == interface_key::STRING_A000) + { + // Backspace + if (search_string.length() > 0) + { + search_string.erase(search_string.length()-1); + filterDisplay(); + centerSelection(); + } + } + else + { + return false; + } + + return true; + } + else + { + return false; + } + + return true; + } + + bool setHighlightByMouse() + { + if (gps->mouse_y >= 3 && gps->mouse_y < display_max_rows + 3 && + gps->mouse_x >= left_margin && gps->mouse_x < left_margin + max_item_width) + { + int new_index = display_start_offset + gps->mouse_y - 3; + if (new_index < display_list.size()) + { + setHighlight(new_index); + feed_mouse_set_highlight = true; + } + + enabler->mouse_lbut = enabler->mouse_rbut = 0; + + return true; + } + + return false; + } + + void sort(bool force_sort = false) + { + if (force_sort || list.size() < 100) + std::sort(list.begin(), list.end(), sort_fn); + + filterDisplay(); + } + + void setTitle(const string t) + { + title = t; + if (title.length() > max_item_width) + max_item_width = title.length(); + } + + size_t getDisplayedListSize() + { + return display_list.size(); + } + +protected: + static void clear_fn(ListEntry &e) { e.selected = false; } + static bool sort_fn(ListEntry const& a, ListEntry const& b) { return a.text.compare(b.text) < 0; } + + vector> list; + vector*> display_list; + string search_string; + string title; + int display_max_rows; + int max_item_width; +}; + diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index f675793df..6a69d7996 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -33,6 +33,7 @@ #include "df/entity_raw.h" #include "uicommon.h" +#include "listcolumn.h" using std::stringstream; using std::set; diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index 4547a62d0..b0835832f 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -22,6 +22,8 @@ #include "DataFuncs.h" DFHACK_PLUGIN("mousequery"); +REQUIRE_GLOBAL(enabler); +REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui_build_selector); @@ -815,9 +817,6 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) { - if (!gps) - return CR_FAILURE; - if (is_enabled != enable) { last_clicked_x = last_clicked_y = last_clicked_z = -1; diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 6629e7893..bc9bdbb08 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -1159,4 +1159,4 @@ static command_result GetBuildingDefList(color_ostream &stream, const EmptyMessa } } return CR_OK; -} \ No newline at end of file +} diff --git a/plugins/stockflow.cpp b/plugins/stockflow.cpp index 34dfc1bd7..75d09e1e8 100644 --- a/plugins/stockflow.cpp +++ b/plugins/stockflow.cpp @@ -22,12 +22,9 @@ using df::building_stockpilest; DFHACK_PLUGIN("stockflow"); #define AUTOENABLE false -#ifdef DFHACK_PLUGIN_IS_ENABLED DFHACK_PLUGIN_IS_ENABLED(enabled); -#else -bool enabled = false; -#endif +REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(ui); diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp index 936052a15..f02d19e45 100644 --- a/plugins/stockpiles/stockpiles.cpp +++ b/plugins/stockpiles/stockpiles.cpp @@ -40,6 +40,7 @@ using namespace google::protobuf; using namespace dfstockpiles; DFHACK_PLUGIN ( "stockpiles" ); +REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(selection_rect); diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index bf7969f6c..0f6039bfc 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -1,4 +1,5 @@ #include "uicommon.h" +#include "listcolumn.h" #include diff --git a/plugins/uicommon.h b/plugins/uicommon.h index 5f5414b72..a54b3aa04 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -37,10 +37,6 @@ using std::set; using namespace DFHack; using namespace df::enums; -using df::global::enabler; -using df::global::gps; - - #ifndef HAVE_NULLPTR #define nullptr 0L #endif @@ -432,471 +428,3 @@ protected: PersistentDataItem config; string persistence_key; }; - - - -/* - * List classes - */ -template -class ListEntry -{ -public: - T elem; - string text, keywords; - bool selected; - UIColor color; - - ListEntry(const string text, const T elem, const string keywords = "", const UIColor color = COLOR_UNSELECTED) : - elem(elem), text(text), selected(false), keywords(keywords), color(color) - { - } -}; - -template -class ListColumn -{ -public: - int highlighted_index; - int display_start_offset; - unsigned short text_clip_at; - int32_t bottom_margin, search_margin, left_margin; - bool multiselect; - bool allow_null; - bool auto_select; - bool allow_search; - bool feed_mouse_set_highlight; - bool feed_changed_highlight; - - ListColumn() - { - bottom_margin = 3; - clear(); - left_margin = 2; - search_margin = 63; - highlighted_index = 0; - text_clip_at = 0; - multiselect = false; - allow_null = true; - auto_select = false; - allow_search = true; - feed_mouse_set_highlight = false; - feed_changed_highlight = false; - } - - void clear() - { - list.clear(); - display_list.clear(); - display_start_offset = 0; - if (highlighted_index != -1) - highlighted_index = 0; - max_item_width = title.length(); - resize(); - } - - void resize() - { - display_max_rows = gps->dimy - 4 - bottom_margin; - } - - void add(ListEntry &entry) - { - list.push_back(entry); - if (entry.text.length() > max_item_width) - max_item_width = entry.text.length(); - } - - void add(const string &text, const T &elem) - { - list.push_back(ListEntry(text, elem)); - if (text.length() > max_item_width) - max_item_width = text.length(); - } - - int fixWidth() - { - if (text_clip_at > 0 && max_item_width > text_clip_at) - max_item_width = text_clip_at; - - for (auto it = list.begin(); it != list.end(); it++) - { - it->text = pad_string(it->text, max_item_width, false); - } - - return getMaxItemWidth(); - } - - int getMaxItemWidth() - { - return left_margin + max_item_width; - } - - virtual void display_extras(const T &elem, int32_t &x, int32_t &y) const {} - - void display(const bool is_selected_column) const - { - int32_t y = 2; - paint_text(COLOR_TITLE, left_margin, y, title); - - int last_index_able_to_display = display_start_offset + display_max_rows; - for (int i = display_start_offset; i < display_list.size() && i < last_index_able_to_display; i++) - { - ++y; - UIColor fg_color = (display_list[i]->selected) ? COLOR_SELECTED : display_list[i]->color; - UIColor bg_color = (is_selected_column && i == highlighted_index) ? COLOR_HIGHLIGHTED : COLOR_BLACK; - - string item_label = display_list[i]->text; - if (text_clip_at > 0 && item_label.length() > text_clip_at) - item_label.resize(text_clip_at); - - paint_text(fg_color, left_margin, y, item_label, bg_color); - int x = left_margin + display_list[i]->text.length() + 1; - display_extras(display_list[i]->elem, x, y); - } - - if (is_selected_column && allow_search) - { - y = gps->dimy - 3; - int32_t x = search_margin; - OutputHotkeyString(x, y, "Search" ,"S"); - OutputString(COLOR_WHITE, x, y, ": "); - OutputString(COLOR_WHITE, x, y, search_string); - OutputString(COLOR_LIGHTGREEN, x, y, "_"); - } - } - - void filterDisplay() - { - ListEntry *prev_selected = (getDisplayListSize() > 0) ? display_list[highlighted_index] : NULL; - display_list.clear(); - - search_string = toLower(search_string); - vector search_tokens; - if (!search_string.empty()) - split_string(&search_tokens, search_string, " "); - - for (size_t i = 0; i < list.size(); i++) - { - ListEntry *entry = &list[i]; - - bool include_item = true; - if (!search_string.empty()) - { - string item_string = toLower(list[i].text); - for (auto si = search_tokens.begin(); si != search_tokens.end(); si++) - { - if (!si->empty() && item_string.find(*si) == string::npos && - list[i].keywords.find(*si) == string::npos) - { - include_item = false; - break; - } - } - } - - if (include_item) - { - display_list.push_back(entry); - if (entry == prev_selected) - highlighted_index = display_list.size() - 1; - } - else if (auto_select) - { - entry->selected = false; - } - } - changeHighlight(0); - feed_changed_highlight = true; - } - - void selectDefaultEntry() - { - for (size_t i = 0; i < display_list.size(); i++) - { - if (display_list[i]->selected) - { - highlighted_index = i; - break; - } - } - } - - void centerSelection() - { - if (display_list.size() == 0) - return; - display_start_offset = highlighted_index - (display_max_rows / 2); - validateDisplayOffset(); - validateHighlight(); - } - - void validateHighlight() - { - set_to_limit(highlighted_index, display_list.size() - 1); - - if (highlighted_index < display_start_offset) - display_start_offset = highlighted_index; - else if (highlighted_index >= display_start_offset + display_max_rows) - display_start_offset = highlighted_index - display_max_rows + 1; - - if (auto_select || (!allow_null && list.size() == 1)) - display_list[highlighted_index]->selected = true; - - feed_changed_highlight = true; - } - - void changeHighlight(const int highlight_change, const int offset_shift = 0) - { - if (!initHighlightChange()) - return; - - highlighted_index += highlight_change + offset_shift * display_max_rows; - - display_start_offset += offset_shift * display_max_rows; - validateDisplayOffset(); - validateHighlight(); - } - - void validateDisplayOffset() - { - set_to_limit(display_start_offset, max(0, (int)(display_list.size())-display_max_rows)); - } - - void setHighlight(const int index) - { - if (!initHighlightChange()) - return; - - highlighted_index = index; - validateHighlight(); - } - - bool initHighlightChange() - { - if (display_list.size() == 0) - return false; - - if (auto_select && !multiselect) - { - for (auto it = list.begin(); it != list.end(); it++) - { - it->selected = false; - } - } - - return true; - } - - void toggleHighlighted() - { - if (display_list.size() == 0) - return; - - if (auto_select) - return; - - ListEntry *entry = display_list[highlighted_index]; - if (!multiselect || !allow_null) - { - int selected_count = 0; - for (size_t i = 0; i < list.size(); i++) - { - if (!multiselect && !entry->selected) - list[i].selected = false; - if (!allow_null && list[i].selected) - selected_count++; - } - - if (!allow_null && entry->selected && selected_count == 1) - return; - } - - entry->selected = !entry->selected; - } - - vector getSelectedElems(bool only_one = false) - { - vector results; - for (auto it = list.begin(); it != list.end(); it++) - { - if ((*it).selected) - { - results.push_back(it->elem); - if (only_one) - break; - } - } - - return results; - } - - T getFirstSelectedElem() - { - vector results = getSelectedElems(true); - if (results.size() == 0) - return (T)nullptr; - else - return results[0]; - } - - void clearSelection() - { - for_each_(list, clear_fn); - } - - void selectItem(const T elem) - { - int i = 0; - for (; i < display_list.size(); i++) - { - if (display_list[i]->elem == elem) - { - setHighlight(i); - break; - } - } - } - - void clearSearch() - { - search_string.clear(); - filterDisplay(); - } - - size_t getDisplayListSize() - { - return display_list.size(); - } - - vector*> &getDisplayList() - { - return display_list; - } - - size_t getBaseListSize() - { - return list.size(); - } - - bool feed(set *input) - { - feed_mouse_set_highlight = feed_changed_highlight = false; - if (input->count(interface_key::CURSOR_UP)) - { - changeHighlight(-1); - } - else if (input->count(interface_key::CURSOR_DOWN)) - { - changeHighlight(1); - } - else if (input->count(interface_key::STANDARDSCROLL_PAGEUP)) - { - changeHighlight(0, -1); - } - else if (input->count(interface_key::STANDARDSCROLL_PAGEDOWN)) - { - changeHighlight(0, 1); - } - else if (input->count(interface_key::SELECT) && !auto_select) - { - toggleHighlighted(); - } - else if (input->count(interface_key::CUSTOM_SHIFT_S)) - { - clearSearch(); - } - else if (enabler->tracking_on && gps->mouse_x != -1 && gps->mouse_y != -1 && enabler->mouse_lbut) - { - return setHighlightByMouse(); - } - else if (allow_search) - { - // Search query typing mode always on - df::interface_key last_token = get_string_key(input); - int charcode = Screen::keyToChar(last_token); - if ((charcode >= 96 && charcode <= 123) || charcode == 32) - { - // Standard character - search_string += char(charcode); - filterDisplay(); - centerSelection(); - } - else if (last_token == interface_key::STRING_A000) - { - // Backspace - if (search_string.length() > 0) - { - search_string.erase(search_string.length()-1); - filterDisplay(); - centerSelection(); - } - } - else - { - return false; - } - - return true; - } - else - { - return false; - } - - return true; - } - - bool setHighlightByMouse() - { - if (gps->mouse_y >= 3 && gps->mouse_y < display_max_rows + 3 && - gps->mouse_x >= left_margin && gps->mouse_x < left_margin + max_item_width) - { - int new_index = display_start_offset + gps->mouse_y - 3; - if (new_index < display_list.size()) - { - setHighlight(new_index); - feed_mouse_set_highlight = true; - } - - enabler->mouse_lbut = enabler->mouse_rbut = 0; - - return true; - } - - return false; - } - - void sort(bool force_sort = false) - { - if (force_sort || list.size() < 100) - std::sort(list.begin(), list.end(), sort_fn); - - filterDisplay(); - } - - void setTitle(const string t) - { - title = t; - if (title.length() > max_item_width) - max_item_width = title.length(); - } - - size_t getDisplayedListSize() - { - return display_list.size(); - } - -protected: - static void clear_fn(ListEntry &e) { e.selected = false; } - static bool sort_fn(ListEntry const& a, ListEntry const& b) { return a.text.compare(b.text) < 0; } - - vector> list; - vector*> display_list; - string search_string; - string title; - int display_max_rows; - int max_item_width; -}; - - From f387eb2960bdf32f47d580790bd8bb35a7303718 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jul 2015 09:34:04 -0400 Subject: [PATCH 35/61] Fix an issue where Ctrl-*-A and Ctrl-*-Z keybindings were not properly detected --- NEWS | 1 + library/Core.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 0b42a35e0..dd33be7a4 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,7 @@ DFHack Future More build information available in plugins Fixed a rare overflow issue that could cause crashes on Linux and OS X Stopped DF window from receiving input when unfocused on OS X + Fixed issues with keybindings involving Ctrl-A and Ctrl-Z Lua Scripts can be enabled with the built-in enable/disable commands A new function, reqscript(), is available as a safer alternative to script_environment() diff --git a/library/Core.cpp b/library/Core.cpp index 29644f535..65c87b292 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1917,7 +1917,7 @@ int UnicodeAwareSym(const SDL::KeyboardEvent& ke) } // convert A-Z to their a-z counterparts: - if('A' < unicode && unicode < 'Z') + if('A' <= unicode && unicode <= 'Z') { unicode += 'a' - 'A'; } From cc5045d6df4c055aed793719fb4c1a04a2296420 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jul 2015 10:32:19 -0400 Subject: [PATCH 36/61] Fix Alt-E/U/N bindings on OS X --- NEWS | 2 +- library/Core.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index dd33be7a4..8a3d81c4e 100644 --- a/NEWS +++ b/NEWS @@ -7,7 +7,7 @@ DFHack Future More build information available in plugins Fixed a rare overflow issue that could cause crashes on Linux and OS X Stopped DF window from receiving input when unfocused on OS X - Fixed issues with keybindings involving Ctrl-A and Ctrl-Z + Fixed issues with keybindings involving Ctrl-A and Ctrl-Z, as well as Alt-E/U/N on OS X Lua Scripts can be enabled with the built-in enable/disable commands A new function, reqscript(), is available as a safer alternative to script_environment() diff --git a/library/Core.cpp b/library/Core.cpp index 65c87b292..62b63411f 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1963,7 +1963,7 @@ int Core::DFH_SDL_Event(SDL::Event* ev) // Use unicode so Windows gives the correct value for the // user's Input Language - if((ke->ksym.unicode & 0xff80) == 0) + if(ke->ksym.unicode && ((ke->ksym.unicode & 0xff80) == 0)) { int key = UnicodeAwareSym(*ke); SelectHotkey(key, modstate); From 7fc682b199814caec1720330a06edb8e65dbbd45 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jul 2015 11:22:38 -0400 Subject: [PATCH 37/61] Fix dismissal of current screen when zooming to items --- plugins/stocks.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index 0f6039bfc..d6611e1de 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -802,6 +802,12 @@ public: return; Screen::dismiss(this); + auto vs = Gui::getCurViewscreen(true); + while (vs && !virtual_cast(vs)) + { + Screen::dismiss(vs); + vs = vs->parent; + } // Could be clever here, if item is in a container, to look inside the container. // But that's different for built containers vs bags/pots in stockpiles. send_key(interface_key::D_LOOK); From 4e45cc5bbb7d45eebcfc65ff53cc9b94a42ecfd7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jul 2015 13:44:32 -0400 Subject: [PATCH 38/61] stocks: Use ^ and $ to match the beginning/end of item names Closes #624 --- plugins/listcolumn.h | 54 +++++++++++------- plugins/stocks.cpp | 133 +++++++++++++++++++++++++++++++++++++++---- plugins/uicommon.h | 12 ++++ 3 files changed, 166 insertions(+), 33 deletions(-) diff --git a/plugins/listcolumn.h b/plugins/listcolumn.h index e90f2f5fb..0576ca4e4 100644 --- a/plugins/listcolumn.h +++ b/plugins/listcolumn.h @@ -134,6 +134,29 @@ public: } } + virtual void tokenizeSearch (vector *dest, const string search) + { + if (!search.empty()) + split_string(dest, search, " "); + } + + virtual bool showEntry(const ListEntry *entry, const vector &search_tokens) + { + if (!search_tokens.empty()) + { + string item_string = toLower(entry->text); + for (auto si = search_tokens.begin(); si != search_tokens.end(); si++) + { + if (!si->empty() && item_string.find(*si) == string::npos && + entry->keywords.find(*si) == string::npos) + { + return false; + } + } + } + return true; + } + void filterDisplay() { ListEntry *prev_selected = (getDisplayListSize() > 0) ? display_list[highlighted_index] : NULL; @@ -141,29 +164,13 @@ public: search_string = toLower(search_string); vector search_tokens; - if (!search_string.empty()) - split_string(&search_tokens, search_string, " "); + tokenizeSearch(&search_tokens, search_string); for (size_t i = 0; i < list.size(); i++) { ListEntry *entry = &list[i]; - bool include_item = true; - if (!search_string.empty()) - { - string item_string = toLower(list[i].text); - for (auto si = search_tokens.begin(); si != search_tokens.end(); si++) - { - if (!si->empty() && item_string.find(*si) == string::npos && - list[i].keywords.find(*si) == string::npos) - { - include_item = false; - break; - } - } - } - - if (include_item) + if (showEntry(entry, search_tokens)) { display_list.push_back(entry); if (entry == prev_selected) @@ -347,14 +354,19 @@ public: return list.size(); } + virtual bool validSearchInput (unsigned char c) + { + return (c >= 'a' && c <= 'z') || c == ' '; + } + bool feed(set *input) { feed_mouse_set_highlight = feed_changed_highlight = false; - if (input->count(interface_key::CURSOR_UP)) + if (input->count(interface_key::STANDARDSCROLL_UP)) { changeHighlight(-1); } - else if (input->count(interface_key::CURSOR_DOWN)) + else if (input->count(interface_key::STANDARDSCROLL_DOWN)) { changeHighlight(1); } @@ -383,7 +395,7 @@ public: // Search query typing mode always on df::interface_key last_token = get_string_key(input); int charcode = Screen::keyToChar(last_token); - if ((charcode >= 96 && charcode <= 123) || charcode == 32) + if (charcode >= 0 && validSearchInput((unsigned char)charcode)) { // Standard character search_string += char(charcode); diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index d6611e1de..85fedb9f2 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -599,8 +599,115 @@ class StockListColumn : public ListColumn OutputString(color, x, y, get_quality_name(quality)); } } + + virtual bool validSearchInput (unsigned char c) + { + switch (c) + { + case '(': + case ')': + return true; + break; + default: + break; + } + string &search_string = ListColumn::search_string; + if (c == '^' && !search_string.size()) + return true; + else if (c == '$' && search_string.size()) + { + if (search_string == "^") + return false; + if (search_string[search_string.size() - 1] != '$') + return true; + } + return ListColumn::validSearchInput(c); + } + + std::string getRawSearch(const std::string s) + { + string raw_search = s; + if (raw_search.size() && raw_search[0] == '^') + raw_search.erase(0, 1); + if (raw_search.size() && raw_search[raw_search.size() - 1] == '$') + raw_search.erase(raw_search.size() - 1, 1); + return toLower(raw_search); + } + + virtual void tokenizeSearch (vector *dest, const string search) + { + string raw_search = getRawSearch(search); + ListColumn::tokenizeSearch(dest, raw_search); + } + + virtual bool showEntry (const ListEntry *entry, const vector &search_tokens) + { + string &search_string = ListColumn::search_string; + if (!search_string.size()) + return true; + + bool match_start = false, match_end = false; + string raw_search = getRawSearch(search_string); + if (search_string.size() && search_string[0] == '^') + match_start = true; + if (search_string.size() && search_string[search_string.size() - 1] == '$') + match_end = true; + + if (!ListColumn::showEntry(entry, search_tokens)) + return false; + + string item_name = toLower(Items::getDescription(entry->elem->entries[0], 0, false)); + + if ((match_start || match_end) && raw_search.size() > item_name.size()) + return false; + if (match_start && item_name.compare(0, raw_search.size(), raw_search) != 0) + return false; + if (match_end && item_name.compare(item_name.size() - raw_search.size(), raw_search.size(), raw_search) != 0) + return false; + + return true; + } }; +class search_help : public dfhack_viewscreen +{ +public: + void feed (std::set *input) + { + if (input->count(interface_key::HELP)) + return; + if (Screen::isDismissed(this)) + return; + Screen::dismiss(this); + if (!input->count(interface_key::LEAVESCREEN) && !input->count(interface_key::SELECT)) + parent->feed(input); + } + void render() + { + static std::string text = + "\7 Flag names can be\n" + " searched for - e.g. job,\n" + " inventory, dump, forbid\n" + "\n" + "\7 Use ^ to match the start\n" + " of a name, and/or $ to\n" + " match the end of a name"; + if (Screen::isDismissed(this)) + return; + parent->render(); + int left_margin = gps->dimx - SIDEBAR_WIDTH; + int x = left_margin, y = 2; + Screen::fillRect(Screen::Pen(' ', 0, 0), left_margin - 1, 1, gps->dimx - 2, gps->dimy - 4); + Screen::fillRect(Screen::Pen(' ', 0, 0), left_margin - 1, 1, left_margin - 1, gps->dimy - 2); + OutputString(COLOR_WHITE, x, y, "Search help", true, left_margin); + ++y; + vector lines; + split_string(&lines, text, "\n"); + for (auto line = lines.begin(); line != lines.end(); ++line) + OutputString(COLOR_WHITE, x, y, line->c_str(), true, left_margin); + } + std::string getFocusString() { return "stocks_view/search_help"; } +}; class ViewscreenStocks : public dfhack_viewscreen { @@ -662,6 +769,10 @@ public: Screen::dismiss(this); return; } + else if (input->count(interface_key::HELP)) + { + Screen::show(new search_help); + } bool key_processed = false; switch (selected_column) @@ -921,19 +1032,18 @@ public: ++y; OutputHotkeyString(x, y, "Clear All", "Shift-C", true, left_margin); OutputHotkeyString(x, y, "Enable All", "Shift-E", true, left_margin); - OutputHotkeyString(x, y, "Toggle Grouping", "TAB", true, left_margin); + OutputHotkeyString(x, y, "Toggle Grouping", interface_key::CHANGETAB, true, left_margin); ++y; - OutputString(COLOR_WHITE, x, y, "Qual: "); - OutputHotkeyString(x, y, "Min: ", "-+"); - OutputString(COLOR_BROWN, x, y, get_quality_name(min_quality), false, left_margin); - ++x; - OutputHotkeyString(x, y, "Max: ", "/*"); + OutputHotkeyString(x, y, "Min Qual: ", "-+"); + OutputString(COLOR_BROWN, x, y, get_quality_name(min_quality), true, left_margin); + OutputHotkeyString(x, y, "Max Qual: ", "/*"); OutputString(COLOR_BROWN, x, y, get_quality_name(max_quality), true, left_margin); OutputHotkeyString(x, y, "Min Wear: ", "Shift-W"); OutputString(COLOR_BROWN, x, y, int_to_string(min_wear), true, left_margin); - ++y; + if (gps->dimy > 27) + ++y; OutputString(COLOR_BROWN, x, y, "Actions ("); OutputString(COLOR_LIGHTGREEN, x, y, int_to_string(items_column.getDisplayedListSize())); OutputString(COLOR_BROWN, x, y, " Items)", true, left_margin); @@ -941,14 +1051,13 @@ public: OutputHotkeyString(x, y, "Dump", "-D", true, left_margin); OutputHotkeyString(x, y, "Forbid ", "Shift-F", false, left_margin); OutputHotkeyString(x, y, "Melt", "-M", true, left_margin); + OutputHotkeyString(x, y, "Mark for Trade", "Shift-T", true, left_margin, + depot_info.canTrade() ? COLOR_WHITE : COLOR_DARKGREY); OutputHotkeyString(x, y, "Apply to: ", "Shift-A"); OutputString(COLOR_BROWN, x, y, (apply_to_all) ? "Listed" : "Selected", true, left_margin); - if (depot_info.canTrade()) - OutputHotkeyString(x, y, "Mark for Trade", "Shift-T", true, left_margin); - y = gps->dimy - 5; - OutputString(COLOR_LIGHTRED, x, y, "Flag names can also", true, left_margin); - OutputString(COLOR_LIGHTRED, x, y, "be searched for", true, left_margin); + y = gps->dimy - 4; + OutputHotkeyString(x, y, "Search help", interface_key::HELP, true, left_margin); } std::string getFocusString() { return "stocks_view"; } diff --git a/plugins/uicommon.h b/plugins/uicommon.h index a54b3aa04..7182ee2f2 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -112,6 +112,12 @@ static void OutputHotkeyString(int &x, int &y, const char *text, const char *hot OutputString(text_color, x, y, display, newline, left_margin); } +static void OutputHotkeyString(int &x, int &y, const char *text, df::interface_key hotkey, + bool newline = false, int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN) +{ + OutputHotkeyString(x, y, text, DFHack::Screen::getKeyDisplay(hotkey).c_str(), newline, left_margin, text_color, hotkey_color); +} + static void OutputLabelString(int &x, int &y, const char *text, const char *hotkey, const string &label, bool newline = false, int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN) { @@ -142,6 +148,12 @@ static void OutputToggleString(int &x, int &y, const char *text, const char *hot OutputString(COLOR_GREY, x, y, "Off", newline, left_margin); } +static void OutputToggleString(int &x, int &y, const char *text, df::interface_key hotkey, bool state, bool newline = true, + int left_margin = 0, int8_t color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN) +{ + OutputToggleString(x, y, text, DFHack::Screen::getKeyDisplay(hotkey).c_str(), state, newline, left_margin, color, hotkey_color); +} + inline string int_to_string(const int n) { return static_cast( &(ostringstream() << n) )->str(); From a90f3c3d6752d8fb0eba0c355cae98e436d6dd80 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jul 2015 14:17:14 -0400 Subject: [PATCH 39/61] stockpiles: Restrict characters in filenames when saving Previously, it was possible to save outside of the stocksettings directory or fail to save in a nonexistent subdirectory (e.g. when a stockpile name had slashes in it). Resolves #621 --- plugins/lua/stockpiles.lua | 48 +++++++++++++++++++++++++++++-- plugins/stockpiles/stockpiles.cpp | 6 +--- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/plugins/lua/stockpiles.lua b/plugins/lua/stockpiles.lua index 815a551b7..ca8c28cd4 100644 --- a/plugins/lua/stockpiles.lua +++ b/plugins/lua/stockpiles.lua @@ -96,6 +96,48 @@ function tablify(iterableObject) return t end +local filename_invalid_regex = '[^A-Za-z0-9 ._-]' + +function valid_filename(filename) + return not filename:match(filename_invalid_regex) +end + +function sanitize_filename(filename) + local ret = '' + for i = 1, #filename do + local ch = filename:sub(i, i) + if valid_filename(ch) then + ret = ret .. ch + else + ret = ret .. '-' + end + end + return ret +end + +FilenameInputBox = defclass(FilenameInputBox, dlg.InputBox) +function FilenameInputBox:onInput(keys) + if not valid_filename(string.char(keys._STRING or 0)) and not keys.STRING_A000 then + keys._STRING = nil + end + FilenameInputBox.super.onInput(self, keys) +end + +function showFilenameInputPrompt(title, text, tcolor, input, min_width) + FilenameInputBox{ + frame_title = title, + text = text, + text_pen = tcolor, + input = input, + frame_width = min_width, + on_input = script.mkresume(true), + on_cancel = script.mkresume(false), + on_close = script.qresume(nil) + }:show() + + return script.wait() +end + function load_settings() init() local path = get_path() @@ -132,16 +174,16 @@ function save_settings(stockpile) if #suggested == 0 then suggested = 'Stock1' end + suggested = sanitize_filename(suggested) local path = get_path() - local sok,filename = script.showInputPrompt('Stockpile Settings', 'Enter stockpile name', COLOR_WHITE, suggested) + local sok,filename = showFilenameInputPrompt('Stockpile Settings', 'Enter filename', COLOR_WHITE, suggested) if sok then - if filename == nil or filename == '' then + if filename == nil or filename == '' or not valid_filename(filename) then script.showMessage('Stockpile Settings', 'Invalid File Name', COLOR_RED) else if not dfhack.filesystem.exists(path) then dfhack.filesystem.mkdir(path) end - print("saving...", path..'/'..filename) stockpiles_save(path..'/'..filename) end end diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp index f02d19e45..bfba7e1e4 100644 --- a/plugins/stockpiles/stockpiles.cpp +++ b/plugins/stockpiles/stockpiles.cpp @@ -246,7 +246,7 @@ static command_result savestock ( color_ostream &out, vector & paramete if ( !is_dfstockfile ( file ) ) file += ".dfstock"; if ( !cereal.serialize_to_file ( file ) ) { - out.printerr ( "serialize failed\n" ); + out.printerr ( "could not save to %s\n", file.c_str() ); return CR_FAILURE; } return CR_OK; @@ -510,11 +510,9 @@ static int stockpiles_list_settings ( lua_State *L ) static void stockpiles_load ( color_ostream &out, std::string filename ) { - out << "stockpiles_load " << filename << " "; std::vector params; params.push_back ( filename ); command_result r = loadstock ( out, params ); - out << " result = "<< r << endl; if ( r != CR_OK ) show_message_box ( "Stockpile Settings Error", "Couldn't load. Does the folder exist?", true ); } @@ -522,11 +520,9 @@ static void stockpiles_load ( color_ostream &out, std::string filename ) static void stockpiles_save ( color_ostream &out, std::string filename ) { - out << "stockpiles_save " << filename << " "; std::vector params; params.push_back ( filename ); command_result r = savestock ( out, params ); - out << " result = "<< r << endl; if ( r != CR_OK ) show_message_box ( "Stockpile Settings Error", "Couldn't save. Does the folder exist?", true ); } From f3080b4fb54b87dbe412118b8a6725ea2699e1fc Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jul 2015 14:21:03 -0400 Subject: [PATCH 40/61] Update NEWS --- NEWS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS b/NEWS index 8a3d81c4e..25a9870a5 100644 --- a/NEWS +++ b/NEWS @@ -35,6 +35,7 @@ DFHack Future gui/hack-wish now properly assigns quality to items. gui/gm-editor handles lua tables properly search: fixed crash in unit list after cancelling a job + stockpiles: now checks/sanitizes filenames when saving stocks: fixed a crash when right-clicking steam-engine: - fixed a crash on arena load @@ -70,6 +71,7 @@ DFHack Future - Supports noble suggestion screen (e.g. suggesting a baron) - Supports fortress mode loo[k] menu - Recognizes ? and ; keys + stocks: can now match beginning and end of item names teleport: Fixed cursor recognition tweak: - debug output now logged to stderr.log instead of console - makes DFHack start faster From d02e67267a2dd5a74834ac7f22c7924660d70bb7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jul 2015 14:47:55 -0400 Subject: [PATCH 41/61] Replace jsonxx with jsoncpp --- .gitmodules | 3 - CMakeLists.txt | 5 +- LICENSE | 27 +- depends/CMakeLists.txt | 2 +- depends/jsoncpp/CMakeLists.txt | 2 + depends/jsoncpp/jsoncpp.cpp | 5089 ++++++++++++++++++++++++++++++++ depends/jsoncpp/jsoncpp.h | 2031 +++++++++++++ depends/jsonxx | 1 - library/CMakeLists.txt | 2 +- plugins/CMakeLists.txt | 2 +- plugins/dwarfmonitor.cpp | 2 - 11 files changed, 7140 insertions(+), 26 deletions(-) create mode 100644 depends/jsoncpp/CMakeLists.txt create mode 100644 depends/jsoncpp/jsoncpp.cpp create mode 100644 depends/jsoncpp/jsoncpp.h delete mode 160000 depends/jsonxx diff --git a/.gitmodules b/.gitmodules index 212095b0c..b20d189ec 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,6 +13,3 @@ [submodule "depends/clsocket"] path = depends/clsocket url = git://github.com/DFHack/clsocket.git -[submodule "depends/jsonxx"] - path = depends/jsonxx - url = git://github.com/lethosor/jsonxx.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 64cf0717e..5a8dabd13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,8 +53,7 @@ if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") endif() # make sure all the necessary submodules have been set up -if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhack_SOURCE_DIR}/depends/clsocket/CMakeLists.txt - OR NOT EXISTS ${dfhack_SOURCE_DIR}/depends/jsonxx/CMakeLists.txt) +if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhack_SOURCE_DIR}/depends/clsocket/CMakeLists.txt) message(FATAL_ERROR "One or more required submodules could not be found! Run 'git submodule update --init' from the root DFHack directory. (See the section 'Getting the Code' in Compile.rst or Compile.html)") endif() @@ -136,7 +135,7 @@ find_package(ZLIB REQUIRED) include_directories(depends/protobuf) include_directories(depends/lua/include) include_directories(depends/md5) -include_directories(depends/jsonxx) +include_directories(depends/jsoncpp) include_directories(depends/tinyxml) include_directories(depends/tthread) include_directories(${ZLIB_INCLUDE_DIRS}) diff --git a/LICENSE b/LICENSE index 3908e04d6..fd4f7abcb 100644 --- a/LICENSE +++ b/LICENSE @@ -164,30 +164,29 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- -jsonxx license +jsoncpp license -Copyright (c) 2010 Hong Jiang +Copyright (c) 2007-2010 Baptiste Lepilleur 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: +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. +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. -------------------------------------------------------------------------------- JSON.lua license diff --git a/depends/CMakeLists.txt b/depends/CMakeLists.txt index 804ca09de..bf0345bfb 100644 --- a/depends/CMakeLists.txt +++ b/depends/CMakeLists.txt @@ -4,7 +4,7 @@ add_subdirectory(md5) add_subdirectory(protobuf) add_subdirectory(tinyxml) add_subdirectory(tthread) -add_subdirectory(jsonxx) +add_subdirectory(jsoncpp) # build clsocket static and only as a dependency. Setting those options here overrides its own default settings. OPTION(CLSOCKET_SHARED "Build clsocket lib as shared." OFF) OPTION(CLSOCKET_DEP_ONLY "Build for use inside other CMake projects as dependency." ON) diff --git a/depends/jsoncpp/CMakeLists.txt b/depends/jsoncpp/CMakeLists.txt new file mode 100644 index 000000000..112903ee3 --- /dev/null +++ b/depends/jsoncpp/CMakeLists.txt @@ -0,0 +1,2 @@ +PROJECT(jsoncpp) +ADD_LIBRARY(jsoncpp STATIC jsoncpp.cpp) diff --git a/depends/jsoncpp/jsoncpp.cpp b/depends/jsoncpp/jsoncpp.cpp new file mode 100644 index 000000000..eb38217ef --- /dev/null +++ b/depends/jsoncpp/jsoncpp.cpp @@ -0,0 +1,5089 @@ +/// Json-cpp amalgated source (http://jsoncpp.sourceforge.net/). +/// It is intended to be used with #include "json/json.h" + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + +/* +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +The author (Baptiste Lepilleur) explicitly disclaims copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur, and is +released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur + +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. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. + +*/ + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + + + + + + +#include "jsoncpp.h" + +#ifndef JSON_IS_AMALGAMATION +#error "Compile with -I PATH_TO_JSON_DIRECTORY" +#endif + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_tool.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef LIB_JSONCPP_JSON_TOOL_H_INCLUDED +#define LIB_JSONCPP_JSON_TOOL_H_INCLUDED + +/* This header provides common string manipulation support, such as UTF-8, + * portable conversion from/to string... + * + * It is an internal header that must not be exposed. + */ + +namespace Json { + +/// Converts a unicode code-point to UTF-8. +static inline std::string codePointToUTF8(unsigned int cp) { + std::string result; + + // based on description from http://en.wikipedia.org/wiki/UTF-8 + + if (cp <= 0x7f) { + result.resize(1); + result[0] = static_cast(cp); + } else if (cp <= 0x7FF) { + result.resize(2); + result[1] = static_cast(0x80 | (0x3f & cp)); + result[0] = static_cast(0xC0 | (0x1f & (cp >> 6))); + } else if (cp <= 0xFFFF) { + result.resize(3); + result[2] = static_cast(0x80 | (0x3f & cp)); + result[1] = static_cast(0x80 | (0x3f & (cp >> 6))); + result[0] = static_cast(0xE0 | (0xf & (cp >> 12))); + } else if (cp <= 0x10FFFF) { + result.resize(4); + result[3] = static_cast(0x80 | (0x3f & cp)); + result[2] = static_cast(0x80 | (0x3f & (cp >> 6))); + result[1] = static_cast(0x80 | (0x3f & (cp >> 12))); + result[0] = static_cast(0xF0 | (0x7 & (cp >> 18))); + } + + return result; +} + +/// Returns true if ch is a control character (in range [1,31]). +static inline bool isControlCharacter(char ch) { return ch > 0 && ch <= 0x1F; } + +enum { + /// Constant that specify the size of the buffer that must be passed to + /// uintToString. + uintToStringBufferSize = 3 * sizeof(LargestUInt) + 1 +}; + +// Defines a char buffer for use with uintToString(). +typedef char UIntToStringBuffer[uintToStringBufferSize]; + +/** Converts an unsigned integer to string. + * @param value Unsigned interger to convert to string + * @param current Input/Output string buffer. + * Must have at least uintToStringBufferSize chars free. + */ +static inline void uintToString(LargestUInt value, char*& current) { + *--current = 0; + do { + *--current = static_cast(value % 10U + static_cast('0')); + value /= 10; + } while (value != 0); +} + +/** Change ',' to '.' everywhere in buffer. + * + * We had a sophisticated way, but it did not work in WinCE. + * @see https://github.com/open-source-parsers/jsoncpp/pull/9 + */ +static inline void fixNumericLocale(char* begin, char* end) { + while (begin < end) { + if (*begin == ',') { + *begin = '.'; + } + ++begin; + } +} + +} // namespace Json { + +#endif // LIB_JSONCPP_JSON_TOOL_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_tool.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_reader.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2011 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include "json_tool.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1500 // VC++ 8.0 and below +#define snprintf _snprintf +#endif + +#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0 +// Disable warning about strdup being deprecated. +#pragma warning(disable : 4996) +#endif + +static int const stackLimit_g = 1000; +static int stackDepth_g = 0; // see readValue() + +namespace Json { + +#if __cplusplus >= 201103L +typedef std::unique_ptr CharReaderPtr; +#else +typedef std::auto_ptr CharReaderPtr; +#endif + +// Implementation of class Features +// //////////////////////////////// + +Features::Features() + : allowComments_(true), strictRoot_(false), + allowDroppedNullPlaceholders_(false), allowNumericKeys_(false) {} + +Features Features::all() { return Features(); } + +Features Features::strictMode() { + Features features; + features.allowComments_ = false; + features.strictRoot_ = true; + features.allowDroppedNullPlaceholders_ = false; + features.allowNumericKeys_ = false; + return features; +} + +// Implementation of class Reader +// //////////////////////////////// + +static bool containsNewLine(Reader::Location begin, Reader::Location end) { + for (; begin < end; ++begin) + if (*begin == '\n' || *begin == '\r') + return true; + return false; +} + +// Class Reader +// ////////////////////////////////////////////////////////////////// + +Reader::Reader() + : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), + lastValue_(), commentsBefore_(), features_(Features::all()), + collectComments_() {} + +Reader::Reader(const Features& features) + : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), + lastValue_(), commentsBefore_(), features_(features), collectComments_() { +} + +bool +Reader::parse(const std::string& document, Value& root, bool collectComments) { + document_ = document; + const char* begin = document_.c_str(); + const char* end = begin + document_.length(); + return parse(begin, end, root, collectComments); +} + +bool Reader::parse(std::istream& sin, Value& root, bool collectComments) { + // std::istream_iterator begin(sin); + // std::istream_iterator end; + // Those would allow streamed input from a file, if parse() were a + // template function. + + // Since std::string is reference-counted, this at least does not + // create an extra copy. + std::string doc; + std::getline(sin, doc, (char)EOF); + return parse(doc, root, collectComments); +} + +bool Reader::parse(const char* beginDoc, + const char* endDoc, + Value& root, + bool collectComments) { + if (!features_.allowComments_) { + collectComments = false; + } + + begin_ = beginDoc; + end_ = endDoc; + collectComments_ = collectComments; + current_ = begin_; + lastValueEnd_ = 0; + lastValue_ = 0; + commentsBefore_ = ""; + errors_.clear(); + while (!nodes_.empty()) + nodes_.pop(); + nodes_.push(&root); + + stackDepth_g = 0; // Yes, this is bad coding, but options are limited. + bool successful = readValue(); + Token token; + skipCommentTokens(token); + if (collectComments_ && !commentsBefore_.empty()) + root.setComment(commentsBefore_, commentAfter); + if (features_.strictRoot_) { + if (!root.isArray() && !root.isObject()) { + // Set error location to start of doc, ideally should be first token found + // in doc + token.type_ = tokenError; + token.start_ = beginDoc; + token.end_ = endDoc; + addError( + "A valid JSON document must be either an array or an object value.", + token); + return false; + } + } + return successful; +} + +bool Reader::readValue() { + // This is a non-reentrant way to support a stackLimit. Terrible! + // But this deprecated class has a security problem: Bad input can + // cause a seg-fault. This seems like a fair, binary-compatible way + // to prevent the problem. + if (stackDepth_g >= stackLimit_g) throwRuntimeError("Exceeded stackLimit in readValue()."); + ++stackDepth_g; + + Token token; + skipCommentTokens(token); + bool successful = true; + + if (collectComments_ && !commentsBefore_.empty()) { + currentValue().setComment(commentsBefore_, commentBefore); + commentsBefore_ = ""; + } + + switch (token.type_) { + case tokenObjectBegin: + successful = readObject(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenArrayBegin: + successful = readArray(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenNumber: + successful = decodeNumber(token); + break; + case tokenString: + successful = decodeString(token); + break; + case tokenTrue: + { + Value v(true); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenFalse: + { + Value v(false); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenNull: + { + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenArraySeparator: + case tokenObjectEnd: + case tokenArrayEnd: + if (features_.allowDroppedNullPlaceholders_) { + // "Un-read" the current token and mark the current value as a null + // token. + current_--; + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(current_ - begin_ - 1); + currentValue().setOffsetLimit(current_ - begin_); + break; + } // Else, fall through... + default: + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return addError("Syntax error: value, object or array expected.", token); + } + + if (collectComments_) { + lastValueEnd_ = current_; + lastValue_ = ¤tValue(); + } + + --stackDepth_g; + return successful; +} + +void Reader::skipCommentTokens(Token& token) { + if (features_.allowComments_) { + do { + readToken(token); + } while (token.type_ == tokenComment); + } else { + readToken(token); + } +} + +bool Reader::readToken(Token& token) { + skipSpaces(); + token.start_ = current_; + Char c = getNextChar(); + bool ok = true; + switch (c) { + case '{': + token.type_ = tokenObjectBegin; + break; + case '}': + token.type_ = tokenObjectEnd; + break; + case '[': + token.type_ = tokenArrayBegin; + break; + case ']': + token.type_ = tokenArrayEnd; + break; + case '"': + token.type_ = tokenString; + ok = readString(); + break; + case '/': + token.type_ = tokenComment; + ok = readComment(); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + token.type_ = tokenNumber; + readNumber(); + break; + case 't': + token.type_ = tokenTrue; + ok = match("rue", 3); + break; + case 'f': + token.type_ = tokenFalse; + ok = match("alse", 4); + break; + case 'n': + token.type_ = tokenNull; + ok = match("ull", 3); + break; + case ',': + token.type_ = tokenArraySeparator; + break; + case ':': + token.type_ = tokenMemberSeparator; + break; + case 0: + token.type_ = tokenEndOfStream; + break; + default: + ok = false; + break; + } + if (!ok) + token.type_ = tokenError; + token.end_ = current_; + return true; +} + +void Reader::skipSpaces() { + while (current_ != end_) { + Char c = *current_; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + ++current_; + else + break; + } +} + +bool Reader::match(Location pattern, int patternLength) { + if (end_ - current_ < patternLength) + return false; + int index = patternLength; + while (index--) + if (current_[index] != pattern[index]) + return false; + current_ += patternLength; + return true; +} + +bool Reader::readComment() { + Location commentBegin = current_ - 1; + Char c = getNextChar(); + bool successful = false; + if (c == '*') + successful = readCStyleComment(); + else if (c == '/') + successful = readCppStyleComment(); + if (!successful) + return false; + + if (collectComments_) { + CommentPlacement placement = commentBefore; + if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { + if (c != '*' || !containsNewLine(commentBegin, current_)) + placement = commentAfterOnSameLine; + } + + addComment(commentBegin, current_, placement); + } + return true; +} + +static std::string normalizeEOL(Reader::Location begin, Reader::Location end) { + std::string normalized; + normalized.reserve(end - begin); + Reader::Location current = begin; + while (current != end) { + char c = *current++; + if (c == '\r') { + if (current != end && *current == '\n') + // convert dos EOL + ++current; + // convert Mac EOL + normalized += '\n'; + } else { + normalized += c; + } + } + return normalized; +} + +void +Reader::addComment(Location begin, Location end, CommentPlacement placement) { + assert(collectComments_); + const std::string& normalized = normalizeEOL(begin, end); + if (placement == commentAfterOnSameLine) { + assert(lastValue_ != 0); + lastValue_->setComment(normalized, placement); + } else { + commentsBefore_ += normalized; + } +} + +bool Reader::readCStyleComment() { + while (current_ != end_) { + Char c = getNextChar(); + if (c == '*' && *current_ == '/') + break; + } + return getNextChar() == '/'; +} + +bool Reader::readCppStyleComment() { + while (current_ != end_) { + Char c = getNextChar(); + if (c == '\n') + break; + if (c == '\r') { + // Consume DOS EOL. It will be normalized in addComment. + if (current_ != end_ && *current_ == '\n') + getNextChar(); + // Break on Moc OS 9 EOL. + break; + } + } + return true; +} + +void Reader::readNumber() { + const char *p = current_; + char c = '0'; // stopgap for already consumed character + // integral part + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : 0; + // fractional part + if (c == '.') { + c = (current_ = p) < end_ ? *p++ : 0; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : 0; + } + // exponential part + if (c == 'e' || c == 'E') { + c = (current_ = p) < end_ ? *p++ : 0; + if (c == '+' || c == '-') + c = (current_ = p) < end_ ? *p++ : 0; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : 0; + } +} + +bool Reader::readString() { + Char c = 0; + while (current_ != end_) { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '"') + break; + } + return c == '"'; +} + +bool Reader::readObject(Token& tokenStart) { + Token tokenName; + std::string name; + Value init(objectValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(tokenStart.start_ - begin_); + while (readToken(tokenName)) { + bool initialTokenOk = true; + while (tokenName.type_ == tokenComment && initialTokenOk) + initialTokenOk = readToken(tokenName); + if (!initialTokenOk) + break; + if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object + return true; + name = ""; + if (tokenName.type_ == tokenString) { + if (!decodeString(tokenName, name)) + return recoverFromError(tokenObjectEnd); + } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { + Value numberName; + if (!decodeNumber(tokenName, numberName)) + return recoverFromError(tokenObjectEnd); + name = numberName.asString(); + } else { + break; + } + + Token colon; + if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { + return addErrorAndRecover( + "Missing ':' after object member name", colon, tokenObjectEnd); + } + Value& value = currentValue()[name]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenObjectEnd); + + Token comma; + if (!readToken(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && + comma.type_ != tokenComment)) { + return addErrorAndRecover( + "Missing ',' or '}' in object declaration", comma, tokenObjectEnd); + } + bool finalizeTokenOk = true; + while (comma.type_ == tokenComment && finalizeTokenOk) + finalizeTokenOk = readToken(comma); + if (comma.type_ == tokenObjectEnd) + return true; + } + return addErrorAndRecover( + "Missing '}' or object member name", tokenName, tokenObjectEnd); +} + +bool Reader::readArray(Token& tokenStart) { + Value init(arrayValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(tokenStart.start_ - begin_); + skipSpaces(); + if (*current_ == ']') // empty array + { + Token endArray; + readToken(endArray); + return true; + } + int index = 0; + for (;;) { + Value& value = currentValue()[index++]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenArrayEnd); + + Token token; + // Accept Comment after last item in the array. + ok = readToken(token); + while (token.type_ == tokenComment && ok) { + ok = readToken(token); + } + bool badTokenType = + (token.type_ != tokenArraySeparator && token.type_ != tokenArrayEnd); + if (!ok || badTokenType) { + return addErrorAndRecover( + "Missing ',' or ']' in array declaration", token, tokenArrayEnd); + } + if (token.type_ == tokenArrayEnd) + break; + } + return true; +} + +bool Reader::decodeNumber(Token& token) { + Value decoded; + if (!decodeNumber(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool Reader::decodeNumber(Token& token, Value& decoded) { + // Attempts to parse the number as an integer. If the number is + // larger than the maximum supported value of an integer then + // we decode the number as a double. + Location current = token.start_; + bool isNegative = *current == '-'; + if (isNegative) + ++current; + // TODO: Help the compiler do the div and mod at compile time or get rid of them. + Value::LargestUInt maxIntegerValue = + isNegative ? Value::LargestUInt(Value::maxLargestInt) + 1 + : Value::maxLargestUInt; + Value::LargestUInt threshold = maxIntegerValue / 10; + Value::LargestUInt value = 0; + while (current < token.end_) { + Char c = *current++; + if (c < '0' || c > '9') + return decodeDouble(token, decoded); + Value::UInt digit(c - '0'); + if (value >= threshold) { + // We've hit or exceeded the max value divided by 10 (rounded down). If + // a) we've only just touched the limit, b) this is the last digit, and + // c) it's small enough to fit in that rounding delta, we're okay. + // Otherwise treat this number as a double to avoid overflow. + if (value > threshold || current != token.end_ || + digit > maxIntegerValue % 10) { + return decodeDouble(token, decoded); + } + } + value = value * 10 + digit; + } + if (isNegative && value == maxIntegerValue) + decoded = Value::minLargestInt; + else if (isNegative) + decoded = -Value::LargestInt(value); + else if (value <= Value::LargestUInt(Value::maxInt)) + decoded = Value::LargestInt(value); + else + decoded = value; + return true; +} + +bool Reader::decodeDouble(Token& token) { + Value decoded; + if (!decodeDouble(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool Reader::decodeDouble(Token& token, Value& decoded) { + double value = 0; + std::string buffer(token.start_, token.end_); + std::istringstream is(buffer); + if (!(is >> value)) + return addError("'" + std::string(token.start_, token.end_) + + "' is not a number.", + token); + decoded = value; + return true; +} + +bool Reader::decodeString(Token& token) { + std::string decoded_string; + if (!decodeString(token, decoded_string)) + return false; + Value decoded(decoded_string); + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool Reader::decodeString(Token& token, std::string& decoded) { + decoded.reserve(token.end_ - token.start_ - 2); + Location current = token.start_ + 1; // skip '"' + Location end = token.end_ - 1; // do not include '"' + while (current != end) { + Char c = *current++; + if (c == '"') + break; + else if (c == '\\') { + if (current == end) + return addError("Empty escape sequence in string", token, current); + Char escape = *current++; + switch (escape) { + case '"': + decoded += '"'; + break; + case '/': + decoded += '/'; + break; + case '\\': + decoded += '\\'; + break; + case 'b': + decoded += '\b'; + break; + case 'f': + decoded += '\f'; + break; + case 'n': + decoded += '\n'; + break; + case 'r': + decoded += '\r'; + break; + case 't': + decoded += '\t'; + break; + case 'u': { + unsigned int unicode; + if (!decodeUnicodeCodePoint(token, current, end, unicode)) + return false; + decoded += codePointToUTF8(unicode); + } break; + default: + return addError("Bad escape sequence in string", token, current); + } + } else { + decoded += c; + } + } + return true; +} + +bool Reader::decodeUnicodeCodePoint(Token& token, + Location& current, + Location end, + unsigned int& unicode) { + + if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) + return false; + if (unicode >= 0xD800 && unicode <= 0xDBFF) { + // surrogate pairs + if (end - current < 6) + return addError( + "additional six characters expected to parse unicode surrogate pair.", + token, + current); + unsigned int surrogatePair; + if (*(current++) == '\\' && *(current++) == 'u') { + if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { + unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); + } else + return false; + } else + return addError("expecting another \\u token to begin the second half of " + "a unicode surrogate pair", + token, + current); + } + return true; +} + +bool Reader::decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& unicode) { + if (end - current < 4) + return addError( + "Bad unicode escape sequence in string: four digits expected.", + token, + current); + unicode = 0; + for (int index = 0; index < 4; ++index) { + Char c = *current++; + unicode *= 16; + if (c >= '0' && c <= '9') + unicode += c - '0'; + else if (c >= 'a' && c <= 'f') + unicode += c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + unicode += c - 'A' + 10; + else + return addError( + "Bad unicode escape sequence in string: hexadecimal digit expected.", + token, + current); + } + return true; +} + +bool +Reader::addError(const std::string& message, Token& token, Location extra) { + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = extra; + errors_.push_back(info); + return false; +} + +bool Reader::recoverFromError(TokenType skipUntilToken) { + int errorCount = int(errors_.size()); + Token skip; + for (;;) { + if (!readToken(skip)) + errors_.resize(errorCount); // discard errors caused by recovery + if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) + break; + } + errors_.resize(errorCount); + return false; +} + +bool Reader::addErrorAndRecover(const std::string& message, + Token& token, + TokenType skipUntilToken) { + addError(message, token); + return recoverFromError(skipUntilToken); +} + +Value& Reader::currentValue() { return *(nodes_.top()); } + +Reader::Char Reader::getNextChar() { + if (current_ == end_) + return 0; + return *current_++; +} + +void Reader::getLocationLineAndColumn(Location location, + int& line, + int& column) const { + Location current = begin_; + Location lastLineStart = current; + line = 0; + while (current < location && current != end_) { + Char c = *current++; + if (c == '\r') { + if (*current == '\n') + ++current; + lastLineStart = current; + ++line; + } else if (c == '\n') { + lastLineStart = current; + ++line; + } + } + // column & line start at 1 + column = int(location - lastLineStart) + 1; + ++line; +} + +std::string Reader::getLocationLineAndColumn(Location location) const { + int line, column; + getLocationLineAndColumn(location, line, column); + char buffer[18 + 16 + 16 + 1]; +#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) +#if defined(WINCE) + _snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); +#else + sprintf_s(buffer, sizeof(buffer), "Line %d, Column %d", line, column); +#endif +#else + snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); +#endif + return buffer; +} + +// Deprecated. Preserved for backward compatibility +std::string Reader::getFormatedErrorMessages() const { + return getFormattedErrorMessages(); +} + +std::string Reader::getFormattedErrorMessages() const { + std::string formattedMessage; + for (Errors::const_iterator itError = errors_.begin(); + itError != errors_.end(); + ++itError) { + const ErrorInfo& error = *itError; + formattedMessage += + "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; + formattedMessage += " " + error.message_ + "\n"; + if (error.extra_) + formattedMessage += + "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; + } + return formattedMessage; +} + +std::vector Reader::getStructuredErrors() const { + std::vector allErrors; + for (Errors::const_iterator itError = errors_.begin(); + itError != errors_.end(); + ++itError) { + const ErrorInfo& error = *itError; + Reader::StructuredError structured; + structured.offset_start = error.token_.start_ - begin_; + structured.offset_limit = error.token_.end_ - begin_; + structured.message = error.message_; + allErrors.push_back(structured); + } + return allErrors; +} + +bool Reader::pushError(const Value& value, const std::string& message) { + size_t length = end_ - begin_; + if(value.getOffsetStart() > length + || value.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = end_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = 0; + errors_.push_back(info); + return true; +} + +bool Reader::pushError(const Value& value, const std::string& message, const Value& extra) { + size_t length = end_ - begin_; + if(value.getOffsetStart() > length + || value.getOffsetLimit() > length + || extra.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = begin_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = begin_ + extra.getOffsetStart(); + errors_.push_back(info); + return true; +} + +bool Reader::good() const { + return !errors_.size(); +} + +// exact copy of Features +class OurFeatures { +public: + static OurFeatures all(); + OurFeatures(); + bool allowComments_; + bool strictRoot_; + bool allowDroppedNullPlaceholders_; + bool allowNumericKeys_; + bool allowSingleQuotes_; + bool failIfExtra_; + bool rejectDupKeys_; + int stackLimit_; +}; // OurFeatures + +// exact copy of Implementation of class Features +// //////////////////////////////// + +OurFeatures::OurFeatures() + : allowComments_(true), strictRoot_(false) + , allowDroppedNullPlaceholders_(false), allowNumericKeys_(false) + , allowSingleQuotes_(false) + , failIfExtra_(false) +{ +} + +OurFeatures OurFeatures::all() { return OurFeatures(); } + +// Implementation of class Reader +// //////////////////////////////// + +// exact copy of Reader, renamed to OurReader +class OurReader { +public: + typedef char Char; + typedef const Char* Location; + struct StructuredError { + size_t offset_start; + size_t offset_limit; + std::string message; + }; + + OurReader(OurFeatures const& features); + bool parse(const char* beginDoc, + const char* endDoc, + Value& root, + bool collectComments = true); + std::string getFormattedErrorMessages() const; + std::vector getStructuredErrors() const; + bool pushError(const Value& value, const std::string& message); + bool pushError(const Value& value, const std::string& message, const Value& extra); + bool good() const; + +private: + OurReader(OurReader const&); // no impl + void operator=(OurReader const&); // no impl + + enum TokenType { + tokenEndOfStream = 0, + tokenObjectBegin, + tokenObjectEnd, + tokenArrayBegin, + tokenArrayEnd, + tokenString, + tokenNumber, + tokenTrue, + tokenFalse, + tokenNull, + tokenArraySeparator, + tokenMemberSeparator, + tokenComment, + tokenError + }; + + class Token { + public: + TokenType type_; + Location start_; + Location end_; + }; + + class ErrorInfo { + public: + Token token_; + std::string message_; + Location extra_; + }; + + typedef std::deque Errors; + + bool readToken(Token& token); + void skipSpaces(); + bool match(Location pattern, int patternLength); + bool readComment(); + bool readCStyleComment(); + bool readCppStyleComment(); + bool readString(); + bool readStringSingleQuote(); + void readNumber(); + bool readValue(); + bool readObject(Token& token); + bool readArray(Token& token); + bool decodeNumber(Token& token); + bool decodeNumber(Token& token, Value& decoded); + bool decodeString(Token& token); + bool decodeString(Token& token, std::string& decoded); + bool decodeDouble(Token& token); + bool decodeDouble(Token& token, Value& decoded); + bool decodeUnicodeCodePoint(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool addError(const std::string& message, Token& token, Location extra = 0); + bool recoverFromError(TokenType skipUntilToken); + bool addErrorAndRecover(const std::string& message, + Token& token, + TokenType skipUntilToken); + void skipUntilSpace(); + Value& currentValue(); + Char getNextChar(); + void + getLocationLineAndColumn(Location location, int& line, int& column) const; + std::string getLocationLineAndColumn(Location location) const; + void addComment(Location begin, Location end, CommentPlacement placement); + void skipCommentTokens(Token& token); + + typedef std::stack Nodes; + Nodes nodes_; + Errors errors_; + std::string document_; + Location begin_; + Location end_; + Location current_; + Location lastValueEnd_; + Value* lastValue_; + std::string commentsBefore_; + int stackDepth_; + + OurFeatures const features_; + bool collectComments_; +}; // OurReader + +// complete copy of Read impl, for OurReader + +OurReader::OurReader(OurFeatures const& features) + : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), + lastValue_(), commentsBefore_(), features_(features), collectComments_() { +} + +bool OurReader::parse(const char* beginDoc, + const char* endDoc, + Value& root, + bool collectComments) { + if (!features_.allowComments_) { + collectComments = false; + } + + begin_ = beginDoc; + end_ = endDoc; + collectComments_ = collectComments; + current_ = begin_; + lastValueEnd_ = 0; + lastValue_ = 0; + commentsBefore_ = ""; + errors_.clear(); + while (!nodes_.empty()) + nodes_.pop(); + nodes_.push(&root); + + stackDepth_ = 0; + bool successful = readValue(); + Token token; + skipCommentTokens(token); + if (features_.failIfExtra_) { + if (token.type_ != tokenError && token.type_ != tokenEndOfStream) { + addError("Extra non-whitespace after JSON value.", token); + return false; + } + } + if (collectComments_ && !commentsBefore_.empty()) + root.setComment(commentsBefore_, commentAfter); + if (features_.strictRoot_) { + if (!root.isArray() && !root.isObject()) { + // Set error location to start of doc, ideally should be first token found + // in doc + token.type_ = tokenError; + token.start_ = beginDoc; + token.end_ = endDoc; + addError( + "A valid JSON document must be either an array or an object value.", + token); + return false; + } + } + return successful; +} + +bool OurReader::readValue() { + if (stackDepth_ >= features_.stackLimit_) throwRuntimeError("Exceeded stackLimit in readValue()."); + ++stackDepth_; + Token token; + skipCommentTokens(token); + bool successful = true; + + if (collectComments_ && !commentsBefore_.empty()) { + currentValue().setComment(commentsBefore_, commentBefore); + commentsBefore_ = ""; + } + + switch (token.type_) { + case tokenObjectBegin: + successful = readObject(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenArrayBegin: + successful = readArray(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenNumber: + successful = decodeNumber(token); + break; + case tokenString: + successful = decodeString(token); + break; + case tokenTrue: + { + Value v(true); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenFalse: + { + Value v(false); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenNull: + { + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenArraySeparator: + case tokenObjectEnd: + case tokenArrayEnd: + if (features_.allowDroppedNullPlaceholders_) { + // "Un-read" the current token and mark the current value as a null + // token. + current_--; + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(current_ - begin_ - 1); + currentValue().setOffsetLimit(current_ - begin_); + break; + } // else, fall through ... + default: + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return addError("Syntax error: value, object or array expected.", token); + } + + if (collectComments_) { + lastValueEnd_ = current_; + lastValue_ = ¤tValue(); + } + + --stackDepth_; + return successful; +} + +void OurReader::skipCommentTokens(Token& token) { + if (features_.allowComments_) { + do { + readToken(token); + } while (token.type_ == tokenComment); + } else { + readToken(token); + } +} + +bool OurReader::readToken(Token& token) { + skipSpaces(); + token.start_ = current_; + Char c = getNextChar(); + bool ok = true; + switch (c) { + case '{': + token.type_ = tokenObjectBegin; + break; + case '}': + token.type_ = tokenObjectEnd; + break; + case '[': + token.type_ = tokenArrayBegin; + break; + case ']': + token.type_ = tokenArrayEnd; + break; + case '"': + token.type_ = tokenString; + ok = readString(); + break; + case '\'': + if (features_.allowSingleQuotes_) { + token.type_ = tokenString; + ok = readStringSingleQuote(); + break; + } // else continue + case '/': + token.type_ = tokenComment; + ok = readComment(); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + token.type_ = tokenNumber; + readNumber(); + break; + case 't': + token.type_ = tokenTrue; + ok = match("rue", 3); + break; + case 'f': + token.type_ = tokenFalse; + ok = match("alse", 4); + break; + case 'n': + token.type_ = tokenNull; + ok = match("ull", 3); + break; + case ',': + token.type_ = tokenArraySeparator; + break; + case ':': + token.type_ = tokenMemberSeparator; + break; + case 0: + token.type_ = tokenEndOfStream; + break; + default: + ok = false; + break; + } + if (!ok) + token.type_ = tokenError; + token.end_ = current_; + return true; +} + +void OurReader::skipSpaces() { + while (current_ != end_) { + Char c = *current_; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + ++current_; + else + break; + } +} + +bool OurReader::match(Location pattern, int patternLength) { + if (end_ - current_ < patternLength) + return false; + int index = patternLength; + while (index--) + if (current_[index] != pattern[index]) + return false; + current_ += patternLength; + return true; +} + +bool OurReader::readComment() { + Location commentBegin = current_ - 1; + Char c = getNextChar(); + bool successful = false; + if (c == '*') + successful = readCStyleComment(); + else if (c == '/') + successful = readCppStyleComment(); + if (!successful) + return false; + + if (collectComments_) { + CommentPlacement placement = commentBefore; + if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { + if (c != '*' || !containsNewLine(commentBegin, current_)) + placement = commentAfterOnSameLine; + } + + addComment(commentBegin, current_, placement); + } + return true; +} + +void +OurReader::addComment(Location begin, Location end, CommentPlacement placement) { + assert(collectComments_); + const std::string& normalized = normalizeEOL(begin, end); + if (placement == commentAfterOnSameLine) { + assert(lastValue_ != 0); + lastValue_->setComment(normalized, placement); + } else { + commentsBefore_ += normalized; + } +} + +bool OurReader::readCStyleComment() { + while (current_ != end_) { + Char c = getNextChar(); + if (c == '*' && *current_ == '/') + break; + } + return getNextChar() == '/'; +} + +bool OurReader::readCppStyleComment() { + while (current_ != end_) { + Char c = getNextChar(); + if (c == '\n') + break; + if (c == '\r') { + // Consume DOS EOL. It will be normalized in addComment. + if (current_ != end_ && *current_ == '\n') + getNextChar(); + // Break on Moc OS 9 EOL. + break; + } + } + return true; +} + +void OurReader::readNumber() { + const char *p = current_; + char c = '0'; // stopgap for already consumed character + // integral part + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : 0; + // fractional part + if (c == '.') { + c = (current_ = p) < end_ ? *p++ : 0; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : 0; + } + // exponential part + if (c == 'e' || c == 'E') { + c = (current_ = p) < end_ ? *p++ : 0; + if (c == '+' || c == '-') + c = (current_ = p) < end_ ? *p++ : 0; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : 0; + } +} +bool OurReader::readString() { + Char c = 0; + while (current_ != end_) { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '"') + break; + } + return c == '"'; +} + + +bool OurReader::readStringSingleQuote() { + Char c = 0; + while (current_ != end_) { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '\'') + break; + } + return c == '\''; +} + +bool OurReader::readObject(Token& tokenStart) { + Token tokenName; + std::string name; + Value init(objectValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(tokenStart.start_ - begin_); + while (readToken(tokenName)) { + bool initialTokenOk = true; + while (tokenName.type_ == tokenComment && initialTokenOk) + initialTokenOk = readToken(tokenName); + if (!initialTokenOk) + break; + if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object + return true; + name = ""; + if (tokenName.type_ == tokenString) { + if (!decodeString(tokenName, name)) + return recoverFromError(tokenObjectEnd); + } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { + Value numberName; + if (!decodeNumber(tokenName, numberName)) + return recoverFromError(tokenObjectEnd); + name = numberName.asString(); + } else { + break; + } + + Token colon; + if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { + return addErrorAndRecover( + "Missing ':' after object member name", colon, tokenObjectEnd); + } + if (name.length() >= (1U<<30)) throwRuntimeError("keylength >= 2^30"); + if (features_.rejectDupKeys_ && currentValue().isMember(name)) { + std::string msg = "Duplicate key: '" + name + "'"; + return addErrorAndRecover( + msg, tokenName, tokenObjectEnd); + } + Value& value = currentValue()[name]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenObjectEnd); + + Token comma; + if (!readToken(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && + comma.type_ != tokenComment)) { + return addErrorAndRecover( + "Missing ',' or '}' in object declaration", comma, tokenObjectEnd); + } + bool finalizeTokenOk = true; + while (comma.type_ == tokenComment && finalizeTokenOk) + finalizeTokenOk = readToken(comma); + if (comma.type_ == tokenObjectEnd) + return true; + } + return addErrorAndRecover( + "Missing '}' or object member name", tokenName, tokenObjectEnd); +} + +bool OurReader::readArray(Token& tokenStart) { + Value init(arrayValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(tokenStart.start_ - begin_); + skipSpaces(); + if (*current_ == ']') // empty array + { + Token endArray; + readToken(endArray); + return true; + } + int index = 0; + for (;;) { + Value& value = currentValue()[index++]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenArrayEnd); + + Token token; + // Accept Comment after last item in the array. + ok = readToken(token); + while (token.type_ == tokenComment && ok) { + ok = readToken(token); + } + bool badTokenType = + (token.type_ != tokenArraySeparator && token.type_ != tokenArrayEnd); + if (!ok || badTokenType) { + return addErrorAndRecover( + "Missing ',' or ']' in array declaration", token, tokenArrayEnd); + } + if (token.type_ == tokenArrayEnd) + break; + } + return true; +} + +bool OurReader::decodeNumber(Token& token) { + Value decoded; + if (!decodeNumber(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool OurReader::decodeNumber(Token& token, Value& decoded) { + // Attempts to parse the number as an integer. If the number is + // larger than the maximum supported value of an integer then + // we decode the number as a double. + Location current = token.start_; + bool isNegative = *current == '-'; + if (isNegative) + ++current; + // TODO: Help the compiler do the div and mod at compile time or get rid of them. + Value::LargestUInt maxIntegerValue = + isNegative ? Value::LargestUInt(-Value::minLargestInt) + : Value::maxLargestUInt; + Value::LargestUInt threshold = maxIntegerValue / 10; + Value::LargestUInt value = 0; + while (current < token.end_) { + Char c = *current++; + if (c < '0' || c > '9') + return decodeDouble(token, decoded); + Value::UInt digit(c - '0'); + if (value >= threshold) { + // We've hit or exceeded the max value divided by 10 (rounded down). If + // a) we've only just touched the limit, b) this is the last digit, and + // c) it's small enough to fit in that rounding delta, we're okay. + // Otherwise treat this number as a double to avoid overflow. + if (value > threshold || current != token.end_ || + digit > maxIntegerValue % 10) { + return decodeDouble(token, decoded); + } + } + value = value * 10 + digit; + } + if (isNegative) + decoded = -Value::LargestInt(value); + else if (value <= Value::LargestUInt(Value::maxInt)) + decoded = Value::LargestInt(value); + else + decoded = value; + return true; +} + +bool OurReader::decodeDouble(Token& token) { + Value decoded; + if (!decodeDouble(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool OurReader::decodeDouble(Token& token, Value& decoded) { + double value = 0; + const int bufferSize = 32; + int count; + int length = int(token.end_ - token.start_); + + // Sanity check to avoid buffer overflow exploits. + if (length < 0) { + return addError("Unable to parse token length", token); + } + + // Avoid using a string constant for the format control string given to + // sscanf, as this can cause hard to debug crashes on OS X. See here for more + // info: + // + // http://developer.apple.com/library/mac/#DOCUMENTATION/DeveloperTools/gcc-4.0.1/gcc/Incompatibilities.html + char format[] = "%lf"; + + if (length <= bufferSize) { + Char buffer[bufferSize + 1]; + memcpy(buffer, token.start_, length); + buffer[length] = 0; + count = sscanf(buffer, format, &value); + } else { + std::string buffer(token.start_, token.end_); + count = sscanf(buffer.c_str(), format, &value); + } + + if (count != 1) + return addError("'" + std::string(token.start_, token.end_) + + "' is not a number.", + token); + decoded = value; + return true; +} + +bool OurReader::decodeString(Token& token) { + std::string decoded_string; + if (!decodeString(token, decoded_string)) + return false; + Value decoded(decoded_string); + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool OurReader::decodeString(Token& token, std::string& decoded) { + decoded.reserve(token.end_ - token.start_ - 2); + Location current = token.start_ + 1; // skip '"' + Location end = token.end_ - 1; // do not include '"' + while (current != end) { + Char c = *current++; + if (c == '"') + break; + else if (c == '\\') { + if (current == end) + return addError("Empty escape sequence in string", token, current); + Char escape = *current++; + switch (escape) { + case '"': + decoded += '"'; + break; + case '/': + decoded += '/'; + break; + case '\\': + decoded += '\\'; + break; + case 'b': + decoded += '\b'; + break; + case 'f': + decoded += '\f'; + break; + case 'n': + decoded += '\n'; + break; + case 'r': + decoded += '\r'; + break; + case 't': + decoded += '\t'; + break; + case 'u': { + unsigned int unicode; + if (!decodeUnicodeCodePoint(token, current, end, unicode)) + return false; + decoded += codePointToUTF8(unicode); + } break; + default: + return addError("Bad escape sequence in string", token, current); + } + } else { + decoded += c; + } + } + return true; +} + +bool OurReader::decodeUnicodeCodePoint(Token& token, + Location& current, + Location end, + unsigned int& unicode) { + + if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) + return false; + if (unicode >= 0xD800 && unicode <= 0xDBFF) { + // surrogate pairs + if (end - current < 6) + return addError( + "additional six characters expected to parse unicode surrogate pair.", + token, + current); + unsigned int surrogatePair; + if (*(current++) == '\\' && *(current++) == 'u') { + if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { + unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); + } else + return false; + } else + return addError("expecting another \\u token to begin the second half of " + "a unicode surrogate pair", + token, + current); + } + return true; +} + +bool OurReader::decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& unicode) { + if (end - current < 4) + return addError( + "Bad unicode escape sequence in string: four digits expected.", + token, + current); + unicode = 0; + for (int index = 0; index < 4; ++index) { + Char c = *current++; + unicode *= 16; + if (c >= '0' && c <= '9') + unicode += c - '0'; + else if (c >= 'a' && c <= 'f') + unicode += c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + unicode += c - 'A' + 10; + else + return addError( + "Bad unicode escape sequence in string: hexadecimal digit expected.", + token, + current); + } + return true; +} + +bool +OurReader::addError(const std::string& message, Token& token, Location extra) { + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = extra; + errors_.push_back(info); + return false; +} + +bool OurReader::recoverFromError(TokenType skipUntilToken) { + int errorCount = int(errors_.size()); + Token skip; + for (;;) { + if (!readToken(skip)) + errors_.resize(errorCount); // discard errors caused by recovery + if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) + break; + } + errors_.resize(errorCount); + return false; +} + +bool OurReader::addErrorAndRecover(const std::string& message, + Token& token, + TokenType skipUntilToken) { + addError(message, token); + return recoverFromError(skipUntilToken); +} + +Value& OurReader::currentValue() { return *(nodes_.top()); } + +OurReader::Char OurReader::getNextChar() { + if (current_ == end_) + return 0; + return *current_++; +} + +void OurReader::getLocationLineAndColumn(Location location, + int& line, + int& column) const { + Location current = begin_; + Location lastLineStart = current; + line = 0; + while (current < location && current != end_) { + Char c = *current++; + if (c == '\r') { + if (*current == '\n') + ++current; + lastLineStart = current; + ++line; + } else if (c == '\n') { + lastLineStart = current; + ++line; + } + } + // column & line start at 1 + column = int(location - lastLineStart) + 1; + ++line; +} + +std::string OurReader::getLocationLineAndColumn(Location location) const { + int line, column; + getLocationLineAndColumn(location, line, column); + char buffer[18 + 16 + 16 + 1]; +#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) +#if defined(WINCE) + _snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); +#else + sprintf_s(buffer, sizeof(buffer), "Line %d, Column %d", line, column); +#endif +#else + snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); +#endif + return buffer; +} + +std::string OurReader::getFormattedErrorMessages() const { + std::string formattedMessage; + for (Errors::const_iterator itError = errors_.begin(); + itError != errors_.end(); + ++itError) { + const ErrorInfo& error = *itError; + formattedMessage += + "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; + formattedMessage += " " + error.message_ + "\n"; + if (error.extra_) + formattedMessage += + "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; + } + return formattedMessage; +} + +std::vector OurReader::getStructuredErrors() const { + std::vector allErrors; + for (Errors::const_iterator itError = errors_.begin(); + itError != errors_.end(); + ++itError) { + const ErrorInfo& error = *itError; + OurReader::StructuredError structured; + structured.offset_start = error.token_.start_ - begin_; + structured.offset_limit = error.token_.end_ - begin_; + structured.message = error.message_; + allErrors.push_back(structured); + } + return allErrors; +} + +bool OurReader::pushError(const Value& value, const std::string& message) { + size_t length = end_ - begin_; + if(value.getOffsetStart() > length + || value.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = end_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = 0; + errors_.push_back(info); + return true; +} + +bool OurReader::pushError(const Value& value, const std::string& message, const Value& extra) { + size_t length = end_ - begin_; + if(value.getOffsetStart() > length + || value.getOffsetLimit() > length + || extra.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = begin_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = begin_ + extra.getOffsetStart(); + errors_.push_back(info); + return true; +} + +bool OurReader::good() const { + return !errors_.size(); +} + + +class OurCharReader : public CharReader { + bool const collectComments_; + OurReader reader_; +public: + OurCharReader( + bool collectComments, + OurFeatures const& features) + : collectComments_(collectComments) + , reader_(features) + {} + virtual bool parse( + char const* beginDoc, char const* endDoc, + Value* root, std::string* errs) { + bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); + if (errs) { + *errs = reader_.getFormattedErrorMessages(); + } + return ok; + } +}; + +CharReaderBuilder::CharReaderBuilder() +{ + setDefaults(&settings_); +} +CharReaderBuilder::~CharReaderBuilder() +{} +CharReader* CharReaderBuilder::newCharReader() const +{ + bool collectComments = settings_["collectComments"].asBool(); + OurFeatures features = OurFeatures::all(); + features.allowComments_ = settings_["allowComments"].asBool(); + features.strictRoot_ = settings_["strictRoot"].asBool(); + features.allowDroppedNullPlaceholders_ = settings_["allowDroppedNullPlaceholders"].asBool(); + features.allowNumericKeys_ = settings_["allowNumericKeys"].asBool(); + features.allowSingleQuotes_ = settings_["allowSingleQuotes"].asBool(); + features.stackLimit_ = settings_["stackLimit"].asInt(); + features.failIfExtra_ = settings_["failIfExtra"].asBool(); + features.rejectDupKeys_ = settings_["rejectDupKeys"].asBool(); + return new OurCharReader(collectComments, features); +} +static void getValidReaderKeys(std::set* valid_keys) +{ + valid_keys->clear(); + valid_keys->insert("collectComments"); + valid_keys->insert("allowComments"); + valid_keys->insert("strictRoot"); + valid_keys->insert("allowDroppedNullPlaceholders"); + valid_keys->insert("allowNumericKeys"); + valid_keys->insert("allowSingleQuotes"); + valid_keys->insert("stackLimit"); + valid_keys->insert("failIfExtra"); + valid_keys->insert("rejectDupKeys"); +} +bool CharReaderBuilder::validate(Json::Value* invalid) const +{ + Json::Value my_invalid; + if (!invalid) invalid = &my_invalid; // so we do not need to test for NULL + Json::Value& inv = *invalid; + std::set valid_keys; + getValidReaderKeys(&valid_keys); + Value::Members keys = settings_.getMemberNames(); + size_t n = keys.size(); + for (size_t i = 0; i < n; ++i) { + std::string const& key = keys[i]; + if (valid_keys.find(key) == valid_keys.end()) { + inv[key] = settings_[key]; + } + } + return 0u == inv.size(); +} +Value& CharReaderBuilder::operator[](std::string key) +{ + return settings_[key]; +} +// static +void CharReaderBuilder::strictMode(Json::Value* settings) +{ +//! [CharReaderBuilderStrictMode] + (*settings)["allowComments"] = false; + (*settings)["strictRoot"] = true; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; + (*settings)["allowSingleQuotes"] = false; + (*settings)["failIfExtra"] = true; + (*settings)["rejectDupKeys"] = true; +//! [CharReaderBuilderStrictMode] +} +// static +void CharReaderBuilder::setDefaults(Json::Value* settings) +{ +//! [CharReaderBuilderDefaults] + (*settings)["collectComments"] = true; + (*settings)["allowComments"] = true; + (*settings)["strictRoot"] = false; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; + (*settings)["allowSingleQuotes"] = false; + (*settings)["stackLimit"] = 1000; + (*settings)["failIfExtra"] = false; + (*settings)["rejectDupKeys"] = false; +//! [CharReaderBuilderDefaults] +} + +////////////////////////////////// +// global functions + +bool parseFromStream( + CharReader::Factory const& fact, std::istream& sin, + Value* root, std::string* errs) +{ + std::ostringstream ssin; + ssin << sin.rdbuf(); + std::string doc = ssin.str(); + char const* begin = doc.data(); + char const* end = begin + doc.size(); + // Note that we do not actually need a null-terminator. + CharReaderPtr const reader(fact.newCharReader()); + return reader->parse(begin, end, root, errs); +} + +std::istream& operator>>(std::istream& sin, Value& root) { + CharReaderBuilder b; + std::string errs; + bool ok = parseFromStream(b, sin, &root, &errs); + if (!ok) { + fprintf(stderr, + "Error from reader: %s", + errs.c_str()); + + throwRuntimeError("reader error"); + } + return sin; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_reader.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_valueiterator.inl +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +// included by json_value.cpp + +namespace Json { + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueIteratorBase +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueIteratorBase::ValueIteratorBase() + : current_(), isNull_(true) { +} + +ValueIteratorBase::ValueIteratorBase( + const Value::ObjectValues::iterator& current) + : current_(current), isNull_(false) {} + +Value& ValueIteratorBase::deref() const { + return current_->second; +} + +void ValueIteratorBase::increment() { + ++current_; +} + +void ValueIteratorBase::decrement() { + --current_; +} + +ValueIteratorBase::difference_type +ValueIteratorBase::computeDistance(const SelfType& other) const { +#ifdef JSON_USE_CPPTL_SMALLMAP + return other.current_ - current_; +#else + // Iterator for null value are initialized using the default + // constructor, which initialize current_ to the default + // std::map::iterator. As begin() and end() are two instance + // of the default std::map::iterator, they can not be compared. + // To allow this, we handle this comparison specifically. + if (isNull_ && other.isNull_) { + return 0; + } + + // Usage of std::distance is not portable (does not compile with Sun Studio 12 + // RogueWave STL, + // which is the one used by default). + // Using a portable hand-made version for non random iterator instead: + // return difference_type( std::distance( current_, other.current_ ) ); + difference_type myDistance = 0; + for (Value::ObjectValues::iterator it = current_; it != other.current_; + ++it) { + ++myDistance; + } + return myDistance; +#endif +} + +bool ValueIteratorBase::isEqual(const SelfType& other) const { + if (isNull_) { + return other.isNull_; + } + return current_ == other.current_; +} + +void ValueIteratorBase::copy(const SelfType& other) { + current_ = other.current_; + isNull_ = other.isNull_; +} + +Value ValueIteratorBase::key() const { + const Value::CZString czstring = (*current_).first; + if (czstring.data()) { + if (czstring.isStaticString()) + return Value(StaticString(czstring.data())); + return Value(czstring.data(), czstring.data() + czstring.length()); + } + return Value(czstring.index()); +} + +UInt ValueIteratorBase::index() const { + const Value::CZString czstring = (*current_).first; + if (!czstring.data()) + return czstring.index(); + return Value::UInt(-1); +} + +std::string ValueIteratorBase::name() const { + char const* keey; + char const* end; + keey = memberName(&end); + if (!keey) return std::string(); + return std::string(keey, end); +} + +char const* ValueIteratorBase::memberName() const { + const char* cname = (*current_).first.data(); + return cname ? cname : ""; +} + +char const* ValueIteratorBase::memberName(char const** end) const { + const char* cname = (*current_).first.data(); + if (!cname) { + *end = NULL; + return NULL; + } + *end = cname + (*current_).first.length(); + return cname; +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueConstIterator +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueConstIterator::ValueConstIterator() {} + +ValueConstIterator::ValueConstIterator( + const Value::ObjectValues::iterator& current) + : ValueIteratorBase(current) {} + +ValueConstIterator& ValueConstIterator:: +operator=(const ValueIteratorBase& other) { + copy(other); + return *this; +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueIterator +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueIterator::ValueIterator() {} + +ValueIterator::ValueIterator(const Value::ObjectValues::iterator& current) + : ValueIteratorBase(current) {} + +ValueIterator::ValueIterator(const ValueConstIterator& other) + : ValueIteratorBase(other) {} + +ValueIterator::ValueIterator(const ValueIterator& other) + : ValueIteratorBase(other) {} + +ValueIterator& ValueIterator::operator=(const SelfType& other) { + copy(other); + return *this; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_valueiterator.inl +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_value.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2011 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#ifdef JSON_USE_CPPTL +#include +#endif +#include // size_t +#include // min() + +#define JSON_ASSERT_UNREACHABLE assert(false) + +namespace Json { + +// This is a walkaround to avoid the static initialization of Value::null. +// kNull must be word-aligned to avoid crashing on ARM. We use an alignment of +// 8 (instead of 4) as a bit of future-proofing. +#if defined(__ARMEL__) +#define ALIGNAS(byte_alignment) __attribute__((aligned(byte_alignment))) +#else +#define ALIGNAS(byte_alignment) +#endif +static const unsigned char ALIGNAS(8) kNull[sizeof(Value)] = { 0 }; +const unsigned char& kNullRef = kNull[0]; +const Value& Value::null = reinterpret_cast(kNullRef); +const Value& Value::nullRef = null; + +const Int Value::minInt = Int(~(UInt(-1) / 2)); +const Int Value::maxInt = Int(UInt(-1) / 2); +const UInt Value::maxUInt = UInt(-1); +#if defined(JSON_HAS_INT64) +const Int64 Value::minInt64 = Int64(~(UInt64(-1) / 2)); +const Int64 Value::maxInt64 = Int64(UInt64(-1) / 2); +const UInt64 Value::maxUInt64 = UInt64(-1); +// The constant is hard-coded because some compiler have trouble +// converting Value::maxUInt64 to a double correctly (AIX/xlC). +// Assumes that UInt64 is a 64 bits integer. +static const double maxUInt64AsDouble = 18446744073709551615.0; +#endif // defined(JSON_HAS_INT64) +const LargestInt Value::minLargestInt = LargestInt(~(LargestUInt(-1) / 2)); +const LargestInt Value::maxLargestInt = LargestInt(LargestUInt(-1) / 2); +const LargestUInt Value::maxLargestUInt = LargestUInt(-1); + +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) +template +static inline bool InRange(double d, T min, U max) { + return d >= min && d <= max; +} +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) +static inline double integerToDouble(Json::UInt64 value) { + return static_cast(Int64(value / 2)) * 2.0 + Int64(value & 1); +} + +template static inline double integerToDouble(T value) { + return static_cast(value); +} + +template +static inline bool InRange(double d, T min, U max) { + return d >= integerToDouble(min) && d <= integerToDouble(max); +} +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + +/** Duplicates the specified string value. + * @param value Pointer to the string to duplicate. Must be zero-terminated if + * length is "unknown". + * @param length Length of the value. if equals to unknown, then it will be + * computed using strlen(value). + * @return Pointer on the duplicate instance of string. + */ +static inline char* duplicateStringValue(const char* value, + size_t length) { + // Avoid an integer overflow in the call to malloc below by limiting length + // to a sane value. + if (length >= (size_t)Value::maxInt) + length = Value::maxInt - 1; + + char* newString = static_cast(malloc(length + 1)); + if (newString == NULL) { + throwRuntimeError( + "in Json::Value::duplicateStringValue(): " + "Failed to allocate string value buffer"); + } + memcpy(newString, value, length); + newString[length] = 0; + return newString; +} + +/* Record the length as a prefix. + */ +static inline char* duplicateAndPrefixStringValue( + const char* value, + unsigned int length) +{ + // Avoid an integer overflow in the call to malloc below by limiting length + // to a sane value. + JSON_ASSERT_MESSAGE(length <= (unsigned)Value::maxInt - sizeof(unsigned) - 1U, + "in Json::Value::duplicateAndPrefixStringValue(): " + "length too big for prefixing"); + unsigned actualLength = length + static_cast(sizeof(unsigned)) + 1U; + char* newString = static_cast(malloc(actualLength)); + if (newString == 0) { + throwRuntimeError( + "in Json::Value::duplicateAndPrefixStringValue(): " + "Failed to allocate string value buffer"); + } + *reinterpret_cast(newString) = length; + memcpy(newString + sizeof(unsigned), value, length); + newString[actualLength - 1U] = 0; // to avoid buffer over-run accidents by users later + return newString; +} +inline static void decodePrefixedString( + bool isPrefixed, char const* prefixed, + unsigned* length, char const** value) +{ + if (!isPrefixed) { + *length = static_cast(strlen(prefixed)); + *value = prefixed; + } else { + *length = *reinterpret_cast(prefixed); + *value = prefixed + sizeof(unsigned); + } +} +/** Free the string duplicated by duplicateStringValue()/duplicateAndPrefixStringValue(). + */ +static inline void releaseStringValue(char* value) { free(value); } + +} // namespace Json + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ValueInternals... +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +#if !defined(JSON_IS_AMALGAMATION) + +#include "json_valueiterator.inl" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + +Exception::Exception(std::string const& msg) + : msg_(msg) +{} +Exception::~Exception() throw() +{} +char const* Exception::what() const throw() +{ + return msg_.c_str(); +} +RuntimeError::RuntimeError(std::string const& msg) + : Exception(msg) +{} +LogicError::LogicError(std::string const& msg) + : Exception(msg) +{} +void throwRuntimeError(std::string const& msg) +{ + throw RuntimeError(msg); +} +void throwLogicError(std::string const& msg) +{ + throw LogicError(msg); +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class Value::CommentInfo +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +Value::CommentInfo::CommentInfo() : comment_(0) {} + +Value::CommentInfo::~CommentInfo() { + if (comment_) + releaseStringValue(comment_); +} + +void Value::CommentInfo::setComment(const char* text, size_t len) { + if (comment_) { + releaseStringValue(comment_); + comment_ = 0; + } + JSON_ASSERT(text != 0); + JSON_ASSERT_MESSAGE( + text[0] == '\0' || text[0] == '/', + "in Json::Value::setComment(): Comments must start with /"); + // It seems that /**/ style comments are acceptable as well. + comment_ = duplicateStringValue(text, len); +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class Value::CZString +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +// Notes: policy_ indicates if the string was allocated when +// a string is stored. + +Value::CZString::CZString(ArrayIndex aindex) : cstr_(0), index_(aindex) {} + +Value::CZString::CZString(char const* str, unsigned ulength, DuplicationPolicy allocate) + : cstr_(str) +{ + // allocate != duplicate + storage_.policy_ = allocate & 0x3; + storage_.length_ = ulength & 0x3FFFFFFF; +} + +Value::CZString::CZString(const CZString& other) + : cstr_(other.storage_.policy_ != noDuplication && other.cstr_ != 0 + ? duplicateStringValue(other.cstr_, other.storage_.length_) + : other.cstr_) +{ + storage_.policy_ = (other.cstr_ + ? (static_cast(other.storage_.policy_) == noDuplication + ? noDuplication : duplicate) + : static_cast(other.storage_.policy_)); + storage_.length_ = other.storage_.length_; +} + +Value::CZString::~CZString() { + if (cstr_ && storage_.policy_ == duplicate) + releaseStringValue(const_cast(cstr_)); +} + +void Value::CZString::swap(CZString& other) { + std::swap(cstr_, other.cstr_); + std::swap(index_, other.index_); +} + +Value::CZString& Value::CZString::operator=(CZString other) { + swap(other); + return *this; +} + +bool Value::CZString::operator<(const CZString& other) const { + if (!cstr_) return index_ < other.index_; + //return strcmp(cstr_, other.cstr_) < 0; + // Assume both are strings. + unsigned this_len = this->storage_.length_; + unsigned other_len = other.storage_.length_; + unsigned min_len = std::min(this_len, other_len); + int comp = memcmp(this->cstr_, other.cstr_, min_len); + if (comp < 0) return true; + if (comp > 0) return false; + return (this_len < other_len); +} + +bool Value::CZString::operator==(const CZString& other) const { + if (!cstr_) return index_ == other.index_; + //return strcmp(cstr_, other.cstr_) == 0; + // Assume both are strings. + unsigned this_len = this->storage_.length_; + unsigned other_len = other.storage_.length_; + if (this_len != other_len) return false; + int comp = memcmp(this->cstr_, other.cstr_, this_len); + return comp == 0; +} + +ArrayIndex Value::CZString::index() const { return index_; } + +//const char* Value::CZString::c_str() const { return cstr_; } +const char* Value::CZString::data() const { return cstr_; } +unsigned Value::CZString::length() const { return storage_.length_; } +bool Value::CZString::isStaticString() const { return storage_.policy_ == noDuplication; } + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class Value::Value +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +/*! \internal Default constructor initialization must be equivalent to: + * memset( this, 0, sizeof(Value) ) + * This optimization is used in ValueInternalMap fast allocator. + */ +Value::Value(ValueType vtype) { + initBasic(vtype); + switch (vtype) { + case nullValue: + break; + case intValue: + case uintValue: + value_.int_ = 0; + break; + case realValue: + value_.real_ = 0.0; + break; + case stringValue: + value_.string_ = 0; + break; + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues(); + break; + case booleanValue: + value_.bool_ = false; + break; + default: + JSON_ASSERT_UNREACHABLE; + } +} + +Value::Value(Int value) { + initBasic(intValue); + value_.int_ = value; +} + +Value::Value(UInt value) { + initBasic(uintValue); + value_.uint_ = value; +} +#if defined(JSON_HAS_INT64) +Value::Value(Int64 value) { + initBasic(intValue); + value_.int_ = value; +} +Value::Value(UInt64 value) { + initBasic(uintValue); + value_.uint_ = value; +} +#endif // defined(JSON_HAS_INT64) + +Value::Value(double value) { + initBasic(realValue); + value_.real_ = value; +} + +Value::Value(const char* value) { + initBasic(stringValue, true); + value_.string_ = duplicateAndPrefixStringValue(value, static_cast(strlen(value))); +} + +Value::Value(const char* beginValue, const char* endValue) { + initBasic(stringValue, true); + value_.string_ = + duplicateAndPrefixStringValue(beginValue, static_cast(endValue - beginValue)); +} + +Value::Value(const std::string& value) { + initBasic(stringValue, true); + value_.string_ = + duplicateAndPrefixStringValue(value.data(), static_cast(value.length())); +} + +Value::Value(const StaticString& value) { + initBasic(stringValue); + value_.string_ = const_cast(value.c_str()); +} + +#ifdef JSON_USE_CPPTL +Value::Value(const CppTL::ConstString& value) { + initBasic(stringValue, true); + value_.string_ = duplicateAndPrefixStringValue(value, static_cast(value.length())); +} +#endif + +Value::Value(bool value) { + initBasic(booleanValue); + value_.bool_ = value; +} + +Value::Value(Value const& other) + : type_(other.type_), allocated_(false) + , + comments_(0), start_(other.start_), limit_(other.limit_) +{ + switch (type_) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + value_ = other.value_; + break; + case stringValue: + if (other.value_.string_ && other.allocated_) { + unsigned len; + char const* str; + decodePrefixedString(other.allocated_, other.value_.string_, + &len, &str); + value_.string_ = duplicateAndPrefixStringValue(str, len); + allocated_ = true; + } else { + value_.string_ = other.value_.string_; + allocated_ = false; + } + break; + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues(*other.value_.map_); + break; + default: + JSON_ASSERT_UNREACHABLE; + } + if (other.comments_) { + comments_ = new CommentInfo[numberOfCommentPlacement]; + for (int comment = 0; comment < numberOfCommentPlacement; ++comment) { + const CommentInfo& otherComment = other.comments_[comment]; + if (otherComment.comment_) + comments_[comment].setComment( + otherComment.comment_, strlen(otherComment.comment_)); + } + } +} + +Value::~Value() { + switch (type_) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + break; + case stringValue: + if (allocated_) + releaseStringValue(value_.string_); + break; + case arrayValue: + case objectValue: + delete value_.map_; + break; + default: + JSON_ASSERT_UNREACHABLE; + } + + if (comments_) + delete[] comments_; +} + +Value& Value::operator=(Value other) { + swap(other); + return *this; +} + +void Value::swapPayload(Value& other) { + ValueType temp = type_; + type_ = other.type_; + other.type_ = temp; + std::swap(value_, other.value_); + int temp2 = allocated_; + allocated_ = other.allocated_; + other.allocated_ = temp2 & 0x1; +} + +void Value::swap(Value& other) { + swapPayload(other); + std::swap(comments_, other.comments_); + std::swap(start_, other.start_); + std::swap(limit_, other.limit_); +} + +ValueType Value::type() const { return type_; } + +int Value::compare(const Value& other) const { + if (*this < other) + return -1; + if (*this > other) + return 1; + return 0; +} + +bool Value::operator<(const Value& other) const { + int typeDelta = type_ - other.type_; + if (typeDelta) + return typeDelta < 0 ? true : false; + switch (type_) { + case nullValue: + return false; + case intValue: + return value_.int_ < other.value_.int_; + case uintValue: + return value_.uint_ < other.value_.uint_; + case realValue: + return value_.real_ < other.value_.real_; + case booleanValue: + return value_.bool_ < other.value_.bool_; + case stringValue: + { + if ((value_.string_ == 0) || (other.value_.string_ == 0)) { + if (other.value_.string_) return true; + else return false; + } + unsigned this_len; + unsigned other_len; + char const* this_str; + char const* other_str; + decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); + decodePrefixedString(other.allocated_, other.value_.string_, &other_len, &other_str); + unsigned min_len = std::min(this_len, other_len); + int comp = memcmp(this_str, other_str, min_len); + if (comp < 0) return true; + if (comp > 0) return false; + return (this_len < other_len); + } + case arrayValue: + case objectValue: { + int delta = int(value_.map_->size() - other.value_.map_->size()); + if (delta) + return delta < 0; + return (*value_.map_) < (*other.value_.map_); + } + default: + JSON_ASSERT_UNREACHABLE; + } + return false; // unreachable +} + +bool Value::operator<=(const Value& other) const { return !(other < *this); } + +bool Value::operator>=(const Value& other) const { return !(*this < other); } + +bool Value::operator>(const Value& other) const { return other < *this; } + +bool Value::operator==(const Value& other) const { + // if ( type_ != other.type_ ) + // GCC 2.95.3 says: + // attempt to take address of bit-field structure member `Json::Value::type_' + // Beats me, but a temp solves the problem. + int temp = other.type_; + if (type_ != temp) + return false; + switch (type_) { + case nullValue: + return true; + case intValue: + return value_.int_ == other.value_.int_; + case uintValue: + return value_.uint_ == other.value_.uint_; + case realValue: + return value_.real_ == other.value_.real_; + case booleanValue: + return value_.bool_ == other.value_.bool_; + case stringValue: + { + if ((value_.string_ == 0) || (other.value_.string_ == 0)) { + return (value_.string_ == other.value_.string_); + } + unsigned this_len; + unsigned other_len; + char const* this_str; + char const* other_str; + decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); + decodePrefixedString(other.allocated_, other.value_.string_, &other_len, &other_str); + if (this_len != other_len) return false; + int comp = memcmp(this_str, other_str, this_len); + return comp == 0; + } + case arrayValue: + case objectValue: + return value_.map_->size() == other.value_.map_->size() && + (*value_.map_) == (*other.value_.map_); + default: + JSON_ASSERT_UNREACHABLE; + } + return false; // unreachable +} + +bool Value::operator!=(const Value& other) const { return !(*this == other); } + +const char* Value::asCString() const { + JSON_ASSERT_MESSAGE(type_ == stringValue, + "in Json::Value::asCString(): requires stringValue"); + if (value_.string_ == 0) return 0; + unsigned this_len; + char const* this_str; + decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); + return this_str; +} + +bool Value::getString(char const** str, char const** cend) const { + if (type_ != stringValue) return false; + if (value_.string_ == 0) return false; + unsigned length; + decodePrefixedString(this->allocated_, this->value_.string_, &length, str); + *cend = *str + length; + return true; +} + +std::string Value::asString() const { + switch (type_) { + case nullValue: + return ""; + case stringValue: + { + if (value_.string_ == 0) return ""; + unsigned this_len; + char const* this_str; + decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); + return std::string(this_str, this_len); + } + case booleanValue: + return value_.bool_ ? "true" : "false"; + case intValue: + return valueToString(value_.int_); + case uintValue: + return valueToString(value_.uint_); + case realValue: + return valueToString(value_.real_); + default: + JSON_FAIL_MESSAGE("Type is not convertible to string"); + } +} + +#ifdef JSON_USE_CPPTL +CppTL::ConstString Value::asConstString() const { + unsigned len; + char const* str; + decodePrefixedString(allocated_, value_.string_, + &len, &str); + return CppTL::ConstString(str, len); +} +#endif + +Value::Int Value::asInt() const { + switch (type_) { + case intValue: + JSON_ASSERT_MESSAGE(isInt(), "LargestInt out of Int range"); + return Int(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isInt(), "LargestUInt out of Int range"); + return Int(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt, maxInt), + "double out of Int range"); + return Int(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to Int."); +} + +Value::UInt Value::asUInt() const { + switch (type_) { + case intValue: + JSON_ASSERT_MESSAGE(isUInt(), "LargestInt out of UInt range"); + return UInt(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isUInt(), "LargestUInt out of UInt range"); + return UInt(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt), + "double out of UInt range"); + return UInt(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to UInt."); +} + +#if defined(JSON_HAS_INT64) + +Value::Int64 Value::asInt64() const { + switch (type_) { + case intValue: + return Int64(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isInt64(), "LargestUInt out of Int64 range"); + return Int64(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt64, maxInt64), + "double out of Int64 range"); + return Int64(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to Int64."); +} + +Value::UInt64 Value::asUInt64() const { + switch (type_) { + case intValue: + JSON_ASSERT_MESSAGE(isUInt64(), "LargestInt out of UInt64 range"); + return UInt64(value_.int_); + case uintValue: + return UInt64(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt64), + "double out of UInt64 range"); + return UInt64(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to UInt64."); +} +#endif // if defined(JSON_HAS_INT64) + +LargestInt Value::asLargestInt() const { +#if defined(JSON_NO_INT64) + return asInt(); +#else + return asInt64(); +#endif +} + +LargestUInt Value::asLargestUInt() const { +#if defined(JSON_NO_INT64) + return asUInt(); +#else + return asUInt64(); +#endif +} + +double Value::asDouble() const { + switch (type_) { + case intValue: + return static_cast(value_.int_); + case uintValue: +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return static_cast(value_.uint_); +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return integerToDouble(value_.uint_); +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + case realValue: + return value_.real_; + case nullValue: + return 0.0; + case booleanValue: + return value_.bool_ ? 1.0 : 0.0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to double."); +} + +float Value::asFloat() const { + switch (type_) { + case intValue: + return static_cast(value_.int_); + case uintValue: +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return static_cast(value_.uint_); +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return integerToDouble(value_.uint_); +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + case realValue: + return static_cast(value_.real_); + case nullValue: + return 0.0; + case booleanValue: + return value_.bool_ ? 1.0f : 0.0f; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to float."); +} + +bool Value::asBool() const { + switch (type_) { + case booleanValue: + return value_.bool_; + case nullValue: + return false; + case intValue: + return value_.int_ ? true : false; + case uintValue: + return value_.uint_ ? true : false; + case realValue: + // This is kind of strange. Not recommended. + return (value_.real_ != 0.0) ? true : false; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to bool."); +} + +bool Value::isConvertibleTo(ValueType other) const { + switch (other) { + case nullValue: + return (isNumeric() && asDouble() == 0.0) || + (type_ == booleanValue && value_.bool_ == false) || + (type_ == stringValue && asString() == "") || + (type_ == arrayValue && value_.map_->size() == 0) || + (type_ == objectValue && value_.map_->size() == 0) || + type_ == nullValue; + case intValue: + return isInt() || + (type_ == realValue && InRange(value_.real_, minInt, maxInt)) || + type_ == booleanValue || type_ == nullValue; + case uintValue: + return isUInt() || + (type_ == realValue && InRange(value_.real_, 0, maxUInt)) || + type_ == booleanValue || type_ == nullValue; + case realValue: + return isNumeric() || type_ == booleanValue || type_ == nullValue; + case booleanValue: + return isNumeric() || type_ == booleanValue || type_ == nullValue; + case stringValue: + return isNumeric() || type_ == booleanValue || type_ == stringValue || + type_ == nullValue; + case arrayValue: + return type_ == arrayValue || type_ == nullValue; + case objectValue: + return type_ == objectValue || type_ == nullValue; + } + JSON_ASSERT_UNREACHABLE; + return false; +} + +/// Number of values in array or object +ArrayIndex Value::size() const { + switch (type_) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + case stringValue: + return 0; + case arrayValue: // size of the array is highest index + 1 + if (!value_.map_->empty()) { + ObjectValues::const_iterator itLast = value_.map_->end(); + --itLast; + return (*itLast).first.index() + 1; + } + return 0; + case objectValue: + return ArrayIndex(value_.map_->size()); + } + JSON_ASSERT_UNREACHABLE; + return 0; // unreachable; +} + +bool Value::empty() const { + if (isNull() || isArray() || isObject()) + return size() == 0u; + else + return false; +} + +bool Value::operator!() const { return isNull(); } + +void Value::clear() { + JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == arrayValue || + type_ == objectValue, + "in Json::Value::clear(): requires complex value"); + start_ = 0; + limit_ = 0; + switch (type_) { + case arrayValue: + case objectValue: + value_.map_->clear(); + break; + default: + break; + } +} + +void Value::resize(ArrayIndex newSize) { + JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == arrayValue, + "in Json::Value::resize(): requires arrayValue"); + if (type_ == nullValue) + *this = Value(arrayValue); + ArrayIndex oldSize = size(); + if (newSize == 0) + clear(); + else if (newSize > oldSize) + (*this)[newSize - 1]; + else { + for (ArrayIndex index = newSize; index < oldSize; ++index) { + value_.map_->erase(index); + } + assert(size() == newSize); + } +} + +Value& Value::operator[](ArrayIndex index) { + JSON_ASSERT_MESSAGE( + type_ == nullValue || type_ == arrayValue, + "in Json::Value::operator[](ArrayIndex): requires arrayValue"); + if (type_ == nullValue) + *this = Value(arrayValue); + CZString key(index); + ObjectValues::iterator it = value_.map_->lower_bound(key); + if (it != value_.map_->end() && (*it).first == key) + return (*it).second; + + ObjectValues::value_type defaultValue(key, nullRef); + it = value_.map_->insert(it, defaultValue); + return (*it).second; +} + +Value& Value::operator[](int index) { + JSON_ASSERT_MESSAGE( + index >= 0, + "in Json::Value::operator[](int index): index cannot be negative"); + return (*this)[ArrayIndex(index)]; +} + +const Value& Value::operator[](ArrayIndex index) const { + JSON_ASSERT_MESSAGE( + type_ == nullValue || type_ == arrayValue, + "in Json::Value::operator[](ArrayIndex)const: requires arrayValue"); + if (type_ == nullValue) + return nullRef; + CZString key(index); + ObjectValues::const_iterator it = value_.map_->find(key); + if (it == value_.map_->end()) + return nullRef; + return (*it).second; +} + +const Value& Value::operator[](int index) const { + JSON_ASSERT_MESSAGE( + index >= 0, + "in Json::Value::operator[](int index) const: index cannot be negative"); + return (*this)[ArrayIndex(index)]; +} + +void Value::initBasic(ValueType vtype, bool allocated) { + type_ = vtype; + allocated_ = allocated; + comments_ = 0; + start_ = 0; + limit_ = 0; +} + +// Access an object value by name, create a null member if it does not exist. +// @pre Type of '*this' is object or null. +// @param key is null-terminated. +Value& Value::resolveReference(const char* key) { + JSON_ASSERT_MESSAGE( + type_ == nullValue || type_ == objectValue, + "in Json::Value::resolveReference(): requires objectValue"); + if (type_ == nullValue) + *this = Value(objectValue); + CZString actualKey( + key, static_cast(strlen(key)), CZString::noDuplication); // NOTE! + ObjectValues::iterator it = value_.map_->lower_bound(actualKey); + if (it != value_.map_->end() && (*it).first == actualKey) + return (*it).second; + + ObjectValues::value_type defaultValue(actualKey, nullRef); + it = value_.map_->insert(it, defaultValue); + Value& value = (*it).second; + return value; +} + +// @param key is not null-terminated. +Value& Value::resolveReference(char const* key, char const* cend) +{ + JSON_ASSERT_MESSAGE( + type_ == nullValue || type_ == objectValue, + "in Json::Value::resolveReference(key, end): requires objectValue"); + if (type_ == nullValue) + *this = Value(objectValue); + CZString actualKey( + key, static_cast(cend-key), CZString::duplicateOnCopy); + ObjectValues::iterator it = value_.map_->lower_bound(actualKey); + if (it != value_.map_->end() && (*it).first == actualKey) + return (*it).second; + + ObjectValues::value_type defaultValue(actualKey, nullRef); + it = value_.map_->insert(it, defaultValue); + Value& value = (*it).second; + return value; +} + +Value Value::get(ArrayIndex index, const Value& defaultValue) const { + const Value* value = &((*this)[index]); + return value == &nullRef ? defaultValue : *value; +} + +bool Value::isValidIndex(ArrayIndex index) const { return index < size(); } + +Value const* Value::find(char const* key, char const* cend) const +{ + JSON_ASSERT_MESSAGE( + type_ == nullValue || type_ == objectValue, + "in Json::Value::find(key, end, found): requires objectValue or nullValue"); + if (type_ == nullValue) return NULL; + CZString actualKey(key, static_cast(cend-key), CZString::noDuplication); + ObjectValues::const_iterator it = value_.map_->find(actualKey); + if (it == value_.map_->end()) return NULL; + return &(*it).second; +} +const Value& Value::operator[](const char* key) const +{ + Value const* found = find(key, key + strlen(key)); + if (!found) return nullRef; + return *found; +} +Value const& Value::operator[](std::string const& key) const +{ + Value const* found = find(key.data(), key.data() + key.length()); + if (!found) return nullRef; + return *found; +} + +Value& Value::operator[](const char* key) { + return resolveReference(key, key + strlen(key)); +} + +Value& Value::operator[](const std::string& key) { + return resolveReference(key.data(), key.data() + key.length()); +} + +Value& Value::operator[](const StaticString& key) { + return resolveReference(key.c_str()); +} + +#ifdef JSON_USE_CPPTL +Value& Value::operator[](const CppTL::ConstString& key) { + return resolveReference(key.c_str(), key.end_c_str()); +} +Value const& Value::operator[](CppTL::ConstString const& key) const +{ + Value const* found = find(key.c_str(), key.end_c_str()); + if (!found) return nullRef; + return *found; +} +#endif + +Value& Value::append(const Value& value) { return (*this)[size()] = value; } + +Value Value::get(char const* key, char const* cend, Value const& defaultValue) const +{ + Value const* found = find(key, cend); + return !found ? defaultValue : *found; +} +Value Value::get(char const* key, Value const& defaultValue) const +{ + return get(key, key + strlen(key), defaultValue); +} +Value Value::get(std::string const& key, Value const& defaultValue) const +{ + return get(key.data(), key.data() + key.length(), defaultValue); +} + + +bool Value::removeMember(const char* key, const char* cend, Value* removed) +{ + if (type_ != objectValue) { + return false; + } + CZString actualKey(key, static_cast(cend-key), CZString::noDuplication); + ObjectValues::iterator it = value_.map_->find(actualKey); + if (it == value_.map_->end()) + return false; + *removed = it->second; + value_.map_->erase(it); + return true; +} +bool Value::removeMember(const char* key, Value* removed) +{ + return removeMember(key, key + strlen(key), removed); +} +bool Value::removeMember(std::string const& key, Value* removed) +{ + return removeMember(key.data(), key.data() + key.length(), removed); +} +Value Value::removeMember(const char* key) +{ + JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == objectValue, + "in Json::Value::removeMember(): requires objectValue"); + if (type_ == nullValue) + return nullRef; + + Value removed; // null + removeMember(key, key + strlen(key), &removed); + return removed; // still null if removeMember() did nothing +} +Value Value::removeMember(const std::string& key) +{ + return removeMember(key.c_str()); +} + +bool Value::removeIndex(ArrayIndex index, Value* removed) { + if (type_ != arrayValue) { + return false; + } + CZString key(index); + ObjectValues::iterator it = value_.map_->find(key); + if (it == value_.map_->end()) { + return false; + } + *removed = it->second; + ArrayIndex oldSize = size(); + // shift left all items left, into the place of the "removed" + for (ArrayIndex i = index; i < (oldSize - 1); ++i){ + CZString keey(i); + (*value_.map_)[keey] = (*this)[i + 1]; + } + // erase the last one ("leftover") + CZString keyLast(oldSize - 1); + ObjectValues::iterator itLast = value_.map_->find(keyLast); + value_.map_->erase(itLast); + return true; +} + +#ifdef JSON_USE_CPPTL +Value Value::get(const CppTL::ConstString& key, + const Value& defaultValue) const { + return get(key.c_str(), key.end_c_str(), defaultValue); +} +#endif + +bool Value::isMember(char const* key, char const* cend) const +{ + Value const* value = find(key, cend); + return NULL != value; +} +bool Value::isMember(char const* key) const +{ + return isMember(key, key + strlen(key)); +} +bool Value::isMember(std::string const& key) const +{ + return isMember(key.data(), key.data() + key.length()); +} + +#ifdef JSON_USE_CPPTL +bool Value::isMember(const CppTL::ConstString& key) const { + return isMember(key.c_str(), key.end_c_str()); +} +#endif + +Value::Members Value::getMemberNames() const { + JSON_ASSERT_MESSAGE( + type_ == nullValue || type_ == objectValue, + "in Json::Value::getMemberNames(), value must be objectValue"); + if (type_ == nullValue) + return Value::Members(); + Members members; + members.reserve(value_.map_->size()); + ObjectValues::const_iterator it = value_.map_->begin(); + ObjectValues::const_iterator itEnd = value_.map_->end(); + for (; it != itEnd; ++it) { + members.push_back(std::string((*it).first.data(), + (*it).first.length())); + } + return members; +} +// +//# ifdef JSON_USE_CPPTL +// EnumMemberNames +// Value::enumMemberNames() const +//{ +// if ( type_ == objectValue ) +// { +// return CppTL::Enum::any( CppTL::Enum::transform( +// CppTL::Enum::keys( *(value_.map_), CppTL::Type() ), +// MemberNamesTransform() ) ); +// } +// return EnumMemberNames(); +//} +// +// +// EnumValues +// Value::enumValues() const +//{ +// if ( type_ == objectValue || type_ == arrayValue ) +// return CppTL::Enum::anyValues( *(value_.map_), +// CppTL::Type() ); +// return EnumValues(); +//} +// +//# endif + +static bool IsIntegral(double d) { + double integral_part; + return modf(d, &integral_part) == 0.0; +} + +bool Value::isNull() const { return type_ == nullValue; } + +bool Value::isBool() const { return type_ == booleanValue; } + +bool Value::isInt() const { + switch (type_) { + case intValue: + return value_.int_ >= minInt && value_.int_ <= maxInt; + case uintValue: + return value_.uint_ <= UInt(maxInt); + case realValue: + return value_.real_ >= minInt && value_.real_ <= maxInt && + IsIntegral(value_.real_); + default: + break; + } + return false; +} + +bool Value::isUInt() const { + switch (type_) { + case intValue: + return value_.int_ >= 0 && LargestUInt(value_.int_) <= LargestUInt(maxUInt); + case uintValue: + return value_.uint_ <= maxUInt; + case realValue: + return value_.real_ >= 0 && value_.real_ <= maxUInt && + IsIntegral(value_.real_); + default: + break; + } + return false; +} + +bool Value::isInt64() const { +#if defined(JSON_HAS_INT64) + switch (type_) { + case intValue: + return true; + case uintValue: + return value_.uint_ <= UInt64(maxInt64); + case realValue: + // Note that maxInt64 (= 2^63 - 1) is not exactly representable as a + // double, so double(maxInt64) will be rounded up to 2^63. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= double(minInt64) && + value_.real_ < double(maxInt64) && IsIntegral(value_.real_); + default: + break; + } +#endif // JSON_HAS_INT64 + return false; +} + +bool Value::isUInt64() const { +#if defined(JSON_HAS_INT64) + switch (type_) { + case intValue: + return value_.int_ >= 0; + case uintValue: + return true; + case realValue: + // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a + // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= 0 && value_.real_ < maxUInt64AsDouble && + IsIntegral(value_.real_); + default: + break; + } +#endif // JSON_HAS_INT64 + return false; +} + +bool Value::isIntegral() const { +#if defined(JSON_HAS_INT64) + return isInt64() || isUInt64(); +#else + return isInt() || isUInt(); +#endif +} + +bool Value::isDouble() const { return type_ == realValue || isIntegral(); } + +bool Value::isNumeric() const { return isIntegral() || isDouble(); } + +bool Value::isString() const { return type_ == stringValue; } + +bool Value::isArray() const { return type_ == arrayValue; } + +bool Value::isObject() const { return type_ == objectValue; } + +void Value::setComment(const char* comment, size_t len, CommentPlacement placement) { + if (!comments_) + comments_ = new CommentInfo[numberOfCommentPlacement]; + if ((len > 0) && (comment[len-1] == '\n')) { + // Always discard trailing newline, to aid indentation. + len -= 1; + } + comments_[placement].setComment(comment, len); +} + +void Value::setComment(const char* comment, CommentPlacement placement) { + setComment(comment, strlen(comment), placement); +} + +void Value::setComment(const std::string& comment, CommentPlacement placement) { + setComment(comment.c_str(), comment.length(), placement); +} + +bool Value::hasComment(CommentPlacement placement) const { + return comments_ != 0 && comments_[placement].comment_ != 0; +} + +std::string Value::getComment(CommentPlacement placement) const { + if (hasComment(placement)) + return comments_[placement].comment_; + return ""; +} + +void Value::setOffsetStart(size_t start) { start_ = start; } + +void Value::setOffsetLimit(size_t limit) { limit_ = limit; } + +size_t Value::getOffsetStart() const { return start_; } + +size_t Value::getOffsetLimit() const { return limit_; } + +std::string Value::toStyledString() const { + StyledWriter writer; + return writer.write(*this); +} + +Value::const_iterator Value::begin() const { + switch (type_) { + case arrayValue: + case objectValue: + if (value_.map_) + return const_iterator(value_.map_->begin()); + break; + default: + break; + } + return const_iterator(); +} + +Value::const_iterator Value::end() const { + switch (type_) { + case arrayValue: + case objectValue: + if (value_.map_) + return const_iterator(value_.map_->end()); + break; + default: + break; + } + return const_iterator(); +} + +Value::iterator Value::begin() { + switch (type_) { + case arrayValue: + case objectValue: + if (value_.map_) + return iterator(value_.map_->begin()); + break; + default: + break; + } + return iterator(); +} + +Value::iterator Value::end() { + switch (type_) { + case arrayValue: + case objectValue: + if (value_.map_) + return iterator(value_.map_->end()); + break; + default: + break; + } + return iterator(); +} + +// class PathArgument +// ////////////////////////////////////////////////////////////////// + +PathArgument::PathArgument() : key_(), index_(), kind_(kindNone) {} + +PathArgument::PathArgument(ArrayIndex index) + : key_(), index_(index), kind_(kindIndex) {} + +PathArgument::PathArgument(const char* key) + : key_(key), index_(), kind_(kindKey) {} + +PathArgument::PathArgument(const std::string& key) + : key_(key.c_str()), index_(), kind_(kindKey) {} + +// class Path +// ////////////////////////////////////////////////////////////////// + +Path::Path(const std::string& path, + const PathArgument& a1, + const PathArgument& a2, + const PathArgument& a3, + const PathArgument& a4, + const PathArgument& a5) { + InArgs in; + in.push_back(&a1); + in.push_back(&a2); + in.push_back(&a3); + in.push_back(&a4); + in.push_back(&a5); + makePath(path, in); +} + +void Path::makePath(const std::string& path, const InArgs& in) { + const char* current = path.c_str(); + const char* end = current + path.length(); + InArgs::const_iterator itInArg = in.begin(); + while (current != end) { + if (*current == '[') { + ++current; + if (*current == '%') + addPathInArg(path, in, itInArg, PathArgument::kindIndex); + else { + ArrayIndex index = 0; + for (; current != end && *current >= '0' && *current <= '9'; ++current) + index = index * 10 + ArrayIndex(*current - '0'); + args_.push_back(index); + } + if (current == end || *current++ != ']') + invalidPath(path, int(current - path.c_str())); + } else if (*current == '%') { + addPathInArg(path, in, itInArg, PathArgument::kindKey); + ++current; + } else if (*current == '.') { + ++current; + } else { + const char* beginName = current; + while (current != end && !strchr("[.", *current)) + ++current; + args_.push_back(std::string(beginName, current)); + } + } +} + +void Path::addPathInArg(const std::string& /*path*/, + const InArgs& in, + InArgs::const_iterator& itInArg, + PathArgument::Kind kind) { + if (itInArg == in.end()) { + // Error: missing argument %d + } else if ((*itInArg)->kind_ != kind) { + // Error: bad argument type + } else { + args_.push_back(**itInArg); + } +} + +void Path::invalidPath(const std::string& /*path*/, int /*location*/) { + // Error: invalid path. +} + +const Value& Path::resolve(const Value& root) const { + const Value* node = &root; + for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) { + const PathArgument& arg = *it; + if (arg.kind_ == PathArgument::kindIndex) { + if (!node->isArray() || !node->isValidIndex(arg.index_)) { + // Error: unable to resolve path (array value expected at position... + } + node = &((*node)[arg.index_]); + } else if (arg.kind_ == PathArgument::kindKey) { + if (!node->isObject()) { + // Error: unable to resolve path (object value expected at position...) + } + node = &((*node)[arg.key_]); + if (node == &Value::nullRef) { + // Error: unable to resolve path (object has no member named '' at + // position...) + } + } + } + return *node; +} + +Value Path::resolve(const Value& root, const Value& defaultValue) const { + const Value* node = &root; + for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) { + const PathArgument& arg = *it; + if (arg.kind_ == PathArgument::kindIndex) { + if (!node->isArray() || !node->isValidIndex(arg.index_)) + return defaultValue; + node = &((*node)[arg.index_]); + } else if (arg.kind_ == PathArgument::kindKey) { + if (!node->isObject()) + return defaultValue; + node = &((*node)[arg.key_]); + if (node == &Value::nullRef) + return defaultValue; + } + } + return *node; +} + +Value& Path::make(Value& root) const { + Value* node = &root; + for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) { + const PathArgument& arg = *it; + if (arg.kind_ == PathArgument::kindIndex) { + if (!node->isArray()) { + // Error: node is not an array at position ... + } + node = &((*node)[arg.index_]); + } else if (arg.kind_ == PathArgument::kindKey) { + if (!node->isObject()) { + // Error: node is not an object at position... + } + node = &((*node)[arg.key_]); + } + } + return *node; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_value.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_writer.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2011 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +#include +#include "json_tool.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER >= 1200 && _MSC_VER < 1800 // Between VC++ 6.0 and VC++ 11.0 +#include +#define isfinite _finite +#elif defined(__sun) && defined(__SVR4) //Solaris +#include +#define isfinite finite +#else +#include +#define isfinite std::isfinite +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1500 // VC++ 8.0 and below +#define snprintf _snprintf +#elif defined(__ANDROID__) +#define snprintf snprintf +#elif __cplusplus >= 201103L +#define snprintf std::snprintf +#endif + +#if defined(__BORLANDC__) +#include +#define isfinite _finite +#define snprintf _snprintf +#endif + +#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0 +// Disable warning about strdup being deprecated. +#pragma warning(disable : 4996) +#endif + +namespace Json { + +#if __cplusplus >= 201103L +typedef std::unique_ptr StreamWriterPtr; +#else +typedef std::auto_ptr StreamWriterPtr; +#endif + +static bool containsControlCharacter(const char* str) { + while (*str) { + if (isControlCharacter(*(str++))) + return true; + } + return false; +} + +static bool containsControlCharacter0(const char* str, unsigned len) { + char const* end = str + len; + while (end != str) { + if (isControlCharacter(*str) || 0==*str) + return true; + ++str; + } + return false; +} + +std::string valueToString(LargestInt value) { + UIntToStringBuffer buffer; + char* current = buffer + sizeof(buffer); + if (value == Value::minLargestInt) { + uintToString(LargestUInt(Value::maxLargestInt) + 1, current); + *--current = '-'; + } else if (value < 0) { + uintToString(LargestUInt(-value), current); + *--current = '-'; + } else { + uintToString(LargestUInt(value), current); + } + assert(current >= buffer); + return current; +} + +std::string valueToString(LargestUInt value) { + UIntToStringBuffer buffer; + char* current = buffer + sizeof(buffer); + uintToString(value, current); + assert(current >= buffer); + return current; +} + +#if defined(JSON_HAS_INT64) + +std::string valueToString(Int value) { + return valueToString(LargestInt(value)); +} + +std::string valueToString(UInt value) { + return valueToString(LargestUInt(value)); +} + +#endif // # if defined(JSON_HAS_INT64) + +std::string valueToString(double value) { + // Allocate a buffer that is more than large enough to store the 16 digits of + // precision requested below. + char buffer[32]; + int len = -1; + +// Print into the buffer. We need not request the alternative representation +// that always has a decimal point because JSON doesn't distingish the +// concepts of reals and integers. +#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with + // visual studio 2005 to + // avoid warning. +#if defined(WINCE) + len = _snprintf(buffer, sizeof(buffer), "%.17g", value); +#else + len = sprintf_s(buffer, sizeof(buffer), "%.17g", value); +#endif +#else + if (isfinite(value)) { + len = snprintf(buffer, sizeof(buffer), "%.17g", value); + } else { + // IEEE standard states that NaN values will not compare to themselves + if (value != value) { + len = snprintf(buffer, sizeof(buffer), "null"); + } else if (value < 0) { + len = snprintf(buffer, sizeof(buffer), "-1e+9999"); + } else { + len = snprintf(buffer, sizeof(buffer), "1e+9999"); + } + // For those, we do not need to call fixNumLoc, but it is fast. + } +#endif + assert(len >= 0); + fixNumericLocale(buffer, buffer + len); + return buffer; +} + +std::string valueToString(bool value) { return value ? "true" : "false"; } + +std::string valueToQuotedString(const char* value) { + if (value == NULL) + return ""; + // Not sure how to handle unicode... + if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL && + !containsControlCharacter(value)) + return std::string("\"") + value + "\""; + // We have to walk value and escape any special characters. + // Appending to std::string is not efficient, but this should be rare. + // (Note: forward slashes are *not* rare, but I am not escaping them.) + std::string::size_type maxsize = + strlen(value) * 2 + 3; // allescaped+quotes+NULL + std::string result; + result.reserve(maxsize); // to avoid lots of mallocs + result += "\""; + for (const char* c = value; *c != 0; ++c) { + switch (*c) { + case '\"': + result += "\\\""; + break; + case '\\': + result += "\\\\"; + break; + case '\b': + result += "\\b"; + break; + case '\f': + result += "\\f"; + break; + case '\n': + result += "\\n"; + break; + case '\r': + result += "\\r"; + break; + case '\t': + result += "\\t"; + break; + // case '/': + // Even though \/ is considered a legal escape in JSON, a bare + // slash is also legal, so I see no reason to escape it. + // (I hope I am not misunderstanding something. + // blep notes: actually escaping \/ may be useful in javascript to avoid (*c); + result += oss.str(); + } else { + result += *c; + } + break; + } + } + result += "\""; + return result; +} + +// https://github.com/upcaste/upcaste/blob/master/src/upcore/src/cstring/strnpbrk.cpp +static char const* strnpbrk(char const* s, char const* accept, size_t n) { + assert((s || !n) && accept); + + char const* const end = s + n; + for (char const* cur = s; cur < end; ++cur) { + int const c = *cur; + for (char const* a = accept; *a; ++a) { + if (*a == c) { + return cur; + } + } + } + return NULL; +} +static std::string valueToQuotedStringN(const char* value, unsigned length) { + if (value == NULL) + return ""; + // Not sure how to handle unicode... + if (strnpbrk(value, "\"\\\b\f\n\r\t", length) == NULL && + !containsControlCharacter0(value, length)) + return std::string("\"") + value + "\""; + // We have to walk value and escape any special characters. + // Appending to std::string is not efficient, but this should be rare. + // (Note: forward slashes are *not* rare, but I am not escaping them.) + std::string::size_type maxsize = + length * 2 + 3; // allescaped+quotes+NULL + std::string result; + result.reserve(maxsize); // to avoid lots of mallocs + result += "\""; + char const* end = value + length; + for (const char* c = value; c != end; ++c) { + switch (*c) { + case '\"': + result += "\\\""; + break; + case '\\': + result += "\\\\"; + break; + case '\b': + result += "\\b"; + break; + case '\f': + result += "\\f"; + break; + case '\n': + result += "\\n"; + break; + case '\r': + result += "\\r"; + break; + case '\t': + result += "\\t"; + break; + // case '/': + // Even though \/ is considered a legal escape in JSON, a bare + // slash is also legal, so I see no reason to escape it. + // (I hope I am not misunderstanding something.) + // blep notes: actually escaping \/ may be useful in javascript to avoid (*c); + result += oss.str(); + } else { + result += *c; + } + break; + } + } + result += "\""; + return result; +} + +// Class Writer +// ////////////////////////////////////////////////////////////////// +Writer::~Writer() {} + +// Class FastWriter +// ////////////////////////////////////////////////////////////////// + +FastWriter::FastWriter() + : yamlCompatiblityEnabled_(false), dropNullPlaceholders_(false), + omitEndingLineFeed_(false) {} + +void FastWriter::enableYAMLCompatibility() { yamlCompatiblityEnabled_ = true; } + +void FastWriter::dropNullPlaceholders() { dropNullPlaceholders_ = true; } + +void FastWriter::omitEndingLineFeed() { omitEndingLineFeed_ = true; } + +std::string FastWriter::write(const Value& root) { + document_ = ""; + writeValue(root); + if (!omitEndingLineFeed_) + document_ += "\n"; + return document_; +} + +void FastWriter::writeValue(const Value& value) { + switch (value.type()) { + case nullValue: + if (!dropNullPlaceholders_) + document_ += "null"; + break; + case intValue: + document_ += valueToString(value.asLargestInt()); + break; + case uintValue: + document_ += valueToString(value.asLargestUInt()); + break; + case realValue: + document_ += valueToString(value.asDouble()); + break; + case stringValue: + { + // Is NULL possible for value.string_? + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) document_ += valueToQuotedStringN(str, static_cast(end-str)); + break; + } + case booleanValue: + document_ += valueToString(value.asBool()); + break; + case arrayValue: { + document_ += '['; + int size = value.size(); + for (int index = 0; index < size; ++index) { + if (index > 0) + document_ += ','; + writeValue(value[index]); + } + document_ += ']'; + } break; + case objectValue: { + Value::Members members(value.getMemberNames()); + document_ += '{'; + for (Value::Members::iterator it = members.begin(); it != members.end(); + ++it) { + const std::string& name = *it; + if (it != members.begin()) + document_ += ','; + document_ += valueToQuotedStringN(name.data(), static_cast(name.length())); + document_ += yamlCompatiblityEnabled_ ? ": " : ":"; + writeValue(value[name]); + } + document_ += '}'; + } break; + } +} + +// Class StyledWriter +// ////////////////////////////////////////////////////////////////// + +StyledWriter::StyledWriter() + : rightMargin_(74), indentSize_(3), addChildValues_() {} + +std::string StyledWriter::write(const Value& root) { + document_ = ""; + addChildValues_ = false; + indentString_ = ""; + writeCommentBeforeValue(root); + writeValue(root); + writeCommentAfterValueOnSameLine(root); + document_ += "\n"; + return document_; +} + +void StyledWriter::writeValue(const Value& value) { + switch (value.type()) { + case nullValue: + pushValue("null"); + break; + case intValue: + pushValue(valueToString(value.asLargestInt())); + break; + case uintValue: + pushValue(valueToString(value.asLargestUInt())); + break; + case realValue: + pushValue(valueToString(value.asDouble())); + break; + case stringValue: + { + // Is NULL possible for value.string_? + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) pushValue(valueToQuotedStringN(str, static_cast(end-str))); + else pushValue(""); + break; + } + case booleanValue: + pushValue(valueToString(value.asBool())); + break; + case arrayValue: + writeArrayValue(value); + break; + case objectValue: { + Value::Members members(value.getMemberNames()); + if (members.empty()) + pushValue("{}"); + else { + writeWithIndent("{"); + indent(); + Value::Members::iterator it = members.begin(); + for (;;) { + const std::string& name = *it; + const Value& childValue = value[name]; + writeCommentBeforeValue(childValue); + writeWithIndent(valueToQuotedString(name.c_str())); + document_ += " : "; + writeValue(childValue); + if (++it == members.end()) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + document_ += ','; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("}"); + } + } break; + } +} + +void StyledWriter::writeArrayValue(const Value& value) { + unsigned size = value.size(); + if (size == 0) + pushValue("[]"); + else { + bool isArrayMultiLine = isMultineArray(value); + if (isArrayMultiLine) { + writeWithIndent("["); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index = 0; + for (;;) { + const Value& childValue = value[index]; + writeCommentBeforeValue(childValue); + if (hasChildValue) + writeWithIndent(childValues_[index]); + else { + writeIndent(); + writeValue(childValue); + } + if (++index == size) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + document_ += ','; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("]"); + } else // output on a single line + { + assert(childValues_.size() == size); + document_ += "[ "; + for (unsigned index = 0; index < size; ++index) { + if (index > 0) + document_ += ", "; + document_ += childValues_[index]; + } + document_ += " ]"; + } + } +} + +bool StyledWriter::isMultineArray(const Value& value) { + int size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (int index = 0; index < size && !isMultiLine; ++index) { + const Value& childValue = value[index]; + isMultiLine = + isMultiLine || ((childValue.isArray() || childValue.isObject()) && + childValue.size() > 0); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (int index = 0; index < size; ++index) { + if (hasCommentForValue(value[index])) { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += int(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + +void StyledWriter::pushValue(const std::string& value) { + if (addChildValues_) + childValues_.push_back(value); + else + document_ += value; +} + +void StyledWriter::writeIndent() { + if (!document_.empty()) { + char last = document_[document_.length() - 1]; + if (last == ' ') // already indented + return; + if (last != '\n') // Comments may add new-line + document_ += '\n'; + } + document_ += indentString_; +} + +void StyledWriter::writeWithIndent(const std::string& value) { + writeIndent(); + document_ += value; +} + +void StyledWriter::indent() { indentString_ += std::string(indentSize_, ' '); } + +void StyledWriter::unindent() { + assert(int(indentString_.size()) >= indentSize_); + indentString_.resize(indentString_.size() - indentSize_); +} + +void StyledWriter::writeCommentBeforeValue(const Value& root) { + if (!root.hasComment(commentBefore)) + return; + + document_ += "\n"; + writeIndent(); + const std::string& comment = root.getComment(commentBefore); + std::string::const_iterator iter = comment.begin(); + while (iter != comment.end()) { + document_ += *iter; + if (*iter == '\n' && + (iter != comment.end() && *(iter + 1) == '/')) + writeIndent(); + ++iter; + } + + // Comments are stripped of trailing newlines, so add one here + document_ += "\n"; +} + +void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) { + if (root.hasComment(commentAfterOnSameLine)) + document_ += " " + root.getComment(commentAfterOnSameLine); + + if (root.hasComment(commentAfter)) { + document_ += "\n"; + document_ += root.getComment(commentAfter); + document_ += "\n"; + } +} + +bool StyledWriter::hasCommentForValue(const Value& value) { + return value.hasComment(commentBefore) || + value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); +} + +// Class StyledStreamWriter +// ////////////////////////////////////////////////////////////////// + +StyledStreamWriter::StyledStreamWriter(std::string indentation) + : document_(NULL), rightMargin_(74), indentation_(indentation), + addChildValues_() {} + +void StyledStreamWriter::write(std::ostream& out, const Value& root) { + document_ = &out; + addChildValues_ = false; + indentString_ = ""; + indented_ = true; + writeCommentBeforeValue(root); + if (!indented_) writeIndent(); + indented_ = true; + writeValue(root); + writeCommentAfterValueOnSameLine(root); + *document_ << "\n"; + document_ = NULL; // Forget the stream, for safety. +} + +void StyledStreamWriter::writeValue(const Value& value) { + switch (value.type()) { + case nullValue: + pushValue("null"); + break; + case intValue: + pushValue(valueToString(value.asLargestInt())); + break; + case uintValue: + pushValue(valueToString(value.asLargestUInt())); + break; + case realValue: + pushValue(valueToString(value.asDouble())); + break; + case stringValue: + { + // Is NULL possible for value.string_? + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) pushValue(valueToQuotedStringN(str, static_cast(end-str))); + else pushValue(""); + break; + } + case booleanValue: + pushValue(valueToString(value.asBool())); + break; + case arrayValue: + writeArrayValue(value); + break; + case objectValue: { + Value::Members members(value.getMemberNames()); + if (members.empty()) + pushValue("{}"); + else { + writeWithIndent("{"); + indent(); + Value::Members::iterator it = members.begin(); + for (;;) { + const std::string& name = *it; + const Value& childValue = value[name]; + writeCommentBeforeValue(childValue); + writeWithIndent(valueToQuotedString(name.c_str())); + *document_ << " : "; + writeValue(childValue); + if (++it == members.end()) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *document_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("}"); + } + } break; + } +} + +void StyledStreamWriter::writeArrayValue(const Value& value) { + unsigned size = value.size(); + if (size == 0) + pushValue("[]"); + else { + bool isArrayMultiLine = isMultineArray(value); + if (isArrayMultiLine) { + writeWithIndent("["); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index = 0; + for (;;) { + const Value& childValue = value[index]; + writeCommentBeforeValue(childValue); + if (hasChildValue) + writeWithIndent(childValues_[index]); + else { + if (!indented_) writeIndent(); + indented_ = true; + writeValue(childValue); + indented_ = false; + } + if (++index == size) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *document_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("]"); + } else // output on a single line + { + assert(childValues_.size() == size); + *document_ << "[ "; + for (unsigned index = 0; index < size; ++index) { + if (index > 0) + *document_ << ", "; + *document_ << childValues_[index]; + } + *document_ << " ]"; + } + } +} + +bool StyledStreamWriter::isMultineArray(const Value& value) { + int size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (int index = 0; index < size && !isMultiLine; ++index) { + const Value& childValue = value[index]; + isMultiLine = + isMultiLine || ((childValue.isArray() || childValue.isObject()) && + childValue.size() > 0); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (int index = 0; index < size; ++index) { + if (hasCommentForValue(value[index])) { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += int(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + +void StyledStreamWriter::pushValue(const std::string& value) { + if (addChildValues_) + childValues_.push_back(value); + else + *document_ << value; +} + +void StyledStreamWriter::writeIndent() { + // blep intended this to look at the so-far-written string + // to determine whether we are already indented, but + // with a stream we cannot do that. So we rely on some saved state. + // The caller checks indented_. + *document_ << '\n' << indentString_; +} + +void StyledStreamWriter::writeWithIndent(const std::string& value) { + if (!indented_) writeIndent(); + *document_ << value; + indented_ = false; +} + +void StyledStreamWriter::indent() { indentString_ += indentation_; } + +void StyledStreamWriter::unindent() { + assert(indentString_.size() >= indentation_.size()); + indentString_.resize(indentString_.size() - indentation_.size()); +} + +void StyledStreamWriter::writeCommentBeforeValue(const Value& root) { + if (!root.hasComment(commentBefore)) + return; + + if (!indented_) writeIndent(); + const std::string& comment = root.getComment(commentBefore); + std::string::const_iterator iter = comment.begin(); + while (iter != comment.end()) { + *document_ << *iter; + if (*iter == '\n' && + (iter != comment.end() && *(iter + 1) == '/')) + // writeIndent(); // would include newline + *document_ << indentString_; + ++iter; + } + indented_ = false; +} + +void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) { + if (root.hasComment(commentAfterOnSameLine)) + *document_ << ' ' << root.getComment(commentAfterOnSameLine); + + if (root.hasComment(commentAfter)) { + writeIndent(); + *document_ << root.getComment(commentAfter); + } + indented_ = false; +} + +bool StyledStreamWriter::hasCommentForValue(const Value& value) { + return value.hasComment(commentBefore) || + value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); +} + +////////////////////////// +// BuiltStyledStreamWriter + +/// Scoped enums are not available until C++11. +struct CommentStyle { + /// Decide whether to write comments. + enum Enum { + None, ///< Drop all comments. + Most, ///< Recover odd behavior of previous versions (not implemented yet). + All ///< Keep all comments. + }; +}; + +struct BuiltStyledStreamWriter : public StreamWriter +{ + BuiltStyledStreamWriter( + std::string const& indentation, + CommentStyle::Enum cs, + std::string const& colonSymbol, + std::string const& nullSymbol, + std::string const& endingLineFeedSymbol); + virtual int write(Value const& root, std::ostream* sout); +private: + void writeValue(Value const& value); + void writeArrayValue(Value const& value); + bool isMultineArray(Value const& value); + void pushValue(std::string const& value); + void writeIndent(); + void writeWithIndent(std::string const& value); + void indent(); + void unindent(); + void writeCommentBeforeValue(Value const& root); + void writeCommentAfterValueOnSameLine(Value const& root); + static bool hasCommentForValue(const Value& value); + + typedef std::vector ChildValues; + + ChildValues childValues_; + std::string indentString_; + int rightMargin_; + std::string indentation_; + CommentStyle::Enum cs_; + std::string colonSymbol_; + std::string nullSymbol_; + std::string endingLineFeedSymbol_; + bool addChildValues_ : 1; + bool indented_ : 1; +}; +BuiltStyledStreamWriter::BuiltStyledStreamWriter( + std::string const& indentation, + CommentStyle::Enum cs, + std::string const& colonSymbol, + std::string const& nullSymbol, + std::string const& endingLineFeedSymbol) + : rightMargin_(74) + , indentation_(indentation) + , cs_(cs) + , colonSymbol_(colonSymbol) + , nullSymbol_(nullSymbol) + , endingLineFeedSymbol_(endingLineFeedSymbol) + , addChildValues_(false) + , indented_(false) +{ +} +int BuiltStyledStreamWriter::write(Value const& root, std::ostream* sout) +{ + sout_ = sout; + addChildValues_ = false; + indented_ = true; + indentString_ = ""; + writeCommentBeforeValue(root); + if (!indented_) writeIndent(); + indented_ = true; + writeValue(root); + writeCommentAfterValueOnSameLine(root); + *sout_ << endingLineFeedSymbol_; + sout_ = NULL; + return 0; +} +void BuiltStyledStreamWriter::writeValue(Value const& value) { + switch (value.type()) { + case nullValue: + pushValue(nullSymbol_); + break; + case intValue: + pushValue(valueToString(value.asLargestInt())); + break; + case uintValue: + pushValue(valueToString(value.asLargestUInt())); + break; + case realValue: + pushValue(valueToString(value.asDouble())); + break; + case stringValue: + { + // Is NULL is possible for value.string_? + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) pushValue(valueToQuotedStringN(str, static_cast(end-str))); + else pushValue(""); + break; + } + case booleanValue: + pushValue(valueToString(value.asBool())); + break; + case arrayValue: + writeArrayValue(value); + break; + case objectValue: { + Value::Members members(value.getMemberNames()); + if (members.empty()) + pushValue("{}"); + else { + writeWithIndent("{"); + indent(); + Value::Members::iterator it = members.begin(); + for (;;) { + std::string const& name = *it; + Value const& childValue = value[name]; + writeCommentBeforeValue(childValue); + writeWithIndent(valueToQuotedStringN(name.data(), static_cast(name.length()))); + *sout_ << colonSymbol_; + writeValue(childValue); + if (++it == members.end()) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *sout_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("}"); + } + } break; + } +} + +void BuiltStyledStreamWriter::writeArrayValue(Value const& value) { + unsigned size = value.size(); + if (size == 0) + pushValue("[]"); + else { + bool isMultiLine = (cs_ == CommentStyle::All) || isMultineArray(value); + if (isMultiLine) { + writeWithIndent("["); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index = 0; + for (;;) { + Value const& childValue = value[index]; + writeCommentBeforeValue(childValue); + if (hasChildValue) + writeWithIndent(childValues_[index]); + else { + if (!indented_) writeIndent(); + indented_ = true; + writeValue(childValue); + indented_ = false; + } + if (++index == size) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *sout_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("]"); + } else // output on a single line + { + assert(childValues_.size() == size); + *sout_ << "["; + if (!indentation_.empty()) *sout_ << " "; + for (unsigned index = 0; index < size; ++index) { + if (index > 0) + *sout_ << ", "; + *sout_ << childValues_[index]; + } + if (!indentation_.empty()) *sout_ << " "; + *sout_ << "]"; + } + } +} + +bool BuiltStyledStreamWriter::isMultineArray(Value const& value) { + int size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (int index = 0; index < size && !isMultiLine; ++index) { + Value const& childValue = value[index]; + isMultiLine = + isMultiLine || ((childValue.isArray() || childValue.isObject()) && + childValue.size() > 0); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (int index = 0; index < size; ++index) { + if (hasCommentForValue(value[index])) { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += int(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + +void BuiltStyledStreamWriter::pushValue(std::string const& value) { + if (addChildValues_) + childValues_.push_back(value); + else + *sout_ << value; +} + +void BuiltStyledStreamWriter::writeIndent() { + // blep intended this to look at the so-far-written string + // to determine whether we are already indented, but + // with a stream we cannot do that. So we rely on some saved state. + // The caller checks indented_. + + if (!indentation_.empty()) { + // In this case, drop newlines too. + *sout_ << '\n' << indentString_; + } +} + +void BuiltStyledStreamWriter::writeWithIndent(std::string const& value) { + if (!indented_) writeIndent(); + *sout_ << value; + indented_ = false; +} + +void BuiltStyledStreamWriter::indent() { indentString_ += indentation_; } + +void BuiltStyledStreamWriter::unindent() { + assert(indentString_.size() >= indentation_.size()); + indentString_.resize(indentString_.size() - indentation_.size()); +} + +void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) { + if (cs_ == CommentStyle::None) return; + if (!root.hasComment(commentBefore)) + return; + + if (!indented_) writeIndent(); + const std::string& comment = root.getComment(commentBefore); + std::string::const_iterator iter = comment.begin(); + while (iter != comment.end()) { + *sout_ << *iter; + if (*iter == '\n' && + (iter != comment.end() && *(iter + 1) == '/')) + // writeIndent(); // would write extra newline + *sout_ << indentString_; + ++iter; + } + indented_ = false; +} + +void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(Value const& root) { + if (cs_ == CommentStyle::None) return; + if (root.hasComment(commentAfterOnSameLine)) + *sout_ << " " + root.getComment(commentAfterOnSameLine); + + if (root.hasComment(commentAfter)) { + writeIndent(); + *sout_ << root.getComment(commentAfter); + } +} + +// static +bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) { + return value.hasComment(commentBefore) || + value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); +} + +/////////////// +// StreamWriter + +StreamWriter::StreamWriter() + : sout_(NULL) +{ +} +StreamWriter::~StreamWriter() +{ +} +StreamWriter::Factory::~Factory() +{} +StreamWriterBuilder::StreamWriterBuilder() +{ + setDefaults(&settings_); +} +StreamWriterBuilder::~StreamWriterBuilder() +{} +StreamWriter* StreamWriterBuilder::newStreamWriter() const +{ + std::string indentation = settings_["indentation"].asString(); + std::string cs_str = settings_["commentStyle"].asString(); + bool eyc = settings_["enableYAMLCompatibility"].asBool(); + bool dnp = settings_["dropNullPlaceholders"].asBool(); + CommentStyle::Enum cs = CommentStyle::All; + if (cs_str == "All") { + cs = CommentStyle::All; + } else if (cs_str == "None") { + cs = CommentStyle::None; + } else { + throwRuntimeError("commentStyle must be 'All' or 'None'"); + } + std::string colonSymbol = " : "; + if (eyc) { + colonSymbol = ": "; + } else if (indentation.empty()) { + colonSymbol = ":"; + } + std::string nullSymbol = "null"; + if (dnp) { + nullSymbol = ""; + } + std::string endingLineFeedSymbol = ""; + return new BuiltStyledStreamWriter( + indentation, cs, + colonSymbol, nullSymbol, endingLineFeedSymbol); +} +static void getValidWriterKeys(std::set* valid_keys) +{ + valid_keys->clear(); + valid_keys->insert("indentation"); + valid_keys->insert("commentStyle"); + valid_keys->insert("enableYAMLCompatibility"); + valid_keys->insert("dropNullPlaceholders"); +} +bool StreamWriterBuilder::validate(Json::Value* invalid) const +{ + Json::Value my_invalid; + if (!invalid) invalid = &my_invalid; // so we do not need to test for NULL + Json::Value& inv = *invalid; + std::set valid_keys; + getValidWriterKeys(&valid_keys); + Value::Members keys = settings_.getMemberNames(); + size_t n = keys.size(); + for (size_t i = 0; i < n; ++i) { + std::string const& key = keys[i]; + if (valid_keys.find(key) == valid_keys.end()) { + inv[key] = settings_[key]; + } + } + return 0u == inv.size(); +} +Value& StreamWriterBuilder::operator[](std::string key) +{ + return settings_[key]; +} +// static +void StreamWriterBuilder::setDefaults(Json::Value* settings) +{ + //! [StreamWriterBuilderDefaults] + (*settings)["commentStyle"] = "All"; + (*settings)["indentation"] = "\t"; + (*settings)["enableYAMLCompatibility"] = false; + (*settings)["dropNullPlaceholders"] = false; + //! [StreamWriterBuilderDefaults] +} + +std::string writeString(StreamWriter::Factory const& builder, Value const& root) { + std::ostringstream sout; + StreamWriterPtr const writer(builder.newStreamWriter()); + writer->write(root, &sout); + return sout.str(); +} + +std::ostream& operator<<(std::ostream& sout, Value const& root) { + StreamWriterBuilder builder; + StreamWriterPtr const writer(builder.newStreamWriter()); + writer->write(root, &sout); + return sout; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_writer.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + diff --git a/depends/jsoncpp/jsoncpp.h b/depends/jsoncpp/jsoncpp.h new file mode 100644 index 000000000..da3807d31 --- /dev/null +++ b/depends/jsoncpp/jsoncpp.h @@ -0,0 +1,2031 @@ +/// Json-cpp amalgated header (http://jsoncpp.sourceforge.net/). +/// It is intended to be used with #include "json/json.h" + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + +/* +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +The author (Baptiste Lepilleur) explicitly disclaims copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur, and is +released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur + +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. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. + +*/ + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + + + + + +#ifndef JSON_AMALGATED_H_INCLUDED +# define JSON_AMALGATED_H_INCLUDED +/// If defined, indicates that the source file is amalgated +/// to prevent private header inclusion. +#define JSON_IS_AMALGAMATION + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/version.h +// ////////////////////////////////////////////////////////////////////// + +// DO NOT EDIT. This file (and "version") is generated by CMake. +// Run CMake configure step to update it. +#ifndef JSON_VERSION_H_INCLUDED +# define JSON_VERSION_H_INCLUDED + +# define JSONCPP_VERSION_STRING "1.6.5" +# define JSONCPP_VERSION_MAJOR 1 +# define JSONCPP_VERSION_MINOR 6 +# define JSONCPP_VERSION_PATCH 5 +# define JSONCPP_VERSION_QUALIFIER +# define JSONCPP_VERSION_HEXA ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | (JSONCPP_VERSION_PATCH << 8)) + +#endif // JSON_VERSION_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/version.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_CONFIG_H_INCLUDED +#define JSON_CONFIG_H_INCLUDED + +/// If defined, indicates that json library is embedded in CppTL library. +//# define JSON_IN_CPPTL 1 + +/// If defined, indicates that json may leverage CppTL library +//# define JSON_USE_CPPTL 1 +/// If defined, indicates that cpptl vector based map should be used instead of +/// std::map +/// as Value container. +//# define JSON_USE_CPPTL_SMALLMAP 1 + +// If non-zero, the library uses exceptions to report bad input instead of C +// assertion macros. The default is to use exceptions. +#ifndef JSON_USE_EXCEPTION +#define JSON_USE_EXCEPTION 1 +#endif + +/// If defined, indicates that the source file is amalgated +/// to prevent private header inclusion. +/// Remarks: it is automatically defined in the generated amalgated header. +// #define JSON_IS_AMALGAMATION + +#ifdef JSON_IN_CPPTL +#include +#ifndef JSON_USE_CPPTL +#define JSON_USE_CPPTL 1 +#endif +#endif + +#ifdef JSON_IN_CPPTL +#define JSON_API CPPTL_API +#elif defined(JSON_DLL_BUILD) +#if defined(_MSC_VER) +#define JSON_API __declspec(dllexport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#endif // if defined(_MSC_VER) +#elif defined(JSON_DLL) +#if defined(_MSC_VER) +#define JSON_API __declspec(dllimport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#endif // if defined(_MSC_VER) +#endif // ifdef JSON_IN_CPPTL +#if !defined(JSON_API) +#define JSON_API +#endif + +// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for +// integer +// Storages, and 64 bits integer support is disabled. +// #define JSON_NO_INT64 1 + +#if defined(_MSC_VER) && _MSC_VER <= 1200 // MSVC 6 +// Microsoft Visual Studio 6 only support conversion from __int64 to double +// (no conversion from unsigned __int64). +#define JSON_USE_INT64_DOUBLE_CONVERSION 1 +// Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255' +// characters in the debug information) +// All projects I've ever seen with VS6 were using this globally (not bothering +// with pragma push/pop). +#pragma warning(disable : 4786) +#endif // if defined(_MSC_VER) && _MSC_VER < 1200 // MSVC 6 + +#if defined(_MSC_VER) && _MSC_VER >= 1500 // MSVC 2008 +/// Indicates that the following function is deprecated. +#define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) +#elif defined(__clang__) && defined(__has_feature) +#if __has_feature(attribute_deprecated_with_message) +#define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message))) +#endif +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +#define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message))) +#elif defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +#define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) +#endif + +#if !defined(JSONCPP_DEPRECATED) +#define JSONCPP_DEPRECATED(message) +#endif // if !defined(JSONCPP_DEPRECATED) + +namespace Json { +typedef int Int; +typedef unsigned int UInt; +#if defined(JSON_NO_INT64) +typedef int LargestInt; +typedef unsigned int LargestUInt; +#undef JSON_HAS_INT64 +#else // if defined(JSON_NO_INT64) +// For Microsoft Visual use specific types as long long is not supported +#if defined(_MSC_VER) // Microsoft Visual Studio +typedef __int64 Int64; +typedef unsigned __int64 UInt64; +#else // if defined(_MSC_VER) // Other platforms, use long long +typedef long long int Int64; +typedef unsigned long long int UInt64; +#endif // if defined(_MSC_VER) +typedef Int64 LargestInt; +typedef UInt64 LargestUInt; +#define JSON_HAS_INT64 +#endif // if defined(JSON_NO_INT64) +} // end namespace Json + +#endif // JSON_CONFIG_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_FORWARDS_H_INCLUDED +#define JSON_FORWARDS_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "config.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + +// writer.h +class FastWriter; +class StyledWriter; + +// reader.h +class Reader; + +// features.h +class Features; + +// value.h +typedef unsigned int ArrayIndex; +class StaticString; +class Path; +class PathArgument; +class Value; +class ValueIteratorBase; +class ValueIterator; +class ValueConstIterator; + +} // namespace Json + +#endif // JSON_FORWARDS_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/features.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_FEATURES_H_INCLUDED +#define CPPTL_JSON_FEATURES_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "forwards.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + +/** \brief Configuration passed to reader and writer. + * This configuration object can be used to force the Reader or Writer + * to behave in a standard conforming way. + */ +class JSON_API Features { +public: + /** \brief A configuration that allows all features and assumes all strings + * are UTF-8. + * - C & C++ comments are allowed + * - Root object can be any JSON value + * - Assumes Value strings are encoded in UTF-8 + */ + static Features all(); + + /** \brief A configuration that is strictly compatible with the JSON + * specification. + * - Comments are forbidden. + * - Root object must be either an array or an object value. + * - Assumes Value strings are encoded in UTF-8 + */ + static Features strictMode(); + + /** \brief Initialize the configuration like JsonConfig::allFeatures; + */ + Features(); + + /// \c true if comments are allowed. Default: \c true. + bool allowComments_; + + /// \c true if root must be either an array or an object value. Default: \c + /// false. + bool strictRoot_; + + /// \c true if dropped null placeholders are allowed. Default: \c false. + bool allowDroppedNullPlaceholders_; + + /// \c true if numeric object key are allowed. Default: \c false. + bool allowNumericKeys_; +}; + +} // namespace Json + +#endif // CPPTL_JSON_FEATURES_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/features.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/value.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_H_INCLUDED +#define CPPTL_JSON_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "forwards.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include + +#ifndef JSON_USE_CPPTL_SMALLMAP +#include +#else +#include +#endif +#ifdef JSON_USE_CPPTL +#include +#endif + +// Disable warning C4251: : needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +/** \brief JSON (JavaScript Object Notation). + */ +namespace Json { + +/** Base class for all exceptions we throw. + * + * We use nothing but these internally. Of course, STL can throw others. + */ +class JSON_API Exception : public std::exception { +public: + Exception(std::string const& msg); + virtual ~Exception() throw(); + virtual char const* what() const throw(); +protected: + std::string const msg_; +}; + +/** Exceptions which the user cannot easily avoid. + * + * E.g. out-of-memory (when we use malloc), stack-overflow, malicious input + * + * \remark derived from Json::Exception + */ +class JSON_API RuntimeError : public Exception { +public: + RuntimeError(std::string const& msg); +}; + +/** Exceptions thrown by JSON_ASSERT/JSON_FAIL macros. + * + * These are precondition-violations (user bugs) and internal errors (our bugs). + * + * \remark derived from Json::Exception + */ +class JSON_API LogicError : public Exception { +public: + LogicError(std::string const& msg); +}; + +/// used internally +void throwRuntimeError(std::string const& msg); +/// used internally +void throwLogicError(std::string const& msg); + +/** \brief Type of the value held by a Value object. + */ +enum ValueType { + nullValue = 0, ///< 'null' value + intValue, ///< signed integer value + uintValue, ///< unsigned integer value + realValue, ///< double value + stringValue, ///< UTF-8 string value + booleanValue, ///< bool value + arrayValue, ///< array value (ordered list) + objectValue ///< object value (collection of name/value pairs). +}; + +enum CommentPlacement { + commentBefore = 0, ///< a comment placed on the line before a value + commentAfterOnSameLine, ///< a comment just after a value on the same line + commentAfter, ///< a comment on the line after a value (only make sense for + /// root value) + numberOfCommentPlacement +}; + +//# ifdef JSON_USE_CPPTL +// typedef CppTL::AnyEnumerator EnumMemberNames; +// typedef CppTL::AnyEnumerator EnumValues; +//# endif + +/** \brief Lightweight wrapper to tag static string. + * + * Value constructor and objectValue member assignement takes advantage of the + * StaticString and avoid the cost of string duplication when storing the + * string or the member name. + * + * Example of usage: + * \code + * Json::Value aValue( StaticString("some text") ); + * Json::Value object; + * static const StaticString code("code"); + * object[code] = 1234; + * \endcode + */ +class JSON_API StaticString { +public: + explicit StaticString(const char* czstring) : c_str_(czstring) {} + + operator const char*() const { return c_str_; } + + const char* c_str() const { return c_str_; } + +private: + const char* c_str_; +}; + +/** \brief Represents a JSON value. + * + * This class is a discriminated union wrapper that can represents a: + * - signed integer [range: Value::minInt - Value::maxInt] + * - unsigned integer (range: 0 - Value::maxUInt) + * - double + * - UTF-8 string + * - boolean + * - 'null' + * - an ordered list of Value + * - collection of name/value pairs (javascript object) + * + * The type of the held value is represented by a #ValueType and + * can be obtained using type(). + * + * Values of an #objectValue or #arrayValue can be accessed using operator[]() + * methods. + * Non-const methods will automatically create the a #nullValue element + * if it does not exist. + * The sequence of an #arrayValue will be automatically resized and initialized + * with #nullValue. resize() can be used to enlarge or truncate an #arrayValue. + * + * The get() methods can be used to obtain default value in the case the + * required element does not exist. + * + * It is possible to iterate over the list of a #objectValue values using + * the getMemberNames() method. + * + * \note #Value string-length fit in size_t, but keys must be < 2^30. + * (The reason is an implementation detail.) A #CharReader will raise an + * exception if a bound is exceeded to avoid security holes in your app, + * but the Value API does *not* check bounds. That is the responsibility + * of the caller. + */ +class JSON_API Value { + friend class ValueIteratorBase; +public: + typedef std::vector Members; + typedef ValueIterator iterator; + typedef ValueConstIterator const_iterator; + typedef Json::UInt UInt; + typedef Json::Int Int; +#if defined(JSON_HAS_INT64) + typedef Json::UInt64 UInt64; + typedef Json::Int64 Int64; +#endif // defined(JSON_HAS_INT64) + typedef Json::LargestInt LargestInt; + typedef Json::LargestUInt LargestUInt; + typedef Json::ArrayIndex ArrayIndex; + + static const Value& null; ///< We regret this reference to a global instance; prefer the simpler Value(). + static const Value& nullRef; ///< just a kludge for binary-compatibility; same as null + /// Minimum signed integer value that can be stored in a Json::Value. + static const LargestInt minLargestInt; + /// Maximum signed integer value that can be stored in a Json::Value. + static const LargestInt maxLargestInt; + /// Maximum unsigned integer value that can be stored in a Json::Value. + static const LargestUInt maxLargestUInt; + + /// Minimum signed int value that can be stored in a Json::Value. + static const Int minInt; + /// Maximum signed int value that can be stored in a Json::Value. + static const Int maxInt; + /// Maximum unsigned int value that can be stored in a Json::Value. + static const UInt maxUInt; + +#if defined(JSON_HAS_INT64) + /// Minimum signed 64 bits int value that can be stored in a Json::Value. + static const Int64 minInt64; + /// Maximum signed 64 bits int value that can be stored in a Json::Value. + static const Int64 maxInt64; + /// Maximum unsigned 64 bits int value that can be stored in a Json::Value. + static const UInt64 maxUInt64; +#endif // defined(JSON_HAS_INT64) + +private: +#ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + class CZString { + public: + enum DuplicationPolicy { + noDuplication = 0, + duplicate, + duplicateOnCopy + }; + CZString(ArrayIndex index); + CZString(char const* str, unsigned length, DuplicationPolicy allocate); + CZString(CZString const& other); + ~CZString(); + CZString& operator=(CZString other); + bool operator<(CZString const& other) const; + bool operator==(CZString const& other) const; + ArrayIndex index() const; + //const char* c_str() const; ///< \deprecated + char const* data() const; + unsigned length() const; + bool isStaticString() const; + + private: + void swap(CZString& other); + + struct StringStorage { + unsigned policy_: 2; + unsigned length_: 30; // 1GB max + }; + + char const* cstr_; // actually, a prefixed string, unless policy is noDup + union { + ArrayIndex index_; + StringStorage storage_; + }; + }; + +public: +#ifndef JSON_USE_CPPTL_SMALLMAP + typedef std::map ObjectValues; +#else + typedef CppTL::SmallMap ObjectValues; +#endif // ifndef JSON_USE_CPPTL_SMALLMAP +#endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + +public: + /** \brief Create a default Value of the given type. + + This is a very useful constructor. + To create an empty array, pass arrayValue. + To create an empty object, pass objectValue. + Another Value can then be set to this one by assignment. +This is useful since clear() and resize() will not alter types. + + Examples: +\code +Json::Value null_value; // null +Json::Value arr_value(Json::arrayValue); // [] +Json::Value obj_value(Json::objectValue); // {} +\endcode + */ + Value(ValueType type = nullValue); + Value(Int value); + Value(UInt value); +#if defined(JSON_HAS_INT64) + Value(Int64 value); + Value(UInt64 value); +#endif // if defined(JSON_HAS_INT64) + Value(double value); + Value(const char* value); ///< Copy til first 0. (NULL causes to seg-fault.) + Value(const char* begin, const char* end); ///< Copy all, incl zeroes. + /** \brief Constructs a value from a static string. + + * Like other value string constructor but do not duplicate the string for + * internal storage. The given string must remain alive after the call to this + * constructor. + * \note This works only for null-terminated strings. (We cannot change the + * size of this class, so we have nowhere to store the length, + * which might be computed later for various operations.) + * + * Example of usage: + * \code + * static StaticString foo("some text"); + * Json::Value aValue(foo); + * \endcode + */ + Value(const StaticString& value); + Value(const std::string& value); ///< Copy data() til size(). Embedded zeroes too. +#ifdef JSON_USE_CPPTL + Value(const CppTL::ConstString& value); +#endif + Value(bool value); + /// Deep copy. + Value(const Value& other); + ~Value(); + + /// Deep copy, then swap(other). + /// \note Over-write existing comments. To preserve comments, use #swapPayload(). + Value& operator=(Value other); + /// Swap everything. + void swap(Value& other); + /// Swap values but leave comments and source offsets in place. + void swapPayload(Value& other); + + ValueType type() const; + + /// Compare payload only, not comments etc. + bool operator<(const Value& other) const; + bool operator<=(const Value& other) const; + bool operator>=(const Value& other) const; + bool operator>(const Value& other) const; + bool operator==(const Value& other) const; + bool operator!=(const Value& other) const; + int compare(const Value& other) const; + + const char* asCString() const; ///< Embedded zeroes could cause you trouble! + std::string asString() const; ///< Embedded zeroes are possible. + /** Get raw char* of string-value. + * \return false if !string. (Seg-fault if str or end are NULL.) + */ + bool getString( + char const** begin, char const** end) const; +#ifdef JSON_USE_CPPTL + CppTL::ConstString asConstString() const; +#endif + Int asInt() const; + UInt asUInt() const; +#if defined(JSON_HAS_INT64) + Int64 asInt64() const; + UInt64 asUInt64() const; +#endif // if defined(JSON_HAS_INT64) + LargestInt asLargestInt() const; + LargestUInt asLargestUInt() const; + float asFloat() const; + double asDouble() const; + bool asBool() const; + + bool isNull() const; + bool isBool() const; + bool isInt() const; + bool isInt64() const; + bool isUInt() const; + bool isUInt64() const; + bool isIntegral() const; + bool isDouble() const; + bool isNumeric() const; + bool isString() const; + bool isArray() const; + bool isObject() const; + + bool isConvertibleTo(ValueType other) const; + + /// Number of values in array or object + ArrayIndex size() const; + + /// \brief Return true if empty array, empty object, or null; + /// otherwise, false. + bool empty() const; + + /// Return isNull() + bool operator!() const; + + /// Remove all object members and array elements. + /// \pre type() is arrayValue, objectValue, or nullValue + /// \post type() is unchanged + void clear(); + + /// Resize the array to size elements. + /// New elements are initialized to null. + /// May only be called on nullValue or arrayValue. + /// \pre type() is arrayValue or nullValue + /// \post type() is arrayValue + void resize(ArrayIndex size); + + /// Access an array element (zero based index ). + /// If the array contains less than index element, then null value are + /// inserted + /// in the array so that its size is index+1. + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + Value& operator[](ArrayIndex index); + + /// Access an array element (zero based index ). + /// If the array contains less than index element, then null value are + /// inserted + /// in the array so that its size is index+1. + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + Value& operator[](int index); + + /// Access an array element (zero based index ) + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + const Value& operator[](ArrayIndex index) const; + + /// Access an array element (zero based index ) + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + const Value& operator[](int index) const; + + /// If the array contains at least index+1 elements, returns the element + /// value, + /// otherwise returns defaultValue. + Value get(ArrayIndex index, const Value& defaultValue) const; + /// Return true if index < size(). + bool isValidIndex(ArrayIndex index) const; + /// \brief Append value to array at the end. + /// + /// Equivalent to jsonvalue[jsonvalue.size()] = value; + Value& append(const Value& value); + + /// Access an object value by name, create a null member if it does not exist. + /// \note Because of our implementation, keys are limited to 2^30 -1 chars. + /// Exceeding that will cause an exception. + Value& operator[](const char* key); + /// Access an object value by name, returns null if there is no member with + /// that name. + const Value& operator[](const char* key) const; + /// Access an object value by name, create a null member if it does not exist. + /// \param key may contain embedded nulls. + Value& operator[](const std::string& key); + /// Access an object value by name, returns null if there is no member with + /// that name. + /// \param key may contain embedded nulls. + const Value& operator[](const std::string& key) const; + /** \brief Access an object value by name, create a null member if it does not + exist. + + * If the object has no entry for that name, then the member name used to store + * the new entry is not duplicated. + * Example of use: + * \code + * Json::Value object; + * static const StaticString code("code"); + * object[code] = 1234; + * \endcode + */ + Value& operator[](const StaticString& key); +#ifdef JSON_USE_CPPTL + /// Access an object value by name, create a null member if it does not exist. + Value& operator[](const CppTL::ConstString& key); + /// Access an object value by name, returns null if there is no member with + /// that name. + const Value& operator[](const CppTL::ConstString& key) const; +#endif + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + Value get(const char* key, const Value& defaultValue) const; + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + /// \note key may contain embedded nulls. + Value get(const char* begin, const char* end, const Value& defaultValue) const; + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + /// \param key may contain embedded nulls. + Value get(const std::string& key, const Value& defaultValue) const; +#ifdef JSON_USE_CPPTL + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + Value get(const CppTL::ConstString& key, const Value& defaultValue) const; +#endif + /// Most general and efficient version of isMember()const, get()const, + /// and operator[]const + /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 + Value const* find(char const* begin, char const* end) const; + /// Most general and efficient version of object-mutators. + /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 + /// \return non-zero, but JSON_ASSERT if this is neither object nor nullValue. + Value const* demand(char const* begin, char const* end); + /// \brief Remove and return the named member. + /// + /// Do nothing if it did not exist. + /// \return the removed Value, or null. + /// \pre type() is objectValue or nullValue + /// \post type() is unchanged + /// \deprecated + Value removeMember(const char* key); + /// Same as removeMember(const char*) + /// \param key may contain embedded nulls. + /// \deprecated + Value removeMember(const std::string& key); + /// Same as removeMember(const char* begin, const char* end, Value* removed), + /// but 'key' is null-terminated. + bool removeMember(const char* key, Value* removed); + /** \brief Remove the named map member. + + Update 'removed' iff removed. + \param key may contain embedded nulls. + \return true iff removed (no exceptions) + */ + bool removeMember(std::string const& key, Value* removed); + /// Same as removeMember(std::string const& key, Value* removed) + bool removeMember(const char* begin, const char* end, Value* removed); + /** \brief Remove the indexed array element. + + O(n) expensive operations. + Update 'removed' iff removed. + \return true iff removed (no exceptions) + */ + bool removeIndex(ArrayIndex i, Value* removed); + + /// Return true if the object has a member named key. + /// \note 'key' must be null-terminated. + bool isMember(const char* key) const; + /// Return true if the object has a member named key. + /// \param key may contain embedded nulls. + bool isMember(const std::string& key) const; + /// Same as isMember(std::string const& key)const + bool isMember(const char* begin, const char* end) const; +#ifdef JSON_USE_CPPTL + /// Return true if the object has a member named key. + bool isMember(const CppTL::ConstString& key) const; +#endif + + /// \brief Return a list of the member names. + /// + /// If null, return an empty list. + /// \pre type() is objectValue or nullValue + /// \post if type() was nullValue, it remains nullValue + Members getMemberNames() const; + + //# ifdef JSON_USE_CPPTL + // EnumMemberNames enumMemberNames() const; + // EnumValues enumValues() const; + //# endif + + /// \deprecated Always pass len. + JSONCPP_DEPRECATED("Use setComment(std::string const&) instead.") + void setComment(const char* comment, CommentPlacement placement); + /// Comments must be //... or /* ... */ + void setComment(const char* comment, size_t len, CommentPlacement placement); + /// Comments must be //... or /* ... */ + void setComment(const std::string& comment, CommentPlacement placement); + bool hasComment(CommentPlacement placement) const; + /// Include delimiters and embedded newlines. + std::string getComment(CommentPlacement placement) const; + + std::string toStyledString() const; + + const_iterator begin() const; + const_iterator end() const; + + iterator begin(); + iterator end(); + + // Accessors for the [start, limit) range of bytes within the JSON text from + // which this value was parsed, if any. + void setOffsetStart(size_t start); + void setOffsetLimit(size_t limit); + size_t getOffsetStart() const; + size_t getOffsetLimit() const; + +private: + void initBasic(ValueType type, bool allocated = false); + + Value& resolveReference(const char* key); + Value& resolveReference(const char* key, const char* end); + + struct CommentInfo { + CommentInfo(); + ~CommentInfo(); + + void setComment(const char* text, size_t len); + + char* comment_; + }; + + // struct MemberNamesTransform + //{ + // typedef const char *result_type; + // const char *operator()( const CZString &name ) const + // { + // return name.c_str(); + // } + //}; + + union ValueHolder { + LargestInt int_; + LargestUInt uint_; + double real_; + bool bool_; + char* string_; // actually ptr to unsigned, followed by str, unless !allocated_ + ObjectValues* map_; + } value_; + ValueType type_ : 8; + unsigned int allocated_ : 1; // Notes: if declared as bool, bitfield is useless. + // If not allocated_, string_ must be null-terminated. + CommentInfo* comments_; + + // [start, limit) byte offsets in the source JSON text from which this Value + // was extracted. + size_t start_; + size_t limit_; +}; + +/** \brief Experimental and untested: represents an element of the "path" to + * access a node. + */ +class JSON_API PathArgument { +public: + friend class Path; + + PathArgument(); + PathArgument(ArrayIndex index); + PathArgument(const char* key); + PathArgument(const std::string& key); + +private: + enum Kind { + kindNone = 0, + kindIndex, + kindKey + }; + std::string key_; + ArrayIndex index_; + Kind kind_; +}; + +/** \brief Experimental and untested: represents a "path" to access a node. + * + * Syntax: + * - "." => root node + * - ".[n]" => elements at index 'n' of root node (an array value) + * - ".name" => member named 'name' of root node (an object value) + * - ".name1.name2.name3" + * - ".[0][1][2].name1[3]" + * - ".%" => member name is provided as parameter + * - ".[%]" => index is provied as parameter + */ +class JSON_API Path { +public: + Path(const std::string& path, + const PathArgument& a1 = PathArgument(), + const PathArgument& a2 = PathArgument(), + const PathArgument& a3 = PathArgument(), + const PathArgument& a4 = PathArgument(), + const PathArgument& a5 = PathArgument()); + + const Value& resolve(const Value& root) const; + Value resolve(const Value& root, const Value& defaultValue) const; + /// Creates the "path" to access the specified node and returns a reference on + /// the node. + Value& make(Value& root) const; + +private: + typedef std::vector InArgs; + typedef std::vector Args; + + void makePath(const std::string& path, const InArgs& in); + void addPathInArg(const std::string& path, + const InArgs& in, + InArgs::const_iterator& itInArg, + PathArgument::Kind kind); + void invalidPath(const std::string& path, int location); + + Args args_; +}; + +/** \brief base class for Value iterators. + * + */ +class JSON_API ValueIteratorBase { +public: + typedef std::bidirectional_iterator_tag iterator_category; + typedef unsigned int size_t; + typedef int difference_type; + typedef ValueIteratorBase SelfType; + + bool operator==(const SelfType& other) const { return isEqual(other); } + + bool operator!=(const SelfType& other) const { return !isEqual(other); } + + difference_type operator-(const SelfType& other) const { + return other.computeDistance(*this); + } + + /// Return either the index or the member name of the referenced value as a + /// Value. + Value key() const; + + /// Return the index of the referenced Value, or -1 if it is not an arrayValue. + UInt index() const; + + /// Return the member name of the referenced Value, or "" if it is not an + /// objectValue. + /// \note Avoid `c_str()` on result, as embedded zeroes are possible. + std::string name() const; + + /// Return the member name of the referenced Value. "" if it is not an + /// objectValue. + /// \deprecated This cannot be used for UTF-8 strings, since there can be embedded nulls. + JSONCPP_DEPRECATED("Use `key = name();` instead.") + char const* memberName() const; + /// Return the member name of the referenced Value, or NULL if it is not an + /// objectValue. + /// \note Better version than memberName(). Allows embedded nulls. + char const* memberName(char const** end) const; + +protected: + Value& deref() const; + + void increment(); + + void decrement(); + + difference_type computeDistance(const SelfType& other) const; + + bool isEqual(const SelfType& other) const; + + void copy(const SelfType& other); + +private: + Value::ObjectValues::iterator current_; + // Indicates that iterator is for a null value. + bool isNull_; + +public: + // For some reason, BORLAND needs these at the end, rather + // than earlier. No idea why. + ValueIteratorBase(); + explicit ValueIteratorBase(const Value::ObjectValues::iterator& current); +}; + +/** \brief const iterator for object and array value. + * + */ +class JSON_API ValueConstIterator : public ValueIteratorBase { + friend class Value; + +public: + typedef const Value value_type; + //typedef unsigned int size_t; + //typedef int difference_type; + typedef const Value& reference; + typedef const Value* pointer; + typedef ValueConstIterator SelfType; + + ValueConstIterator(); + +private: +/*! \internal Use by Value to create an iterator. + */ + explicit ValueConstIterator(const Value::ObjectValues::iterator& current); +public: + SelfType& operator=(const ValueIteratorBase& other); + + SelfType operator++(int) { + SelfType temp(*this); + ++*this; + return temp; + } + + SelfType operator--(int) { + SelfType temp(*this); + --*this; + return temp; + } + + SelfType& operator--() { + decrement(); + return *this; + } + + SelfType& operator++() { + increment(); + return *this; + } + + reference operator*() const { return deref(); } + + pointer operator->() const { return &deref(); } +}; + +/** \brief Iterator for object and array value. + */ +class JSON_API ValueIterator : public ValueIteratorBase { + friend class Value; + +public: + typedef Value value_type; + typedef unsigned int size_t; + typedef int difference_type; + typedef Value& reference; + typedef Value* pointer; + typedef ValueIterator SelfType; + + ValueIterator(); + ValueIterator(const ValueConstIterator& other); + ValueIterator(const ValueIterator& other); + +private: +/*! \internal Use by Value to create an iterator. + */ + explicit ValueIterator(const Value::ObjectValues::iterator& current); +public: + SelfType& operator=(const SelfType& other); + + SelfType operator++(int) { + SelfType temp(*this); + ++*this; + return temp; + } + + SelfType operator--(int) { + SelfType temp(*this); + --*this; + return temp; + } + + SelfType& operator--() { + decrement(); + return *this; + } + + SelfType& operator++() { + increment(); + return *this; + } + + reference operator*() const { return deref(); } + + pointer operator->() const { return &deref(); } +}; + +} // namespace Json + + +namespace std { +/// Specialize std::swap() for Json::Value. +template<> +inline void swap(Json::Value& a, Json::Value& b) { a.swap(b); } +} + + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // CPPTL_JSON_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/value.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/reader.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_READER_H_INCLUDED +#define CPPTL_JSON_READER_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "features.h" +#include "value.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include + +// Disable warning C4251: : needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +namespace Json { + +/** \brief Unserialize a JSON document into a + *Value. + * + * \deprecated Use CharReader and CharReaderBuilder. + */ +class JSON_API Reader { +public: + typedef char Char; + typedef const Char* Location; + + /** \brief An error tagged with where in the JSON text it was encountered. + * + * The offsets give the [start, limit) range of bytes within the text. Note + * that this is bytes, not codepoints. + * + */ + struct StructuredError { + size_t offset_start; + size_t offset_limit; + std::string message; + }; + + /** \brief Constructs a Reader allowing all features + * for parsing. + */ + Reader(); + + /** \brief Constructs a Reader allowing the specified feature set + * for parsing. + */ + Reader(const Features& features); + + /** \brief Read a Value from a JSON + * document. + * \param document UTF-8 encoded string containing the document to read. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param collectComments \c true to collect comment and allow writing them + * back during + * serialization, \c false to discard comments. + * This parameter is ignored if + * Features::allowComments_ + * is \c false. + * \return \c true if the document was successfully parsed, \c false if an + * error occurred. + */ + bool + parse(const std::string& document, Value& root, bool collectComments = true); + + /** \brief Read a Value from a JSON + document. + * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the + document to read. + * \param endDoc Pointer on the end of the UTF-8 encoded string of the + document to read. + * Must be >= beginDoc. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param collectComments \c true to collect comment and allow writing them + back during + * serialization, \c false to discard comments. + * This parameter is ignored if + Features::allowComments_ + * is \c false. + * \return \c true if the document was successfully parsed, \c false if an + error occurred. + */ + bool parse(const char* beginDoc, + const char* endDoc, + Value& root, + bool collectComments = true); + + /// \brief Parse from input stream. + /// \see Json::operator>>(std::istream&, Json::Value&). + bool parse(std::istream& is, Value& root, bool collectComments = true); + + /** \brief Returns a user friendly string that list errors in the parsed + * document. + * \return Formatted error message with the list of errors with their location + * in + * the parsed document. An empty string is returned if no error + * occurred + * during parsing. + * \deprecated Use getFormattedErrorMessages() instead (typo fix). + */ + JSONCPP_DEPRECATED("Use getFormattedErrorMessages() instead.") + std::string getFormatedErrorMessages() const; + + /** \brief Returns a user friendly string that list errors in the parsed + * document. + * \return Formatted error message with the list of errors with their location + * in + * the parsed document. An empty string is returned if no error + * occurred + * during parsing. + */ + std::string getFormattedErrorMessages() const; + + /** \brief Returns a vector of structured erros encounted while parsing. + * \return A (possibly empty) vector of StructuredError objects. Currently + * only one error can be returned, but the caller should tolerate + * multiple + * errors. This can occur if the parser recovers from a non-fatal + * parse error and then encounters additional errors. + */ + std::vector getStructuredErrors() const; + + /** \brief Add a semantic error message. + * \param value JSON Value location associated with the error + * \param message The error message. + * \return \c true if the error was successfully added, \c false if the + * Value offset exceeds the document size. + */ + bool pushError(const Value& value, const std::string& message); + + /** \brief Add a semantic error message with extra context. + * \param value JSON Value location associated with the error + * \param message The error message. + * \param extra Additional JSON Value location to contextualize the error + * \return \c true if the error was successfully added, \c false if either + * Value offset exceeds the document size. + */ + bool pushError(const Value& value, const std::string& message, const Value& extra); + + /** \brief Return whether there are any errors. + * \return \c true if there are no errors to report \c false if + * errors have occurred. + */ + bool good() const; + +private: + enum TokenType { + tokenEndOfStream = 0, + tokenObjectBegin, + tokenObjectEnd, + tokenArrayBegin, + tokenArrayEnd, + tokenString, + tokenNumber, + tokenTrue, + tokenFalse, + tokenNull, + tokenArraySeparator, + tokenMemberSeparator, + tokenComment, + tokenError + }; + + class Token { + public: + TokenType type_; + Location start_; + Location end_; + }; + + class ErrorInfo { + public: + Token token_; + std::string message_; + Location extra_; + }; + + typedef std::deque Errors; + + bool readToken(Token& token); + void skipSpaces(); + bool match(Location pattern, int patternLength); + bool readComment(); + bool readCStyleComment(); + bool readCppStyleComment(); + bool readString(); + void readNumber(); + bool readValue(); + bool readObject(Token& token); + bool readArray(Token& token); + bool decodeNumber(Token& token); + bool decodeNumber(Token& token, Value& decoded); + bool decodeString(Token& token); + bool decodeString(Token& token, std::string& decoded); + bool decodeDouble(Token& token); + bool decodeDouble(Token& token, Value& decoded); + bool decodeUnicodeCodePoint(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool addError(const std::string& message, Token& token, Location extra = 0); + bool recoverFromError(TokenType skipUntilToken); + bool addErrorAndRecover(const std::string& message, + Token& token, + TokenType skipUntilToken); + void skipUntilSpace(); + Value& currentValue(); + Char getNextChar(); + void + getLocationLineAndColumn(Location location, int& line, int& column) const; + std::string getLocationLineAndColumn(Location location) const; + void addComment(Location begin, Location end, CommentPlacement placement); + void skipCommentTokens(Token& token); + + typedef std::stack Nodes; + Nodes nodes_; + Errors errors_; + std::string document_; + Location begin_; + Location end_; + Location current_; + Location lastValueEnd_; + Value* lastValue_; + std::string commentsBefore_; + Features features_; + bool collectComments_; +}; // Reader + +/** Interface for reading JSON from a char array. + */ +class JSON_API CharReader { +public: + virtual ~CharReader() {} + /** \brief Read a Value from a JSON + document. + * The document must be a UTF-8 encoded string containing the document to read. + * + * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the + document to read. + * \param endDoc Pointer on the end of the UTF-8 encoded string of the + document to read. + * Must be >= beginDoc. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param errs [out] Formatted error messages (if not NULL) + * a user friendly string that lists errors in the parsed + * document. + * \return \c true if the document was successfully parsed, \c false if an + error occurred. + */ + virtual bool parse( + char const* beginDoc, char const* endDoc, + Value* root, std::string* errs) = 0; + + class Factory { + public: + virtual ~Factory() {} + /** \brief Allocate a CharReader via operator new(). + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + virtual CharReader* newCharReader() const = 0; + }; // Factory +}; // CharReader + +/** \brief Build a CharReader implementation. + +Usage: +\code + using namespace Json; + CharReaderBuilder builder; + builder["collectComments"] = false; + Value value; + std::string errs; + bool ok = parseFromStream(builder, std::cin, &value, &errs); +\endcode +*/ +class JSON_API CharReaderBuilder : public CharReader::Factory { +public: + // Note: We use a Json::Value so that we can add data-members to this class + // without a major version bump. + /** Configuration of this builder. + These are case-sensitive. + Available settings (case-sensitive): + - `"collectComments": false or true` + - true to collect comment and allow writing them + back during serialization, false to discard comments. + This parameter is ignored if allowComments is false. + - `"allowComments": false or true` + - true if comments are allowed. + - `"strictRoot": false or true` + - true if root must be either an array or an object value + - `"allowDroppedNullPlaceholders": false or true` + - true if dropped null placeholders are allowed. (See StreamWriterBuilder.) + - `"allowNumericKeys": false or true` + - true if numeric object keys are allowed. + - `"allowSingleQuotes": false or true` + - true if '' are allowed for strings (both keys and values) + - `"stackLimit": integer` + - Exceeding stackLimit (recursive depth of `readValue()`) will + cause an exception. + - This is a security issue (seg-faults caused by deeply nested JSON), + so the default is low. + - `"failIfExtra": false or true` + - If true, `parse()` returns false when extra non-whitespace trails + the JSON value in the input string. + - `"rejectDupKeys": false or true` + - If true, `parse()` returns false when a key is duplicated within an object. + + You can examine 'settings_` yourself + to see the defaults. You can also write and read them just like any + JSON Value. + \sa setDefaults() + */ + Json::Value settings_; + + CharReaderBuilder(); + virtual ~CharReaderBuilder(); + + virtual CharReader* newCharReader() const; + + /** \return true if 'settings' are legal and consistent; + * otherwise, indicate bad settings via 'invalid'. + */ + bool validate(Json::Value* invalid) const; + + /** A simple way to update a specific setting. + */ + Value& operator[](std::string key); + + /** Called by ctor, but you can use this to reset settings_. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderDefaults + */ + static void setDefaults(Json::Value* settings); + /** Same as old Features::strictMode(). + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderStrictMode + */ + static void strictMode(Json::Value* settings); +}; + +/** Consume entire stream and use its begin/end. + * Someday we might have a real StreamReader, but for now this + * is convenient. + */ +bool JSON_API parseFromStream( + CharReader::Factory const&, + std::istream&, + Value* root, std::string* errs); + +/** \brief Read from 'sin' into 'root'. + + Always keep comments from the input JSON. + + This can be used to read a file into a particular sub-object. + For example: + \code + Json::Value root; + cin >> root["dir"]["file"]; + cout << root; + \endcode + Result: + \verbatim + { + "dir": { + "file": { + // The input stream JSON would be nested here. + } + } + } + \endverbatim + \throw std::exception on parse error. + \see Json::operator<<() +*/ +JSON_API std::istream& operator>>(std::istream&, Value&); + +} // namespace Json + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // CPPTL_JSON_READER_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/reader.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/writer.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_WRITER_H_INCLUDED +#define JSON_WRITER_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "value.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include + +// Disable warning C4251: : needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +namespace Json { + +class Value; + +/** + +Usage: +\code + using namespace Json; + void writeToStdout(StreamWriter::Factory const& factory, Value const& value) { + std::unique_ptr const writer( + factory.newStreamWriter()); + writer->write(value, &std::cout); + std::cout << std::endl; // add lf and flush + } +\endcode +*/ +class JSON_API StreamWriter { +protected: + std::ostream* sout_; // not owned; will not delete +public: + StreamWriter(); + virtual ~StreamWriter(); + /** Write Value into document as configured in sub-class. + Do not take ownership of sout, but maintain a reference during function. + \pre sout != NULL + \return zero on success (For now, we always return zero, so check the stream instead.) + \throw std::exception possibly, depending on configuration + */ + virtual int write(Value const& root, std::ostream* sout) = 0; + + /** \brief A simple abstract factory. + */ + class JSON_API Factory { + public: + virtual ~Factory(); + /** \brief Allocate a CharReader via operator new(). + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + virtual StreamWriter* newStreamWriter() const = 0; + }; // Factory +}; // StreamWriter + +/** \brief Write into stringstream, then return string, for convenience. + * A StreamWriter will be created from the factory, used, and then deleted. + */ +std::string JSON_API writeString(StreamWriter::Factory const& factory, Value const& root); + + +/** \brief Build a StreamWriter implementation. + +Usage: +\code + using namespace Json; + Value value = ...; + StreamWriterBuilder builder; + builder["commentStyle"] = "None"; + builder["indentation"] = " "; // or whatever you like + std::unique_ptr writer( + builder.newStreamWriter()); + writer->write(value, &std::cout); + std::cout << std::endl; // add lf and flush +\endcode +*/ +class JSON_API StreamWriterBuilder : public StreamWriter::Factory { +public: + // Note: We use a Json::Value so that we can add data-members to this class + // without a major version bump. + /** Configuration of this builder. + Available settings (case-sensitive): + - "commentStyle": "None" or "All" + - "indentation": "" + - "enableYAMLCompatibility": false or true + - slightly change the whitespace around colons + - "dropNullPlaceholders": false or true + - Drop the "null" string from the writer's output for nullValues. + Strictly speaking, this is not valid JSON. But when the output is being + fed to a browser's Javascript, it makes for smaller output and the + browser can handle the output just fine. + + You can examine 'settings_` yourself + to see the defaults. You can also write and read them just like any + JSON Value. + \sa setDefaults() + */ + Json::Value settings_; + + StreamWriterBuilder(); + virtual ~StreamWriterBuilder(); + + /** + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + virtual StreamWriter* newStreamWriter() const; + + /** \return true if 'settings' are legal and consistent; + * otherwise, indicate bad settings via 'invalid'. + */ + bool validate(Json::Value* invalid) const; + /** A simple way to update a specific setting. + */ + Value& operator[](std::string key); + + /** Called by ctor, but you can use this to reset settings_. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_writer.cpp StreamWriterBuilderDefaults + */ + static void setDefaults(Json::Value* settings); +}; + +/** \brief Abstract class for writers. + * \deprecated Use StreamWriter. (And really, this is an implementation detail.) + */ +class JSON_API Writer { +public: + virtual ~Writer(); + + virtual std::string write(const Value& root) = 0; +}; + +/** \brief Outputs a Value in JSON format + *without formatting (not human friendly). + * + * The JSON document is written in a single line. It is not intended for 'human' + *consumption, + * but may be usefull to support feature such as RPC where bandwith is limited. + * \sa Reader, Value + * \deprecated Use StreamWriterBuilder. + */ +class JSON_API FastWriter : public Writer { + +public: + FastWriter(); + virtual ~FastWriter() {} + + void enableYAMLCompatibility(); + + /** \brief Drop the "null" string from the writer's output for nullValues. + * Strictly speaking, this is not valid JSON. But when the output is being + * fed to a browser's Javascript, it makes for smaller output and the + * browser can handle the output just fine. + */ + void dropNullPlaceholders(); + + void omitEndingLineFeed(); + +public: // overridden from Writer + virtual std::string write(const Value& root); + +private: + void writeValue(const Value& value); + + std::string document_; + bool yamlCompatiblityEnabled_; + bool dropNullPlaceholders_; + bool omitEndingLineFeed_; +}; + +/** \brief Writes a Value in JSON format in a + *human friendly way. + * + * The rules for line break and indent are as follow: + * - Object value: + * - if empty then print {} without indent and line break + * - if not empty the print '{', line break & indent, print one value per + *line + * and then unindent and line break and print '}'. + * - Array value: + * - if empty then print [] without indent and line break + * - if the array contains no object value, empty array or some other value + *types, + * and all the values fit on one lines, then print the array on a single + *line. + * - otherwise, it the values do not fit on one line, or the array contains + * object or non empty array, then print one value per line. + * + * If the Value have comments then they are outputed according to their + *#CommentPlacement. + * + * \sa Reader, Value, Value::setComment() + * \deprecated Use StreamWriterBuilder. + */ +class JSON_API StyledWriter : public Writer { +public: + StyledWriter(); + virtual ~StyledWriter() {} + +public: // overridden from Writer + /** \brief Serialize a Value in JSON format. + * \param root Value to serialize. + * \return String containing the JSON document that represents the root value. + */ + virtual std::string write(const Value& root); + +private: + void writeValue(const Value& value); + void writeArrayValue(const Value& value); + bool isMultineArray(const Value& value); + void pushValue(const std::string& value); + void writeIndent(); + void writeWithIndent(const std::string& value); + void indent(); + void unindent(); + void writeCommentBeforeValue(const Value& root); + void writeCommentAfterValueOnSameLine(const Value& root); + bool hasCommentForValue(const Value& value); + static std::string normalizeEOL(const std::string& text); + + typedef std::vector ChildValues; + + ChildValues childValues_; + std::string document_; + std::string indentString_; + int rightMargin_; + int indentSize_; + bool addChildValues_; +}; + +/** \brief Writes a Value in JSON format in a + human friendly way, + to a stream rather than to a string. + * + * The rules for line break and indent are as follow: + * - Object value: + * - if empty then print {} without indent and line break + * - if not empty the print '{', line break & indent, print one value per + line + * and then unindent and line break and print '}'. + * - Array value: + * - if empty then print [] without indent and line break + * - if the array contains no object value, empty array or some other value + types, + * and all the values fit on one lines, then print the array on a single + line. + * - otherwise, it the values do not fit on one line, or the array contains + * object or non empty array, then print one value per line. + * + * If the Value have comments then they are outputed according to their + #CommentPlacement. + * + * \param indentation Each level will be indented by this amount extra. + * \sa Reader, Value, Value::setComment() + * \deprecated Use StreamWriterBuilder. + */ +class JSON_API StyledStreamWriter { +public: + StyledStreamWriter(std::string indentation = "\t"); + ~StyledStreamWriter() {} + +public: + /** \brief Serialize a Value in JSON format. + * \param out Stream to write to. (Can be ostringstream, e.g.) + * \param root Value to serialize. + * \note There is no point in deriving from Writer, since write() should not + * return a value. + */ + void write(std::ostream& out, const Value& root); + +private: + void writeValue(const Value& value); + void writeArrayValue(const Value& value); + bool isMultineArray(const Value& value); + void pushValue(const std::string& value); + void writeIndent(); + void writeWithIndent(const std::string& value); + void indent(); + void unindent(); + void writeCommentBeforeValue(const Value& root); + void writeCommentAfterValueOnSameLine(const Value& root); + bool hasCommentForValue(const Value& value); + static std::string normalizeEOL(const std::string& text); + + typedef std::vector ChildValues; + + ChildValues childValues_; + std::ostream* document_; + std::string indentString_; + int rightMargin_; + std::string indentation_; + bool addChildValues_ : 1; + bool indented_ : 1; +}; + +#if defined(JSON_HAS_INT64) +std::string JSON_API valueToString(Int value); +std::string JSON_API valueToString(UInt value); +#endif // if defined(JSON_HAS_INT64) +std::string JSON_API valueToString(LargestInt value); +std::string JSON_API valueToString(LargestUInt value); +std::string JSON_API valueToString(double value); +std::string JSON_API valueToString(bool value); +std::string JSON_API valueToQuotedString(const char* value); + +/// \brief Output using the StyledStreamWriter. +/// \see Json::operator>>() +JSON_API std::ostream& operator<<(std::ostream&, const Value& root); + +} // namespace Json + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // JSON_WRITER_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/writer.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/assertions.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_ASSERTIONS_H_INCLUDED +#define CPPTL_JSON_ASSERTIONS_H_INCLUDED + +#include +#include + +#if !defined(JSON_IS_AMALGAMATION) +#include "config.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +/** It should not be possible for a maliciously designed file to + * cause an abort() or seg-fault, so these macros are used only + * for pre-condition violations and internal logic errors. + */ +#if JSON_USE_EXCEPTION + +// @todo <= add detail about condition in exception +# define JSON_ASSERT(condition) \ + {if (!(condition)) {Json::throwLogicError( "assert json failed" );}} + +# define JSON_FAIL_MESSAGE(message) \ + { \ + std::ostringstream oss; oss << message; \ + Json::throwLogicError(oss.str()); \ + abort(); \ + } + +#else // JSON_USE_EXCEPTION + +# define JSON_ASSERT(condition) assert(condition) + +// The call to assert() will show the failure message in debug builds. In +// release builds we abort, for a core-dump or debugger. +# define JSON_FAIL_MESSAGE(message) \ + { \ + std::ostringstream oss; oss << message; \ + assert(false && oss.str().c_str()); \ + abort(); \ + } + + +#endif + +#define JSON_ASSERT_MESSAGE(condition, message) \ + if (!(condition)) { \ + JSON_FAIL_MESSAGE(message); \ + } + +#endif // CPPTL_JSON_ASSERTIONS_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/assertions.h +// ////////////////////////////////////////////////////////////////////// + + + + + +#endif //ifndef JSON_AMALGATED_H_INCLUDED diff --git a/depends/jsonxx b/depends/jsonxx deleted file mode 160000 index 9a0b18acb..000000000 --- a/depends/jsonxx +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9a0b18acb0967bfc523dca7af2cb34030d97c02b diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 16cf03d1a..270948e1a 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -310,7 +310,7 @@ IF(APPLE) SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0) ENDIF() -TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua jsonxx dfhack-version ${PROJECT_LIBS}) +TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua jsoncpp dfhack-version ${PROJECT_LIBS}) SET_TARGET_PROPERTIES(dfhack PROPERTIES LINK_INTERFACE_LIBRARIES "") TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 628c521de..fdc6e931c 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -113,7 +113,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(digFlood digFlood.cpp) add_subdirectory(diggingInvaders) DFHACK_PLUGIN(drybuckets drybuckets.cpp) - DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES jsonxx lua) + DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(embark-tools embark-tools.cpp) DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(fastdwarf fastdwarf.cpp) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index e70bb8399..bfbc1168a 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -44,8 +44,6 @@ #include "df/descriptor_shape.h" #include "df/descriptor_color.h" -#include "jsonxx.h" - using std::deque; DFHACK_PLUGIN("dwarfmonitor"); From 071f67f2aabb2aeba70d3f74ee578b0e70e25f54 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jul 2015 20:09:55 -0400 Subject: [PATCH 42/61] Add some JSON helper functions --- depends/jsoncpp/jsoncpp-ex.h | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 depends/jsoncpp/jsoncpp-ex.h diff --git a/depends/jsoncpp/jsoncpp-ex.h b/depends/jsoncpp/jsoncpp-ex.h new file mode 100644 index 000000000..fc1174133 --- /dev/null +++ b/depends/jsoncpp/jsoncpp-ex.h @@ -0,0 +1,31 @@ +#include "jsoncpp.h" +#pragma once + +namespace JsonEx { + + template bool is (const Json::Value &val) { return false; } + template T as (const Json::Value &val); + template T get (const Json::Value &val, const std::string &key, const T &default_) { return default_; } + +#define define_helpers(type, is_func, as_func) \ + template<> inline bool is (const Json::Value &val) { return val.is_func(); } \ + template<> inline type as (const Json::Value &val) { return val.as_func(); } \ + template<> inline type get (const Json::Value &val, const std::string &key, const type &default_) \ + { Json::Value x = val[key]; return is(x) ? as(x) : default_; } + define_helpers(bool, isBool, asBool); + define_helpers(Json::Int, isInt, asInt); + define_helpers(Json::UInt, isUInt, asUInt); + define_helpers(Json::Int64, isInt64, asInt64); + define_helpers(Json::UInt64, isUInt64, asUInt64); + define_helpers(float, isDouble, asFloat); + define_helpers(double, isDouble, asDouble); + define_helpers(std::string, isString, asString); +#undef define_helpers + + inline std::string toSimpleString (const Json::Value &val) + { + Json::FastWriter w; + return w.write(val); + } + +} From 01e04c24c593c8ebff31a4ce4a8f42067ad08722 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 31 Jul 2015 14:25:26 -0400 Subject: [PATCH 43/61] Use a separate lua state in dwarfmonitor render hook --- plugins/dwarfmonitor.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index e70bb8399..216664a93 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -44,8 +44,6 @@ #include "df/descriptor_shape.h" #include "df/descriptor_color.h" -#include "jsonxx.h" - using std::deque; DFHACK_PLUGIN("dwarfmonitor"); @@ -157,6 +155,7 @@ static void open_stats_srceen(); namespace dm_lua { static color_ostream_proxy *out; + static lua_State *state; typedef int(*initializer)(lua_State*); int no_args (lua_State *L) { return 0; } void cleanup() @@ -166,26 +165,26 @@ namespace dm_lua { delete out; out = NULL; } + lua_close(state); } - bool init_call (lua_State *L, const char *func) + bool init_call (const char *func) { if (!out) out = new color_ostream_proxy(Core::getInstance().getConsole()); - return Lua::PushModulePublic(*out, L, "plugins.dwarfmonitor", func); + return Lua::PushModulePublic(*out, state, "plugins.dwarfmonitor", func); } - bool safe_call (lua_State *L, int nargs) + bool safe_call (int nargs) { - return Lua::SafeCall(*out, L, nargs, 0); + return Lua::SafeCall(*out, state, nargs, 0); } bool call (const char *func, initializer init = no_args) { - auto L = Lua::Core::State; - Lua::StackUnwinder top(L); - if (!init_call(L, func)) + Lua::StackUnwinder top(state); + if (!init_call(func)) return false; - int nargs = init(L); - return safe_call(L, nargs); + int nargs = init(state); + return safe_call(nargs); } template @@ -1977,6 +1976,8 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector Date: Fri, 31 Jul 2015 16:46:00 -0400 Subject: [PATCH 44/61] Fix manipulator crash when selecting from empty custom profession list --- NEWS | 1 + plugins/manipulator.cpp | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/NEWS b/NEWS index 25a9870a5..19c3f9f09 100644 --- a/NEWS +++ b/NEWS @@ -34,6 +34,7 @@ DFHack Future buildingplan: Now supports hatch covers gui/hack-wish now properly assigns quality to items. gui/gm-editor handles lua tables properly + manipulator: fixed crash when selecting custom professions when none are found search: fixed crash in unit list after cancelling a job stockpiles: now checks/sanitizes filenames when saving stocks: fixed a crash when right-clicking diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 6a69d7996..d6d2cb222 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -998,6 +998,8 @@ public: } void select_profession(size_t selected) { + if (selected >= manager.templates.size()) + return; ProfessionTemplate prof = manager.templates[selected - 1]; for (auto it = units.begin(); it != units.end(); ++it) @@ -1013,6 +1015,11 @@ public: Screen::clear(); int x = 2, y = 2; Screen::drawBorder(" Dwarf Manipulator - Custom Profession "); + if (!manager.templates.size()) + { + OutputString(COLOR_LIGHTRED, x, y, "No saved professions"); + return; + } if (selection_empty) { OutputString(COLOR_LIGHTRED, x, y, "No dwarves selected!"); From d7b1714a6ce4a06873c0149861bc8c5eb5385a3b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 2 Aug 2015 10:07:57 -0400 Subject: [PATCH 45/61] gui/create-item: Improve quality assignment and make "Esc" work --- NEWS | 2 +- scripts/gui/create-item.lua | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 19c3f9f09..1fba80908 100644 --- a/NEWS +++ b/NEWS @@ -32,7 +32,7 @@ DFHack Future Circular lua dependencies (reqscript/script_environment) fixed Prevented crash in Items::createItem() buildingplan: Now supports hatch covers - gui/hack-wish now properly assigns quality to items. + gui/create-item now properly assigns quality to items. gui/gm-editor handles lua tables properly manipulator: fixed crash when selecting custom professions when none are found search: fixed crash in unit list after cancelling a job diff --git a/scripts/gui/create-item.lua b/scripts/gui/create-item.lua index 05cc2fc42..2339c28c2 100644 --- a/scripts/gui/create-item.lua +++ b/scripts/gui/create-item.lua @@ -3,6 +3,7 @@ -- author Putnam -- edited by expwnent +--@module = true local function getGenderString(gender) local genderStr @@ -112,9 +113,9 @@ end local function createItem(mat,itemType,quality,creator,description) local item=df.item.find(dfhack.items.createItem(itemType[1], itemType[2], mat[1], mat[2], creator)) - if pcall(function() print(item.quality) end) then - item.quality=quality-1 - end + assert(item, 'failed to create item') + quality = math.max(0, math.min(5, quality - 1)) + item:setQuality(quality) if df.item_type[itemType[1]]=='SLAB' then item.description=description end @@ -175,23 +176,29 @@ function hackWish(unit) local amountok, amount local matok,mattype,matindex,matFilter local itemok,itemtype,itemsubtype=showItemPrompt('What item do you want?',function(itype) return df.item_type[itype]~='CORPSE' and df.item_type[itype]~='FOOD' end ,true) + if not itemok then return end if not args.notRestrictive then matFilter=getMatFilter(itemtype) end if not usesCreature(itemtype) then matok,mattype,matindex=showMaterialPrompt('Wish','And what material should it be made of?',matFilter) + if not matok then return end else local creatureok,useless,creatureTable=script.showListPrompt('Wish','What creature should it be?',COLOR_LIGHTGREEN,getCreatureList()) + if not creatureok then return end mattype,matindex=getCreatureRaceAndCaste(creatureTable[3]) end local qualityok,quality=script.showListPrompt('Wish','What quality should it be?',COLOR_LIGHTGREEN,qualityTable()) + if not qualityok then return end local description if df.item_type[itemtype]=='SLAB' then local descriptionok descriptionok,description=script.showInputPrompt('Slab','What should the slab say?',COLOR_WHITE) + if not descriptionok then return end end if args.multi then repeat amountok,amount=script.showInputPrompt('Wish','How many do you want? (numbers only!)',COLOR_LIGHTGREEN) until tonumber(amount) + if not amountok then return end if mattype and itemtype then if df.item_type.attrs[itemtype].is_stackable then local proper_item=df.item.find(dfhack.items.createItem(itemtype, itemsubtype, mattype, matindex, unit)) From 2f1bd12312c98f2671675d9002fd4dd547dc8dea Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 2 Aug 2015 10:09:36 -0400 Subject: [PATCH 46/61] Update NEWS --- NEWS | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 1fba80908..f87d901dc 100644 --- a/NEWS +++ b/NEWS @@ -32,7 +32,9 @@ DFHack Future Circular lua dependencies (reqscript/script_environment) fixed Prevented crash in Items::createItem() buildingplan: Now supports hatch covers - gui/create-item now properly assigns quality to items. + gui/create-item: + - fixed assigning quality to items + - made "esc" work properly gui/gm-editor handles lua tables properly manipulator: fixed crash when selecting custom professions when none are found search: fixed crash in unit list after cancelling a job From 090f542d49be3891e03b42ceff8df3648336ead0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 6 Aug 2015 13:33:53 -0400 Subject: [PATCH 47/61] Allow for per-plugin compile flags --- plugins/Plugins.cmake | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/plugins/Plugins.cmake b/plugins/Plugins.cmake index 1e8ef8936..c801570db 100644 --- a/plugins/Plugins.cmake +++ b/plugins/Plugins.cmake @@ -59,7 +59,7 @@ ENDMACRO() MACRO(DFHACK_PLUGIN) PARSE_ARGUMENTS(PLUGIN - "LINK_LIBRARIES;DEPENDS;PROTOBUFS" + "LINK_LIBRARIES;DEPENDS;PROTOBUFS;COMPILE_FLAGS;COMPILE_FLAGS_GCC;COMPILE_FLAGS_MSVC" "SOME_OPT" ${ARGN} ) @@ -95,6 +95,13 @@ MACRO(DFHACK_PLUGIN) TARGET_LINK_LIBRARIES(${PLUGIN_NAME} dfhack dfhack-version ${PLUGIN_LINK_LIBRARIES}) ENDIF() + SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS}") + IF(UNIX) + SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS_GCC}") + ELSE() + SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS_MSVC}") + ENDIF() + IF(APPLE) SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES SUFFIX .plug.dylib PREFIX "") ELSEIF(UNIX) From 44287fc3a09816180c5bceebc034def699c4ea4c Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 6 Aug 2015 13:42:13 -0400 Subject: [PATCH 48/61] remotefortressreader: Check map validity before reading map info Fixes JapaMala/armok-vision#5 --- NEWS | 1 + plugins/remotefortressreader.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/NEWS b/NEWS index f87d901dc..9094dec76 100644 --- a/NEWS +++ b/NEWS @@ -37,6 +37,7 @@ DFHack Future - made "esc" work properly gui/gm-editor handles lua tables properly manipulator: fixed crash when selecting custom professions when none are found + remotefortressreader: fixed crash when attempting to send map info when no map was loaded search: fixed crash in unit list after cancelling a job stockpiles: now checks/sanitizes filenames when saving stocks: fixed a crash when right-clicking diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index bc9bdbb08..d3fba0827 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -931,6 +931,8 @@ static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in, static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, MapInfo *out) { + if (!Maps::IsValid()) + return CR_FAILURE; uint32_t size_x, size_y, size_z; int32_t pos_x, pos_y, pos_z; Maps::getSize(size_x, size_y, size_z); From a67326ad0058619ab6de3ab37e90a887999ae003 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 6 Aug 2015 17:01:11 -0400 Subject: [PATCH 49/61] git-describe: always use long format --- library/git-describe.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/git-describe.cmake b/library/git-describe.cmake index 5cc2bbb67..7ccd2d91d 100644 --- a/library/git-describe.cmake +++ b/library/git-describe.cmake @@ -1,4 +1,4 @@ -execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags +execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --long WORKING_DIRECTORY "${dfhack_SOURCE_DIR}" OUTPUT_VARIABLE DFHACK_GIT_DESCRIPTION) string(STRIP ${DFHACK_GIT_DESCRIPTION} DFHACK_GIT_DESCRIPTION) From de49befdbbf4f015a0d12f3767031fce9218be9c Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 6 Aug 2015 17:30:51 -0400 Subject: [PATCH 50/61] Improve version information * Add reimplementations of old DFHACK_VERSION-style macros * Expose full git commit ID * Expose all DFHack::Version functions to Lua --- Lua API.rst | 14 ++++++++++++++ NEWS | 2 +- library/DFHackVersion.cpp | 5 +++++ library/LuaApi.cpp | 8 ++++++++ library/git-describe.cmake | 8 +++++++- library/include/DFHackVersion.h | 9 +++++++++ 6 files changed, 44 insertions(+), 2 deletions(-) diff --git a/Lua API.rst b/Lua API.rst index 86233470a..3f92ad4db 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -785,6 +785,20 @@ can be omitted. Returns the DF version string from ``symbols.xml``. +* ``getDFHackVersion()`` +* ``getDFHackRelease()`` +* ``getCompiledDFVersion()`` +* ``getGitDescription()`` +* ``getGitCommit()`` + + Return information about the DFHack build in use. + + **Note:** ``getCompiledDFVersion()`` returns the DF version specified at compile time, + while ``getDFVersion()`` returns the version and typically the OS as well. + These do not necessarily match - for example, DFHack 0.34.11-r5 worked with + DF 0.34.10 and 0.34.11, so the former function would always return ``0.34.11`` + while the latter would return ``v0.34.10 `` or ``v0.34.11 ``. + * ``dfhack.getDFPath()`` Returns the DF directory path. diff --git a/NEWS b/NEWS index 9094dec76..10178f501 100644 --- a/NEWS +++ b/NEWS @@ -4,7 +4,7 @@ DFHack Future Developer plugins can be ignored on startup by setting the DFHACK_NO_DEV_PLUGINS environment variable The console on Linux and OS X now recognizes keyboard input between prompts JSON libraries available (C++ and Lua) - More build information available in plugins + More DFHack build information used in plugin version checks and available to plugins and lua scripts Fixed a rare overflow issue that could cause crashes on Linux and OS X Stopped DF window from receiving input when unfocused on OS X Fixed issues with keybindings involving Ctrl-A and Ctrl-Z, as well as Alt-E/U/N on OS X diff --git a/library/DFHackVersion.cpp b/library/DFHackVersion.cpp index df05779fb..cc56af180 100644 --- a/library/DFHackVersion.cpp +++ b/library/DFHackVersion.cpp @@ -1,3 +1,4 @@ +#define NO_DFHACK_VERSION_MACROS #include "DFHackVersion.h" #include "git-describe.h" #include "Export.h" @@ -19,5 +20,9 @@ namespace DFHack { { return DFHACK_GIT_DESCRIPTION; } + const char *git_commit() + { + return DFHACK_GIT_COMMIT; + } } } diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index ee2ca3702..0e507dafe 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -38,6 +38,7 @@ distribution. #include "DataDefs.h" #include "DataIdentity.h" #include "DataFuncs.h" +#include "DFHackVersion.h" #include "modules/World.h" #include "modules/Gui.h" @@ -1396,6 +1397,8 @@ static std::string df2utf(std::string s) { return DF2UTF(s); } static std::string utf2df(std::string s) { return UTF2DF(s); } static std::string df2console(std::string s) { return DF2CONSOLE(s); } +#define WRAP_VERSION_FUNC(name, function) WRAPN(name, DFHack::Version::function) + static const LuaWrapper::FunctionReg dfhack_module[] = { WRAP(getOSType), WRAP(getDFVersion), @@ -1408,6 +1411,11 @@ static const LuaWrapper::FunctionReg dfhack_module[] = { WRAP(df2utf), WRAP(utf2df), WRAP(df2console), + WRAP_VERSION_FUNC(getDFHackVersion, dfhack_version), + WRAP_VERSION_FUNC(getDFHackRelease, dfhack_release), + WRAP_VERSION_FUNC(getCompiledDFVersion, df_version), + WRAP_VERSION_FUNC(getGitDescription, git_description), + WRAP_VERSION_FUNC(getGitCommit, git_commit), { NULL, NULL } }; diff --git a/library/git-describe.cmake b/library/git-describe.cmake index 7ccd2d91d..9c5e7a874 100644 --- a/library/git-describe.cmake +++ b/library/git-describe.cmake @@ -1,9 +1,15 @@ execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --long WORKING_DIRECTORY "${dfhack_SOURCE_DIR}" OUTPUT_VARIABLE DFHACK_GIT_DESCRIPTION) +execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD + WORKING_DIRECTORY "${dfhack_SOURCE_DIR}" + OUTPUT_VARIABLE DFHACK_GIT_COMMIT) string(STRIP ${DFHACK_GIT_DESCRIPTION} DFHACK_GIT_DESCRIPTION) file(WRITE ${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h - "#define DFHACK_GIT_DESCRIPTION \"${DFHACK_GIT_DESCRIPTION}\"") + "#define DFHACK_GIT_DESCRIPTION \"${DFHACK_GIT_DESCRIPTION}\"\n") +string(STRIP ${DFHACK_GIT_COMMIT} DFHACK_GIT_COMMIT) +file(APPEND ${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h + "#define DFHACK_GIT_COMMIT \"${DFHACK_GIT_COMMIT}\"") execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different ${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h ${dfhack_SOURCE_DIR}/library/include/git-describe.h) diff --git a/library/include/DFHackVersion.h b/library/include/DFHackVersion.h index aa528ccbb..359ed0eba 100644 --- a/library/include/DFHackVersion.h +++ b/library/include/DFHackVersion.h @@ -5,5 +5,14 @@ namespace DFHack { const char *df_version(); const char *dfhack_release(); const char *git_description(); + const char *git_commit(); } } + +#ifndef NO_DFHACK_VERSION_MACROS +#define DF_VERSION DFHack::Version::df_version() +#define DFHACK_RELEASE DFHack::Version::dfhack_release() +#define DFHACK_VERSION DFHack::Version::dfhack_version() +#define DFHACK_GIT_DESCRIPTION DFHack::Version::git_description() +#define DFHACK_GIT_COMMIT DFHack::Version::git_commit() +#endif From afce8d810b637b4ddfe7ffe9ed8e31e667b0b1c2 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 6 Aug 2015 17:32:42 -0400 Subject: [PATCH 51/61] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index ccca7b721..e512e1845 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit ccca7b721bef0cbddb54694cf16927e5fe668b7f +Subproject commit e512e184568991deb28704c42ab87c60cecc4e68 From f46d3d137f72c4e5c619033253f5045fa0212c88 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 6 Aug 2015 20:41:54 -0400 Subject: [PATCH 52/61] Allow multiple contexts to be specified when adding keybindings --- NEWS | 1 + Readme.rst | 5 +++-- library/Core.cpp | 26 ++++++++++++++++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index 10178f501..5eee8c72e 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,7 @@ DFHack Future Fixed a rare overflow issue that could cause crashes on Linux and OS X Stopped DF window from receiving input when unfocused on OS X Fixed issues with keybindings involving Ctrl-A and Ctrl-Z, as well as Alt-E/U/N on OS X + Multiple contexts can now be specified when adding keybindings Lua Scripts can be enabled with the built-in enable/disable commands A new function, reqscript(), is available as a safer alternative to script_environment() diff --git a/Readme.rst b/Readme.rst index 84fc475a5..fec765dd1 100644 --- a/Readme.rst +++ b/Readme.rst @@ -212,7 +212,7 @@ Possible ways to call the command: The ** parameter above has the following *case-sensitive* syntax:: - [Ctrl-][Alt-][Shift-]KEY[@context] + [Ctrl-][Alt-][Shift-]KEY[@context[|context...]] where the *KEY* part can be F1-F9 or A-Z, and [] denote optional parts. @@ -227,7 +227,8 @@ the ``keybinding`` command among other things prints the current context string. Only bindings with a *context* tag that either matches the current context fully, or is a prefix ending at a '/' boundary would be considered for execution, i.e. for context ``foo/bar/baz``, possible matches are any of ``@foo/bar/baz``, ``@foo/bar``, -``@foo`` or none. +``@foo`` or none. Multiple contexts can be specified by separating them with a +pipe (``|``) - for example, ``@foo|bar|baz/foo``. Enabling plugins ================ diff --git a/library/Core.cpp b/library/Core.cpp index 62b63411f..d48f45199 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -2105,6 +2105,23 @@ bool Core::ClearKeyBindings(std::string keyspec) bool Core::AddKeyBinding(std::string keyspec, std::string cmdline) { + size_t at_pos = keyspec.find('@'); + if (at_pos != std::string::npos) + { + std::string raw_spec = keyspec.substr(0, at_pos); + std::string raw_focus = keyspec.substr(at_pos + 1); + if (raw_focus.find('|') != std::string::npos) + { + std::vector focus_strings; + split_string(&focus_strings, raw_focus, "|"); + for (size_t i = 0; i < focus_strings.size(); i++) + { + if (!AddKeyBinding(raw_spec + "@" + focus_strings[i], cmdline)) + return false; + } + return true; + } + } int sym; KeyBinding binding; if (!parseKeySpec(keyspec, &sym, &binding.modifiers, &binding.focus)) @@ -2156,11 +2173,12 @@ std::vector Core::ListKeyBindings(std::string keyspec) return rv; } -//////////////// -// ClassNamCheck -//////////////// -// Since there is no Process.cpp, put ClassNamCheck stuff in Core.cpp +///////////////// +// ClassNameCheck +///////////////// + +// Since there is no Process.cpp, put ClassNameCheck stuff in Core.cpp static std::set known_class_names; static std::map known_vptrs; From bce9d98633d56054138bdee51b1b41f343322c73 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 7 Aug 2015 14:33:38 -0400 Subject: [PATCH 53/61] New plugin: fix-unit-occupancy (fixes bug 3499) --- NEWS | 1 + Readme.rst | 16 +++ plugins/CMakeLists.txt | 1 + plugins/fix-unit-occupancy.cpp | 236 +++++++++++++++++++++++++++++++++ 4 files changed, 254 insertions(+) create mode 100644 plugins/fix-unit-occupancy.cpp diff --git a/NEWS b/NEWS index 5eee8c72e..ca285d1d6 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,7 @@ DFHack Future kill-lua: Interrupt running Lua scripts New plugins confirm: Adds confirmation dialogs for several potentially dangerous actions + fix-unit-occupancy: Fixes issues with unit occupancy, such as faulty "unit blocking tile" messages (bug 3499) New scripts burial: sets all unowned coffins to allow burial ("-pets" to allow pets too) fix-ster: changes fertility/sterility of animals or dwarves diff --git a/Readme.rst b/Readme.rst index fec765dd1..2c4d5d203 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1264,6 +1264,22 @@ This command adds the Guild Representative position to all Human civilizations, allowing them to make trade agreements (just as they did back in 0.28.181.40d and earlier) in case you haven't already modified your raws accordingly. +fix-unit-occupancy +------------------ +This plugin fixes issues with unit occupancy, notably issues with phantom +"unit blocking tile" messages (`Bug 3499`_). It can be run manually, or +periodically when enabled with the built-in enable/disable commands: + +* ``fix-unit-occupancy``: Run the plugin immediately. Available options: + + * ``-h``, ``here``, ``cursor``: Only operate on the tile at the cursor + * ``-n``, ``dry``, ``dry-run``: Do not write changes to map + +* ``fix-unit-occupancy interval X``: Run the plugin every ``X`` ticks (when enabled). + The default is 1200 ticks, or 1 day. Ticks are only counted when the game is unpaused. + +.. _`Bug 3499`: http://bay12games.com/dwarves/mantisbt/view.php?id=3499 + fixveins -------- Removes invalid references to mineral inclusions and restores missing ones. diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index fdc6e931c..2d726186d 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -121,6 +121,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(filltraffic filltraffic.cpp) DFHACK_PLUGIN(fix-armory fix-armory.cpp) DFHACK_PLUGIN(fixpositions fixpositions.cpp) + DFHACK_PLUGIN(fix-unit-occupancy fix-unit-occupancy.cpp) DFHACK_PLUGIN(fixveins fixveins.cpp) DFHACK_PLUGIN(flows flows.cpp) DFHACK_PLUGIN(follow follow.cpp) diff --git a/plugins/fix-unit-occupancy.cpp b/plugins/fix-unit-occupancy.cpp new file mode 100644 index 000000000..c55870bea --- /dev/null +++ b/plugins/fix-unit-occupancy.cpp @@ -0,0 +1,236 @@ +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" + +#include "modules/Units.h" +#include "modules/Translation.h" +#include "modules/World.h" + +#include "df/map_block.h" +#include "df/unit.h" +#include "df/world.h" + +using namespace DFHack; + +DFHACK_PLUGIN("fix-unit-occupancy"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); +REQUIRE_GLOBAL(cursor); +REQUIRE_GLOBAL(world); + +static int run_interval = 1200; // daily + +inline float getClock() +{ + return (float)clock() / (float)CLOCKS_PER_SEC; +} + +static std::string get_unit_description(df::unit *unit) +{ + if (!unit) + return ""; + std::string desc; + auto name = Units::getVisibleName(unit); + if (name->has_name) + desc = Translation::TranslateName(name, false); + desc += (desc.size() ? ", " : "") + Units::getProfessionName(unit); // Check animal type too + + return desc; +} + +df::unit *findUnit(int x, int y, int z) +{ + for (auto u = world->units.active.begin(); u != world->units.active.end(); ++u) + { + if ((**u).pos.x == x && (**u).pos.y == y && (**u).pos.z == z) + return *u; + } + return NULL; +} + +struct uo_opts { + bool dry_run; + bool use_cursor; + uo_opts (bool dry_run = false, bool use_cursor = false) + :dry_run(dry_run), use_cursor(use_cursor) + {} +}; + +command_result cmd_fix_unit_occupancy (color_ostream &out, std::vector & parameters); + +unsigned fix_unit_occupancy (color_ostream &out, uo_opts &opts) +{ + if (!Core::getInstance().isWorldLoaded()) + return 0; + + if (opts.use_cursor && cursor->x < 0) + out.printerr("No cursor\n"); + + unsigned count = 0; + float time1 = getClock(); + for (size_t i = 0; i < world->map.map_blocks.size(); i++) + { + df::map_block *block = world->map.map_blocks[i]; + int map_z = block->map_pos.z; + if (opts.use_cursor && (map_z != cursor->z || block->map_pos.y != (cursor->y / 16) * 16 || block->map_pos.x != (cursor->x / 16) * 16)) + continue; + for (int x = 0; x < 16; x++) + { + int map_x = x + block->map_pos.x; + for (int y = 0; y < 16; y++) + { + if (block->designation[x][y].bits.hidden) + continue; + int map_y = y + block->map_pos.y; + if (opts.use_cursor && (map_x != cursor->x || map_y != cursor->y)) + continue; + bool cur_occupancy = block->occupancy[x][y].bits.unit; + bool fixed_occupancy = cur_occupancy; + df::unit *cur_unit = findUnit(map_x, map_y, map_z); + if (cur_occupancy && !cur_unit) + { + out.print("%sFixing occupancy at (%i, %i, %i) - no unit found\n", + opts.dry_run ? "(Dry run) " : "", + map_x, map_y, map_z); + fixed_occupancy = false; + } + // else if (!cur_occupancy && cur_unit) + // { + // out.printerr("Unit found at (%i, %i, %i): %s\n", map_x, map_y, map_z, get_unit_description(cur_unit).c_str()); + // fixed_occupancy = true; + // } + if (cur_occupancy != fixed_occupancy && !opts.dry_run) + { + ++count; + block->occupancy[x][y].bits.unit = fixed_occupancy; + } + } + } + } + float time2 = getClock(); + std::cerr << "fix-unit-occupancy: elapsed time: " << time2 - time1 << " secs" << endl; + if (count) + out << "Fixed occupancy of " << count << " tiles [fix-unit-occupancy]" << endl; + return count; +} + +unsigned fix_unit_occupancy (color_ostream &out) +{ + uo_opts tmp; + return fix_unit_occupancy(out, tmp); +} + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "fix-unit-occupancy", + "Fix unit occupancy issues such as phantom 'creature blocking site' messages (bug 3499)", + cmd_fix_unit_occupancy, + false, //allow non-interactive use + " enable fix-unit-occupancy: Enable the plugin\n" + " disable fix-unit-occupancy fix-unit-occupancy: Disable the plugin\n" + " fix-unit-occupancy: Run the plugin immediately. Available options:\n" + " -h|here|cursor: Only operate on the tile at the cursor\n" + " -n|dry|dry-run: Do not write changes to map\n" + " fix-unit-occupancy interval X: Run the plugin every X ticks (when enabled).\n" + " Default is 1200, or 1 day. Ticks are only counted when the game is unpaused.\n" + + )); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) +{ + return CR_OK; +} + +DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) +{ + is_enabled = enable; + if (is_enabled) + fix_unit_occupancy(out); + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate (color_ostream &out) +{ + static unsigned tick = UINT_MAX; + static decltype(world->frame_counter) old_world_frame = 0; + if (is_enabled && World::isFortressMode()) + { + // only increment tick when the world has changed + if (old_world_frame != world->frame_counter) + { + old_world_frame = world->frame_counter; + tick++; + } + if (tick > run_interval) + { + tick = 0; + fix_unit_occupancy(out); + } + } + else + { + tick = INT_MAX; + old_world_frame = 0; + } + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange (color_ostream &out, state_change_event evt) +{ + if (evt == SC_MAP_LOADED && is_enabled && World::isFortressMode()) + fix_unit_occupancy(out); + return CR_OK; +} + +command_result cmd_fix_unit_occupancy (color_ostream &out, std::vector & parameters) +{ + CoreSuspender suspend; + uo_opts opts; + bool ok = true; + + if (parameters.size() >= 1 && (parameters[0] == "-i" || parameters[0].find("interval") != std::string::npos)) + { + if (parameters.size() >= 2) + { + int new_interval = atoi(parameters[1].c_str()); + if (new_interval < 100) + { + out.printerr("Invalid interval - minimum is 100 ticks\n"); + return CR_WRONG_USAGE; + } + run_interval = new_interval; + if (!is_enabled) + out << "note: Plugin not enabled (use `enable fix-unit-occupancy` to enable)" << endl; + return CR_OK; + } + else + return CR_WRONG_USAGE; + } + + for (auto opt = parameters.begin(); opt != parameters.end(); ++opt) + { + if (*opt == "-n" || opt->find("dry") != std::string::npos) + opts.dry_run = true; + else if (*opt == "-h" || opt->find("cursor") != std::string::npos || opt->find("here") != std::string::npos) + opts.use_cursor = true; + else if (opt->find("enable") != std::string::npos) + plugin_enable(out, true); + else if (opt->find("disable") != std::string::npos) + plugin_enable(out, false); + else + { + out.printerr("Unknown parameter: %s\n", opt->c_str()); + ok = false; + } + } + if (!ok) + return CR_WRONG_USAGE; + + fix_unit_occupancy(out, opts); + + return CR_OK; +} From dc747d59df7a7fa83923caf5d7b86969917550fb Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 9 Aug 2015 13:20:28 -0400 Subject: [PATCH 54/61] Link dfhack-md5 on Windows --- library/CMakeLists.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 270948e1a..328443f48 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -240,14 +240,13 @@ IF(UNIX) ENDIF() ENDIF() -IF(UNIX) +IF(APPLE) + SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) +ELSEIF(UNIX) SET(PROJECT_LIBS rt dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) - IF(APPLE) - SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) - ENDIF() ELSE(WIN32) #FIXME: do we really need psapi? - SET(PROJECT_LIBS psapi dfhack-tinyxml dfhack-tinythread) + SET(PROJECT_LIBS psapi dfhack-md5 dfhack-tinyxml dfhack-tinythread) ENDIF() ADD_LIBRARY(dfhack-version STATIC DFHackVersion.cpp) From fcd15bfd7313446ebbce1b28745c96952cb3c84a Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 9 Aug 2015 13:37:26 -0400 Subject: [PATCH 55/61] Move vshook plugin to title-version and enable by default --- NEWS | 1 + dfhack.init-example | 3 ++ plugins/CMakeLists.txt | 1 + plugins/devel/CMakeLists.txt | 1 - plugins/devel/vshook.cpp | 71 ------------------------------------ plugins/title-version.cpp | 66 +++++++++++++++++++++++++++++++++ 6 files changed, 71 insertions(+), 72 deletions(-) delete mode 100644 plugins/devel/vshook.cpp create mode 100644 plugins/title-version.cpp diff --git a/NEWS b/NEWS index ca285d1d6..77f62bc99 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,7 @@ DFHack Future New plugins confirm: Adds confirmation dialogs for several potentially dangerous actions fix-unit-occupancy: Fixes issues with unit occupancy, such as faulty "unit blocking tile" messages (bug 3499) + title-version (formerly vshook): Display DFHack version on title screen New scripts burial: sets all unowned coffins to allow burial ("-pets" to allow pets too) fix-ster: changes fertility/sterility of animals or dwarves diff --git a/dfhack.init-example b/dfhack.init-example index 780655c0a..bbf938981 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -191,6 +191,9 @@ tweak tradereq-pet-gender # Globally acting plugins # ########################### +# Display DFHack version on title screen +enable title-version + # Dwarf Manipulator (simple in-game Dwarf Therapist replacement) enable manipulator diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 2d726186d..258b0f5de 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -161,6 +161,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(stocks stocks.cpp) DFHACK_PLUGIN(strangemood strangemood.cpp) DFHACK_PLUGIN(tiletypes tiletypes.cpp Brushes.h) + DFHACK_PLUGIN(title-version title-version.cpp) DFHACK_PLUGIN(trackstop trackstop.cpp) # DFHACK_PLUGIN(treefarm treefarm.cpp) DFHACK_PLUGIN(tubefill tubefill.cpp) diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index e03d4c4b7..74c198645 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -19,7 +19,6 @@ DFHACK_PLUGIN(stepBetween stepBetween.cpp) DFHACK_PLUGIN(stockcheck stockcheck.cpp) DFHACK_PLUGIN(stripcaged stripcaged.cpp) DFHACK_PLUGIN(tilesieve tilesieve.cpp) -DFHACK_PLUGIN(vshook vshook.cpp) DFHACK_PLUGIN(zoom zoom.cpp) IF(UNIX) diff --git a/plugins/devel/vshook.cpp b/plugins/devel/vshook.cpp deleted file mode 100644 index a0e2a5e59..000000000 --- a/plugins/devel/vshook.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "Core.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "df/graphic.h" -#include "df/viewscreen_titlest.h" - -using std::vector; -using std::string; -using std::stack; -using namespace DFHack; - -using df::global::gps; - -DFHACK_PLUGIN("vshook"); - -DFHACK_PLUGIN_IS_ENABLED(is_enabled); - -struct title_hook : df::viewscreen_titlest { - typedef df::viewscreen_titlest interpose_base; - - DEFINE_VMETHOD_INTERPOSE(void, render, ()) - { - INTERPOSE_NEXT(render)(); - - Screen::Pen pen(' ',COLOR_WHITE,COLOR_BLACK); - Screen::paintString(pen, 0, 0, "DFHack "); - Screen::paintString(pen, 7, 0, Version::dfhack_version()); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(title_hook, render); - -DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) -{ - if (!gps) - return CR_FAILURE; - - if (enable != is_enabled) - { - if (!INTERPOSE_HOOK(title_hook, render).apply(enable)) - return CR_FAILURE; - - is_enabled = enable; - } - - return CR_OK; -} - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - // DON'T DO THIS IN NON-EXAMPLE PLUGINS - plugin_enable(out, true); - - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - INTERPOSE_HOOK(title_hook, render).remove(); - return CR_OK; -} diff --git a/plugins/title-version.cpp b/plugins/title-version.cpp new file mode 100644 index 000000000..c52a7a9f5 --- /dev/null +++ b/plugins/title-version.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include + +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" +#include "modules/Gui.h" +#include "modules/Screen.h" +#include "VTableInterpose.h" +#include "DFHackVersion.h" + +#include "df/graphic.h" +#include "df/viewscreen_titlest.h" +#include "uicommon.h" + +using std::vector; +using std::string; +using namespace DFHack; + +DFHACK_PLUGIN("title-version"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); +REQUIRE_GLOBAL(gps); + +struct title_version_hook : df::viewscreen_titlest { + typedef df::viewscreen_titlest interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + int x = 0, y = 0; + OutputString(COLOR_WHITE, x, y, string("DFHack ") + DFHACK_VERSION); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(title_version_hook, render); + +DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) +{ + if (!gps) + return CR_FAILURE; + + if (enable != is_enabled) + { + if (!INTERPOSE_HOOK(title_version_hook, render).apply(enable)) + return CR_FAILURE; + + is_enabled = enable; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_init (color_ostream &out, vector &commands) +{ + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) +{ + INTERPOSE_HOOK(title_version_hook, render).remove(); + return CR_OK; +} From fec19b97006f56e1e7e80f8e06a0c74011917b75 Mon Sep 17 00:00:00 2001 From: Lethosor Date: Mon, 10 Aug 2015 10:12:40 -0400 Subject: [PATCH 56/61] Compile.rst: Link to "Linux dependencies" wiki page --- Compile.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Compile.rst b/Compile.rst index 260af3b13..36563e881 100644 --- a/Compile.rst +++ b/Compile.rst @@ -59,6 +59,8 @@ You should be able to find them in your distro repositories (on Arch linux 'perl To build Stonesense, you'll also need OpenGL headers. +Some additional dependencies for other distros are listed on the `wiki `_. + Build ===== Building is fairly straightforward. Enter the ``build`` folder (or create an empty folder in the DFHack directory to use instead) and start the build like this:: From 9b5f42e3a918b8167a76752c5b86c9cfdaca6bc8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 10 Aug 2015 10:41:47 -0400 Subject: [PATCH 57/61] tweak farm-plot-select: Only show controls when plots are fully built Fixes #521 --- NEWS | 1 + plugins/tweak/tweaks/farm-plot-select.h | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 77f62bc99..a47a18934 100644 --- a/NEWS +++ b/NEWS @@ -48,6 +48,7 @@ DFHack Future - fixed a crash on arena load - number keys (e.g. 2/8) take priority over cursor keys when applicable tweak fps-min fixed + tweak farm-plot-select: Stopped controls from appearing when plots weren't fully built workflow: Fixed some issues with stuck jobs - Note: Existing stuck jobs must be cancelled and re-added zone: Fixed a crash when using "zone set" (and a few other potential crashes) diff --git a/plugins/tweak/tweaks/farm-plot-select.h b/plugins/tweak/tweaks/farm-plot-select.h index 38b0596bd..696c7ca2e 100644 --- a/plugins/tweak/tweaks/farm-plot-select.h +++ b/plugins/tweak/tweaks/farm-plot-select.h @@ -76,7 +76,10 @@ struct farm_select_hook : df::viewscreen_dwarfmodest { DEFINE_VMETHOD_INTERPOSE(void, render, ()) { INTERPOSE_NEXT(render)(); - if (!getFarmPlot() || !ui->selected_farm_crops.size()) + auto farm_plot = getFarmPlot(); + if (!farm_plot || !ui->selected_farm_crops.size()) + return; + if (farm_plot->getBuildStage() != farm_plot->getMaxBuildStage()) return; auto dims = Gui::getDwarfmodeViewDims(); int x = dims.menu_x1 + 1, From d09cdc83baf69bbe68b2b6d70514c355e1a00db5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 10 Aug 2015 11:25:15 -0400 Subject: [PATCH 58/61] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index e512e1845..7cfed040f 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit e512e184568991deb28704c42ab87c60cecc4e68 +Subproject commit 7cfed040fe83b94fb6b97afec1c5d86ccf1cb81e From 05f36856f4abd6339b6f44ac188d5acabf853811 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 10 Aug 2015 11:30:21 -0400 Subject: [PATCH 59/61] fix-unit-occupancy: Always provide feedback when running command --- plugins/fix-unit-occupancy.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/fix-unit-occupancy.cpp b/plugins/fix-unit-occupancy.cpp index c55870bea..4675b5cfb 100644 --- a/plugins/fix-unit-occupancy.cpp +++ b/plugins/fix-unit-occupancy.cpp @@ -230,7 +230,9 @@ command_result cmd_fix_unit_occupancy (color_ostream &out, std::vector Date: Mon, 10 Aug 2015 12:44:27 -0400 Subject: [PATCH 60/61] Make lua listdir functions return useful error messages --- library/LuaApi.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 0e507dafe..c23cfe22a 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2209,7 +2209,14 @@ static int filesystem_listdir(lua_State *L) luaL_checktype(L,1,LUA_TSTRING); std::string dir=lua_tostring(L,1); std::vector files; - DFHack::Filesystem::listdir(dir, files); + int err = DFHack::Filesystem::listdir(dir, files); + if (err) + { + lua_pushnil(L); + lua_pushstring(L, strerror(err)); + lua_pushinteger(L, err); + return 3; + } lua_newtable(L); for(int i=0;i Date: Mon, 10 Aug 2015 23:24:24 -0400 Subject: [PATCH 61/61] typo s/ot a forum/to a forum/ --- dfhack.init-example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dfhack.init-example b/dfhack.init-example index 1bb3f8acf..0c322d850 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -45,7 +45,7 @@ keybinding add Ctrl-Shift-P command-prompt keybinding add Alt-M@dwarfmode/Default "dwarfmonitor prefs" keybinding add Ctrl-F@dwarfmode/Default "dwarfmonitor stats" -# export a Dwarf's preferences screen in BBCode to post ot a forum +# export a Dwarf's preferences screen in BBCode to post to a forum keybinding add Ctrl-Shift-F@dwarfmode forum-dwarves ##############################