Merge branch 'develop' into docs
commit
df9c37a8b7
@ -0,0 +1,166 @@
|
||||
// This template is appropriate for plugins that periodically check game state
|
||||
// and make some sort of automated change. These types of plugins typically
|
||||
// provide a command that can be used to configure the plugin behavior and
|
||||
// require a world to be loaded before they can function. This kind of plugin
|
||||
// should persist its state in the savegame and auto-re-enable itself when a
|
||||
// savegame that had this plugin enabled is loaded.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "df/world.h"
|
||||
|
||||
#include "Core.h"
|
||||
#include "Debug.h"
|
||||
#include "PluginManager.h"
|
||||
|
||||
#include "modules/Persistence.h"
|
||||
#include "modules/World.h"
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
using namespace DFHack;
|
||||
|
||||
DFHACK_PLUGIN("persistent_per_save_example");
|
||||
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
||||
|
||||
REQUIRE_GLOBAL(world);
|
||||
|
||||
// logging levels can be dynamically controlled with the `debugfilter` command.
|
||||
namespace DFHack {
|
||||
// for configuration-related logging
|
||||
DBG_DECLARE(persistent_per_save_example, status, DebugCategory::LINFO);
|
||||
// for logging during the periodic scan
|
||||
DBG_DECLARE(persistent_per_save_example, cycle, DebugCategory::LINFO);
|
||||
}
|
||||
|
||||
static const string CONFIG_KEY = string(plugin_name) + "/config";
|
||||
static PersistentDataItem config;
|
||||
enum ConfigValues {
|
||||
CONFIG_IS_ENABLED = 0,
|
||||
CONFIG_CYCLE_TICKS = 1,
|
||||
};
|
||||
static int get_config_val(int index) {
|
||||
if (!config.isValid())
|
||||
return -1;
|
||||
return config.ival(index);
|
||||
}
|
||||
static bool get_config_bool(int index) {
|
||||
return get_config_val(index) == 1;
|
||||
}
|
||||
static void set_config_val(int index, int value) {
|
||||
if (config.isValid())
|
||||
config.ival(index) = value;
|
||||
}
|
||||
static void set_config_bool(int index, bool value) {
|
||||
set_config_val(index, value ? 1 : 0);
|
||||
}
|
||||
|
||||
static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
|
||||
|
||||
static command_result do_command(color_ostream &out, vector<string> ¶meters);
|
||||
static void do_cycle(color_ostream &out);
|
||||
|
||||
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) {
|
||||
DEBUG(status,out).print("initializing %s\n", plugin_name);
|
||||
|
||||
// provide a configuration interface for the plugin
|
||||
commands.push_back(PluginCommand(
|
||||
plugin_name,
|
||||
"Short (~54 character) description of command.",
|
||||
do_command));
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
|
||||
if (!Core::getInstance().isWorldLoaded()) {
|
||||
out.printerr("Cannot enable %s without a loaded world.\n", plugin_name);
|
||||
return CR_FAILURE;
|
||||
}
|
||||
|
||||
if (enable != is_enabled) {
|
||||
is_enabled = enable;
|
||||
DEBUG(status,out).print("%s from the API; persisting\n",
|
||||
is_enabled ? "enabled" : "disabled");
|
||||
set_config_bool(CONFIG_IS_ENABLED, is_enabled);
|
||||
} else {
|
||||
DEBUG(status,out).print("%s from the API, but already %s; no action\n",
|
||||
is_enabled ? "enabled" : "disabled",
|
||||
is_enabled ? "enabled" : "disabled");
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown (color_ostream &out) {
|
||||
DEBUG(status,out).print("shutting down %s\n", plugin_name);
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_load_data (color_ostream &out) {
|
||||
config = World::GetPersistentData(CONFIG_KEY);
|
||||
|
||||
if (!config.isValid()) {
|
||||
DEBUG(status,out).print("no config found in this save; initializing\n");
|
||||
config = World::AddPersistentData(CONFIG_KEY);
|
||||
set_config_bool(CONFIG_IS_ENABLED, is_enabled);
|
||||
set_config_val(CONFIG_CYCLE_TICKS, 6000);
|
||||
}
|
||||
|
||||
// we have to copy our enabled flag into the global plugin variable, but
|
||||
// all the other state we can directly read/modify from the persistent
|
||||
// data structure.
|
||||
is_enabled = get_config_bool(CONFIG_IS_ENABLED);
|
||||
DEBUG(status,out).print("loading persisted enabled state: %s\n",
|
||||
is_enabled ? "true" : "false");
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
|
||||
if (event == DFHack::SC_WORLD_UNLOADED) {
|
||||
if (is_enabled) {
|
||||
DEBUG(status,out).print("world unloaded; disabling %s\n",
|
||||
plugin_name);
|
||||
is_enabled = false;
|
||||
}
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_onupdate(color_ostream &out) {
|
||||
if (is_enabled && world->frame_counter - cycle_timestamp >= get_config_val(CONFIG_CYCLE_TICKS))
|
||||
do_cycle(out);
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
static command_result do_command(color_ostream &out, vector<string> ¶meters) {
|
||||
// be sure to suspend the core if any DF state is read or modified
|
||||
CoreSuspender suspend;
|
||||
|
||||
if (!Core::getInstance().isWorldLoaded()) {
|
||||
out.printerr("Cannot run %s without a loaded world.\n", plugin_name);
|
||||
return CR_FAILURE;
|
||||
}
|
||||
|
||||
// TODO: configuration logic
|
||||
// simple commandline parsing can be done in C++, but there are lua libraries
|
||||
// that can easily handle more complex commandlines. see the blueprint plugin
|
||||
// for an example.
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// cycle logic
|
||||
//
|
||||
|
||||
static void do_cycle(color_ostream &out) {
|
||||
// mark that we have recently run
|
||||
cycle_timestamp = world->frame_counter;
|
||||
|
||||
DEBUG(cycle,out).print("running %s cycle\n", plugin_name);
|
||||
|
||||
// TODO: logic that runs every get_config_val(CONFIG_CYCLE_TICKS) ticks
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
// This template is appropriate for plugins that simply provide one or more
|
||||
// commands, but don't need to be "enabled" to function.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Debug.h"
|
||||
#include "PluginManager.h"
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
using namespace DFHack;
|
||||
|
||||
DFHACK_PLUGIN("simple_command_example");
|
||||
|
||||
namespace DFHack {
|
||||
DBG_DECLARE(simple_command_example, log);
|
||||
}
|
||||
|
||||
static command_result do_command(color_ostream &out, vector<string> ¶meters);
|
||||
|
||||
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) {
|
||||
DEBUG(log,out).print("initializing %s\n", plugin_name);
|
||||
|
||||
commands.push_back(PluginCommand(
|
||||
plugin_name,
|
||||
"Short (~54 character) description of command.",
|
||||
do_command));
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
static command_result do_command(color_ostream &out, vector<string> ¶meters) {
|
||||
// be sure to suspend the core if any DF state is read or modified
|
||||
CoreSuspender suspend;
|
||||
|
||||
// TODO: command logic
|
||||
|
||||
return CR_OK;
|
||||
}
|
@ -0,0 +1,193 @@
|
||||
// This is an example plugin that documents and implements all the plugin
|
||||
// callbacks and features. You can include it in the regular build by setting
|
||||
// the BUILD_SKELETON option in CMake to ON. Play with loading and unloading
|
||||
// the plugin in various game states (e.g. with and without a world loaded),
|
||||
// and see the debug messages get printed to the console.
|
||||
//
|
||||
// See the other example plugins in this directory for plugins that are
|
||||
// configured for specific use cases (but don't come with as many comments as
|
||||
// this one does).
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "df/world.h"
|
||||
|
||||
#include "Core.h"
|
||||
#include "Debug.h"
|
||||
#include "PluginManager.h"
|
||||
|
||||
#include "modules/Persistence.h"
|
||||
#include "modules/World.h"
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
using namespace DFHack;
|
||||
|
||||
// Expose the plugin name to the DFHack core, as well as metadata like the
|
||||
// DFHack version that this plugin was compiled with. This macro provides a
|
||||
// variable for the plugin name as const char * plugin_name.
|
||||
// The name provided must correspond to the filename --
|
||||
// skeleton.plug.so, skeleton.plug.dylib, or skeleton.plug.dll in this case
|
||||
DFHACK_PLUGIN("skeleton");
|
||||
|
||||
// The identifier declared with this macro (i.e. is_enabled) is used to track
|
||||
// whether the plugin is in an "enabled" state. If you don't need enablement
|
||||
// for your plugin, you don't need this line. This variable will also be read
|
||||
// by the `plug` builtin command; when true the plugin will be shown as enabled.
|
||||
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
||||
|
||||
// Any globals a plugin requires (e.g. world) should be listed here.
|
||||
// For example, this line expands to "using df::global::world" and prevents the
|
||||
// plugin from being loaded if df::global::world is null (i.e. missing from
|
||||
// symbols.xml).
|
||||
REQUIRE_GLOBAL(world);
|
||||
|
||||
// logging levels can be dynamically controlled with the `debugfilter` command.
|
||||
// Actual plugins will likely want to set the default level to LINFO or LWARNING
|
||||
// instead of the LDEBUG used here.
|
||||
namespace DFHack {
|
||||
// for configuration-related logging
|
||||
DBG_DECLARE(skeleton, status, DebugCategory::LDEBUG);
|
||||
// for plugin_onupdate logging
|
||||
DBG_DECLARE(skeleton, onupdate, DebugCategory::LDEBUG);
|
||||
// for command-related logging
|
||||
DBG_DECLARE(skeleton, command, DebugCategory::LDEBUG);
|
||||
}
|
||||
|
||||
static command_result command_callback1(color_ostream &out, vector<string> ¶meters);
|
||||
|
||||
// run when the plugin is loaded
|
||||
DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) {
|
||||
DEBUG(status,out).print("initializing %s\n", plugin_name);
|
||||
|
||||
// For in-tree plugins, don't use the "usage" parameter of PluginCommand.
|
||||
// Instead, add an .rst file with the same name as the plugin to the
|
||||
// docs/plugins/ directory.
|
||||
commands.push_back(PluginCommand(
|
||||
"skeleton",
|
||||
"Short (~54 character) description of command.", // to use one line in the ``[DFHack]# ls`` output
|
||||
command_callback1));
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
// run when the plugin is unloaded
|
||||
DFhackCExport command_result plugin_shutdown(color_ostream &out) {
|
||||
DEBUG(status,out).print("shutting down %s\n", plugin_name);
|
||||
|
||||
// You *MUST* kill all threads you created before this returns.
|
||||
// If everything fails, just return CR_FAILURE. Your plugin will be
|
||||
// in a zombie state, but things won't crash.
|
||||
return CR_OK;
|
||||
|
||||
}
|
||||
|
||||
// run when the `enable` or `disable` command is run with this plugin name as
|
||||
// an argument
|
||||
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
|
||||
DEBUG(status,out).print("%s from the API\n", enable ? "enabled" : "disabled");
|
||||
|
||||
// you have to maintain the state of the is_enabled variable yourself. it
|
||||
// doesn't happen automatically.
|
||||
is_enabled = enable;
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
// Called to notify the plugin about important state changes.
|
||||
// Invoked with DF suspended, and always before the matching plugin_onupdate.
|
||||
// More event codes may be added in the future.
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
|
||||
switch (event) {
|
||||
case SC_UNKNOWN:
|
||||
DEBUG(status,out).print("game state changed: SC_UNKNOWN\n");
|
||||
break;
|
||||
case SC_WORLD_LOADED:
|
||||
DEBUG(status,out).print("game state changed: SC_WORLD_LOADED\n");
|
||||
break;
|
||||
case SC_WORLD_UNLOADED:
|
||||
DEBUG(status,out).print("game state changed: SC_WORLD_UNLOADED\n");
|
||||
break;
|
||||
case SC_MAP_LOADED:
|
||||
DEBUG(status,out).print("game state changed: SC_MAP_LOADED\n");
|
||||
break;
|
||||
case SC_MAP_UNLOADED:
|
||||
DEBUG(status,out).print("game state changed: SC_MAP_UNLOADED\n");
|
||||
break;
|
||||
case SC_VIEWSCREEN_CHANGED:
|
||||
DEBUG(status,out).print("game state changed: SC_VIEWSCREEN_CHANGED\n");
|
||||
break;
|
||||
case SC_CORE_INITIALIZED:
|
||||
DEBUG(status,out).print("game state changed: SC_CORE_INITIALIZED\n");
|
||||
break;
|
||||
case SC_BEGIN_UNLOAD:
|
||||
DEBUG(status,out).print("game state changed: SC_BEGIN_UNLOAD\n");
|
||||
break;
|
||||
case SC_PAUSED:
|
||||
DEBUG(status,out).print("game state changed: SC_PAUSED\n");
|
||||
break;
|
||||
case SC_UNPAUSED:
|
||||
DEBUG(status,out).print("game state changed: SC_UNPAUSED\n");
|
||||
break;
|
||||
}
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
// Whatever you put here will be done in each game frame refresh. Don't abuse it.
|
||||
// Note that if the plugin implements the enabled API, this function is only called
|
||||
// if the plugin is enabled.
|
||||
DFhackCExport command_result plugin_onupdate (color_ostream &out) {
|
||||
DEBUG(onupdate,out).print(
|
||||
"onupdate called (run 'debugfilter set info skeleton onupdate' to stop"
|
||||
" seeing these messages)\n");
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
// If you need to save or load world-specific data, define these functions.
|
||||
// plugin_save_data is called when the game might be about to save the world,
|
||||
// and plugin_load_data is called whenever a new world is loaded. If the plugin
|
||||
// is loaded or unloaded while a world is active, plugin_save_data or
|
||||
// plugin_load_data will be called immediately.
|
||||
DFhackCExport command_result plugin_save_data (color_ostream &out) {
|
||||
DEBUG(status,out).print("save or unload is imminent; time to persist state\n");
|
||||
|
||||
// Call functions in the Persistence module here. If your PersistantDataItem
|
||||
// objects are already up to date, then they will get persisted with the
|
||||
// save automatically and there is nothing extra you need to do here.
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_load_data (color_ostream &out) {
|
||||
DEBUG(status,out).print("world is loading; time to load persisted state\n");
|
||||
|
||||
// Call functions in the Persistence module here. See
|
||||
// persistent_per_save_example.cpp for an example.
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
// This is the callback we registered in plugin_init. Note that while plugin
|
||||
// callbacks are called with the core suspended, command callbacks are called
|
||||
// from a different thread and need to explicity suspend the core if they
|
||||
// interact with Lua or DF game state (most commands do at least one of these).
|
||||
static command_result command_callback1(color_ostream &out, vector<string> ¶meters) {
|
||||
DEBUG(command,out).print("%s command called with %zu parameters\n",
|
||||
plugin_name, parameters.size());
|
||||
|
||||
// I'll say it again: always suspend the core in command callbacks unless
|
||||
// all your data is local.
|
||||
CoreSuspender suspend;
|
||||
|
||||
// Return CR_WRONG_USAGE to print out your help text. The help text is
|
||||
// sourced from the associated rst file in docs/plugins/. The same help will
|
||||
// also be returned by 'help your-command'.
|
||||
|
||||
// simple commandline parsing can be done in C++, but there are lua libraries
|
||||
// that can easily handle more complex commandlines. see the blueprint plugin
|
||||
// for an example.
|
||||
|
||||
// TODO: do something according to the flags set in the options struct
|
||||
|
||||
return CR_OK;
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
// This template is appropriate for plugins that can be enabled to make some
|
||||
// specific persistent change to the game, but don't need a world to be loaded
|
||||
// before they are enabled. These types of plugins typically register some sort
|
||||
// of hook on enable and clear the hook on disable. They are generally enabled
|
||||
// from dfhack.init and do not need to persist and reload their enabled state.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "df/viewscreen_titlest.h"
|
||||
|
||||
#include "Debug.h"
|
||||
#include "PluginManager.h"
|
||||
#include "VTableInterpose.h"
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
using namespace DFHack;
|
||||
|
||||
DFHACK_PLUGIN("ui_addition_example");
|
||||
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
||||
|
||||
namespace DFHack {
|
||||
DBG_DECLARE(ui_addition_example, log);
|
||||
}
|
||||
|
||||
// example of hooking a screen so the plugin code will run whenever the screen
|
||||
// is visible
|
||||
struct title_version_hook : df::viewscreen_titlest {
|
||||
typedef df::viewscreen_titlest interpose_base;
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(void, render, ()) {
|
||||
INTERPOSE_NEXT(render)();
|
||||
|
||||
// TODO: injected render logic here
|
||||
}
|
||||
};
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(title_version_hook, render);
|
||||
|
||||
DFhackCExport command_result plugin_shutdown (color_ostream &out) {
|
||||
DEBUG(log,out).print("shutting down %s\n", plugin_name);
|
||||
INTERPOSE_HOOK(title_version_hook, render).remove();
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) {
|
||||
if (enable != is_enabled) {
|
||||
DEBUG(log,out).print("%s %s\n", plugin_name,
|
||||
is_enabled ? "enabled" : "disabled");
|
||||
if (!INTERPOSE_HOOK(title_version_hook, render).apply(enable))
|
||||
return CR_FAILURE;
|
||||
|
||||
is_enabled = enable;
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
project(skeleton)
|
||||
# A list of source files
|
||||
set(PROJECT_SRCS
|
||||
skeleton.cpp
|
||||
)
|
||||
# A list of headers
|
||||
set(PROJECT_HDRS
|
||||
skeleton.h
|
||||
)
|
||||
set_source_files_properties(${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||
|
||||
# mash them together (headers are marked as headers and nothing will try to compile them)
|
||||
list(APPEND PROJECT_SRCS ${PROJECT_HDRS})
|
||||
|
||||
# option to use a thread for no particular reason
|
||||
option(SKELETON_THREAD "Use threads in the skeleton plugin." ON)
|
||||
if(UNIX)
|
||||
if(APPLE)
|
||||
set(PROJECT_LIBS
|
||||
# add any extra mac libraries here
|
||||
${PROJECT_LIBS}
|
||||
)
|
||||
else()
|
||||
set(PROJECT_LIBS
|
||||
# add any extra linux libraries here
|
||||
${PROJECT_LIBS}
|
||||
)
|
||||
endif()
|
||||
else()
|
||||
set(PROJECT_LIBS
|
||||
# add any extra windows libraries here
|
||||
${PROJECT_LIBS}
|
||||
)
|
||||
endif()
|
||||
# this makes sure all the stuff is put in proper places and linked to dfhack
|
||||
dfhack_plugin(skeleton ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS})
|
@ -1,171 +0,0 @@
|
||||
// This is a generic plugin that does nothing useful apart from acting as an example... of a plugin that does nothing :D
|
||||
|
||||
// some headers required for a plugin. Nothing special, just the basics.
|
||||
#include "Core.h"
|
||||
#include <Console.h>
|
||||
#include <Export.h>
|
||||
#include <PluginManager.h>
|
||||
#include <modules/EventManager.h>
|
||||
// If you need to save data per-world:
|
||||
//#include "modules/Persistence.h"
|
||||
|
||||
// DF data structure definition headers
|
||||
#include "DataDefs.h"
|
||||
//#include "df/world.h"
|
||||
|
||||
// our own, empty header.
|
||||
#include "skeleton.h"
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
|
||||
// Expose the plugin name to the DFHack core, as well as metadata like the DFHack version.
|
||||
// The name string provided must correspond to the filename -
|
||||
// skeleton.plug.so, skeleton.plug.dylib, or skeleton.plug.dll in this case
|
||||
DFHACK_PLUGIN("skeleton");
|
||||
|
||||
// The identifier declared with this macro (ie. enabled) can be specified by the user
|
||||
// and subsequently used to manage the plugin's operations.
|
||||
// This will also be tracked by `plug`; when true the plugin will be shown as enabled.
|
||||
DFHACK_PLUGIN_IS_ENABLED(enabled);
|
||||
|
||||
// Any globals a plugin requires (e.g. world) should be listed here.
|
||||
// For example, this line expands to "using df::global::world" and prevents the
|
||||
// plugin from being loaded if df::global::world is null (i.e. missing from symbols.xml):
|
||||
//
|
||||
REQUIRE_GLOBAL(world);
|
||||
|
||||
// You may want some compile time debugging options
|
||||
// one easy system just requires you to cache the color_ostream &out into a global debug variable
|
||||
//#define P_DEBUG 1
|
||||
//uint16_t maxTickFreq = 1200; //maybe you want to use some events
|
||||
|
||||
command_result command_callback1(color_ostream &out, std::vector<std::string> ¶meters);
|
||||
|
||||
DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) {
|
||||
commands.push_back(PluginCommand("skeleton",
|
||||
"~54 character description of plugin", //to use one line in the ``[DFHack]# ls`` output
|
||||
command_callback1,
|
||||
false,
|
||||
"example usage"
|
||||
" skeleton <option> <args>\n"
|
||||
" explanation of plugin/command\n"
|
||||
"\n"
|
||||
" skeleton\n"
|
||||
" what happens when using the command\n"
|
||||
"\n"
|
||||
" skeleton option1\n"
|
||||
" what happens when using the command with option1\n"
|
||||
"\n"));
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown(color_ostream &out) {
|
||||
// You *MUST* kill all threads you created before this returns.
|
||||
// If everything fails, just return CR_FAILURE. Your plugin will be
|
||||
// in a zombie state, but things won't crash.
|
||||
return CR_OK;
|
||||
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
|
||||
namespace EM = EventManager;
|
||||
if (enable && !enabled) {
|
||||
//using namespace EM::EventType;
|
||||
//EM::EventHandler eventHandler(onNewEvent, maxTickFreq);
|
||||
//EM::registerListener(EventType::JOB_COMPLETED, eventHandler, plugin_self);
|
||||
//out.print("plugin enabled!\n");
|
||||
} else if (!enable && enabled) {
|
||||
EM::unregisterAll(plugin_self);
|
||||
//out.print("plugin disabled!\n");
|
||||
}
|
||||
enabled = enable;
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
|
||||
/* OPTIONAL *
|
||||
// Called to notify the plugin about important state changes.
|
||||
// Invoked with DF suspended, and always before the matching plugin_onupdate.
|
||||
// More event codes may be added in the future.
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
|
||||
if (enabled) {
|
||||
switch (event) {
|
||||
case SC_UNKNOWN:
|
||||
break;
|
||||
case SC_WORLD_LOADED:
|
||||
break;
|
||||
case SC_WORLD_UNLOADED:
|
||||
break;
|
||||
case SC_MAP_LOADED:
|
||||
break;
|
||||
case SC_MAP_UNLOADED:
|
||||
break;
|
||||
case SC_VIEWSCREEN_CHANGED:
|
||||
break;
|
||||
case SC_CORE_INITIALIZED:
|
||||
break;
|
||||
case SC_BEGIN_UNLOAD:
|
||||
break;
|
||||
case SC_PAUSED:
|
||||
break;
|
||||
case SC_UNPAUSED:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
// Whatever you put here will be done in each game step. Don't abuse it.
|
||||
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
|
||||
{
|
||||
// whetever. You don't need to suspend DF execution here.
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
// If you need to save or load world-specific data, define these functions.
|
||||
// plugin_save_data is called when the game might be about to save the world,
|
||||
// and plugin_load_data is called whenever a new world is loaded. If the plugin
|
||||
// is loaded or unloaded while a world is active, plugin_save_data or
|
||||
// plugin_load_data will be called immediately.
|
||||
DFhackCExport command_result plugin_save_data (color_ostream &out)
|
||||
{
|
||||
// Call functions in the Persistence module here.
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_load_data (color_ostream &out)
|
||||
{
|
||||
// Call functions in the Persistence module here.
|
||||
return CR_OK;
|
||||
}
|
||||
* OPTIONAL */
|
||||
|
||||
|
||||
// A command! It sits around and looks pretty. And it's nice and friendly.
|
||||
command_result command_callback1(color_ostream &out, std::vector<std::string> ¶meters) {
|
||||
// It's nice to print a help message you get invalid options
|
||||
// from the user instead of just acting strange.
|
||||
// This can be achieved by adding the extended help string to the
|
||||
// PluginCommand registration as show above, and then returning
|
||||
// CR_WRONG_USAGE from the function. The same string will also
|
||||
// be used by 'help your-command'.
|
||||
if (!parameters.empty()) {
|
||||
return CR_WRONG_USAGE; //or maybe you want it to do something else
|
||||
}
|
||||
// Commands are called from threads other than the DF one.
|
||||
// Suspend this thread until DF has time for us.
|
||||
// **If you use CoreSuspender** it'll automatically resume DF when
|
||||
// execution leaves the current scope.
|
||||
CoreSuspender suspend;
|
||||
// Actually do something here. Yay.
|
||||
|
||||
// process parameters
|
||||
if (parameters.size() == 1 && parameters[0] == "option1") {
|
||||
// stuff
|
||||
} else {
|
||||
return CR_FAILURE;
|
||||
}
|
||||
// Give control back to DF.
|
||||
return CR_OK;
|
||||
}
|
@ -1 +0,0 @@
|
||||
#pragma once
|
@ -1,61 +0,0 @@
|
||||
#include "Core.h"
|
||||
#include <Console.h>
|
||||
#include <Export.h>
|
||||
#include <PluginManager.h>
|
||||
#include <modules/EventManager.h>
|
||||
|
||||
//#include "df/world.h"
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
|
||||
DFHACK_PLUGIN("skeleton2");
|
||||
DFHACK_PLUGIN_IS_ENABLED(enabled);
|
||||
//REQUIRE_GLOBAL(world);
|
||||
|
||||
command_result skeleton2 (color_ostream &out, std::vector <std::string> & parameters);
|
||||
|
||||
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands) {
|
||||
commands.push_back(PluginCommand("skeleton2",
|
||||
"~54 character description of plugin", //to use one line in the ``[DFHack]# ls`` output
|
||||
skeleton2,
|
||||
false,
|
||||
"example usage"
|
||||
" skeleton2 <option> <args>\n"
|
||||
" explanation of plugin/command\n"
|
||||
"\n"
|
||||
" skeleton2\n"
|
||||
" what happens when using the command\n"
|
||||
"\n"
|
||||
" skeleton2 option1\n"
|
||||
" what happens when using the command with option1\n"
|
||||
"\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) {
|
||||
namespace EM = EventManager;
|
||||
if (enable && !enabled) {
|
||||
//using namespace EM::EventType;
|
||||
//EM::EventHandler eventHandler(onNewEvent, maxTickFreq);
|
||||
//EM::registerListener(EventType::JOB_COMPLETED, eventHandler, plugin_self);
|
||||
//out.print("plugin enabled!\n");
|
||||
} else if (!enable && enabled) {
|
||||
EM::unregisterAll(plugin_self);
|
||||
//out.print("plugin disabled!\n");
|
||||
}
|
||||
enabled = enable;
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
command_result skeleton2 (color_ostream &out, std::vector <std::string> & parameters) {
|
||||
if (!parameters.empty())
|
||||
return CR_WRONG_USAGE;
|
||||
CoreSuspender suspend;
|
||||
out.print("blah");
|
||||
return CR_OK;
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit 3d65c9a75a8c7a3607e71529f6264bab3e637755
|
||||
Subproject commit 52e292ecf7e498c6114a81223380d2b12f3718c1
|
@ -0,0 +1,56 @@
|
||||
local widgets = require('gui.widgets')
|
||||
|
||||
function test.editfield_cursor()
|
||||
local e = widgets.EditField{}
|
||||
e:setFocus(true)
|
||||
expect.eq(1, e.cursor, 'cursor should be after the empty string')
|
||||
|
||||
e:onInput{_STRING=string.byte('a')}
|
||||
expect.eq('a', e.text)
|
||||
expect.eq(2, e.cursor)
|
||||
|
||||
e:setText('one two three')
|
||||
expect.eq(14, e.cursor, 'cursor should be after the last char')
|
||||
e:onInput{_STRING=string.byte('s')}
|
||||
expect.eq('one two threes', e.text)
|
||||
expect.eq(15, e.cursor)
|
||||
|
||||
e:setCursor(4)
|
||||
e:onInput{_STRING=string.byte('s')}
|
||||
expect.eq('ones two threes', e.text)
|
||||
expect.eq(5, e.cursor)
|
||||
|
||||
e:onInput{CURSOR_LEFT=true}
|
||||
expect.eq(4, e.cursor)
|
||||
e:onInput{CURSOR_RIGHT=true}
|
||||
expect.eq(5, e.cursor)
|
||||
e:onInput{A_CARE_MOVE_W=true}
|
||||
expect.eq(1, e.cursor, 'interpret alt-left as home')
|
||||
e:onInput{A_MOVE_E_DOWN=true}
|
||||
expect.eq(6, e.cursor, 'interpret ctrl-right as goto beginning of next word')
|
||||
e:onInput{A_CARE_MOVE_E=true}
|
||||
expect.eq(16, e.cursor, 'interpret alt-right as end')
|
||||
e:onInput{A_MOVE_W_DOWN=true}
|
||||
expect.eq(9, e.cursor, 'interpret ctrl-left as goto end of previous word')
|
||||
end
|
||||
|
||||
function test.editfield_click()
|
||||
local e = widgets.EditField{text='word'}
|
||||
e:setFocus(true)
|
||||
expect.eq(5, e.cursor)
|
||||
|
||||
mock.patch(e, 'getMousePos', mock.func(0), function()
|
||||
e:onInput{_MOUSE_L=true}
|
||||
expect.eq(1, e.cursor)
|
||||
end)
|
||||
|
||||
mock.patch(e, 'getMousePos', mock.func(20), function()
|
||||
e:onInput{_MOUSE_L=true}
|
||||
expect.eq(5, e.cursor, 'should only seek to end of text')
|
||||
end)
|
||||
|
||||
mock.patch(e, 'getMousePos', mock.func(2), function()
|
||||
e:onInput{_MOUSE_L=true}
|
||||
expect.eq(3, e.cursor)
|
||||
end)
|
||||
end
|
@ -1,18 +1,37 @@
|
||||
local widgets = require('gui.widgets')
|
||||
|
||||
function test.hotkeylabel_click()
|
||||
local func = mock.func()
|
||||
local l = widgets.HotkeyLabel{key='SELECT', on_activate=func}
|
||||
|
||||
mock.patch(l, 'getMousePos', mock.func(0), function()
|
||||
l:onInput{_MOUSE_L=true}
|
||||
expect.eq(1, func.call_count)
|
||||
end)
|
||||
end
|
||||
|
||||
function test.togglehotkeylabel()
|
||||
local toggle = widgets.ToggleHotkeyLabel{}
|
||||
expect.true_(toggle:getOptionValue())
|
||||
toggle:cycle()
|
||||
expect.false_(toggle:getOptionValue())
|
||||
toggle:cycle()
|
||||
expect.true_(toggle:getOptionValue())
|
||||
local toggle = widgets.ToggleHotkeyLabel{}
|
||||
expect.true_(toggle:getOptionValue())
|
||||
toggle:cycle()
|
||||
expect.false_(toggle:getOptionValue())
|
||||
toggle:cycle()
|
||||
expect.true_(toggle:getOptionValue())
|
||||
end
|
||||
|
||||
function test.togglehotkeylabel_default_value()
|
||||
local toggle = widgets.ToggleHotkeyLabel{initial_option=2}
|
||||
expect.false_(toggle:getOptionValue())
|
||||
local toggle = widgets.ToggleHotkeyLabel{initial_option=2}
|
||||
expect.false_(toggle:getOptionValue())
|
||||
|
||||
toggle = widgets.ToggleHotkeyLabel{initial_option=false}
|
||||
expect.false_(toggle:getOptionValue())
|
||||
end
|
||||
|
||||
toggle = widgets.ToggleHotkeyLabel{initial_option=false}
|
||||
expect.false_(toggle:getOptionValue())
|
||||
function test.togglehotkeylabel_click()
|
||||
local l = widgets.ToggleHotkeyLabel{}
|
||||
expect.true_(l:getOptionValue())
|
||||
mock.patch(l, 'getMousePos', mock.func(0), function()
|
||||
l:onInput{_MOUSE_L=true}
|
||||
expect.false_(l:getOptionValue())
|
||||
end)
|
||||
end
|
||||
|
@ -0,0 +1,648 @@
|
||||
local h = require('helpdb')
|
||||
|
||||
local mock_plugin_db = {
|
||||
hascommands={
|
||||
boxbinders={description="Box your binders.", help=[[Box your binders.
|
||||
This command will help you box your binders.]]},
|
||||
bindboxers={description="Bind your boxers.", help=[[Bind your boxers.
|
||||
This command will help you bind your boxers.]]}
|
||||
},
|
||||
samename={
|
||||
samename={description="Samename.", help=[[Samename.
|
||||
This command has the same name as its host plugin.]]}
|
||||
},
|
||||
nocommand={},
|
||||
nodocs_hascommands={
|
||||
nodoc_command={description="cpp description.", help=[[cpp description.
|
||||
Rest of help.]]}
|
||||
},
|
||||
nodocs_samename={
|
||||
nodocs_samename={description="Nodocs samename.", help=[[Nodocs samename.
|
||||
This command has the same name as its host plugin but no rst docs.]]}
|
||||
},
|
||||
nodocs_nocommand={},
|
||||
}
|
||||
|
||||
local mock_command_db = {}
|
||||
for k,v in pairs(mock_plugin_db) do
|
||||
for c,d in pairs(v) do
|
||||
mock_command_db[c] = d
|
||||
end
|
||||
end
|
||||
|
||||
local mock_script_db = {
|
||||
basic=true,
|
||||
['subdir/scriptname']=true,
|
||||
inscript_docs=true,
|
||||
inscript_short_only=true,
|
||||
nodocs_script=true,
|
||||
}
|
||||
|
||||
local files = {
|
||||
['hack/docs/docs/Tags.txt']=[[
|
||||
* fort: Tools that are useful while in fort mode.
|
||||
|
||||
* armok: Tools that give you complete control over
|
||||
an aspect of the game or provide access to
|
||||
information that the game intentionally keeps
|
||||
hidden.
|
||||
|
||||
* map: Tools that interact with the game map.
|
||||
|
||||
* units: Tools that interact with units.
|
||||
|
||||
* nomembers: Nothing is tagged with this.
|
||||
]],
|
||||
['hack/docs/docs/tools/hascommands.txt']=[[
|
||||
hascommands
|
||||
***********
|
||||
|
||||
**Tags:** fort | armok | units
|
||||
|
||||
Documented a plugin that
|
||||
has commands.
|
||||
|
||||
**Command:** "boxbinders"
|
||||
|
||||
Documented boxbinders.
|
||||
|
||||
**Command:** "bindboxers"
|
||||
|
||||
Documented bindboxers.
|
||||
|
||||
Documented full help.
|
||||
]],
|
||||
['hack/docs/docs/tools/samename.txt']=[[
|
||||
samename
|
||||
********
|
||||
|
||||
**Tags:** fort | armok
|
||||
| units
|
||||
|
||||
**Command:** "samename"
|
||||
|
||||
Documented samename.
|
||||
|
||||
Documented full help.
|
||||
]],
|
||||
['hack/docs/docs/tools/nocommand.txt']=[[
|
||||
nocommand
|
||||
*********
|
||||
|
||||
**Tags:** fort | armok |
|
||||
units
|
||||
|
||||
Documented nocommand.
|
||||
|
||||
Documented full help.
|
||||
]],
|
||||
['hack/docs/docs/tools/basic.txt']=[[
|
||||
basic
|
||||
*****
|
||||
|
||||
**Tags:** map
|
||||
|
||||
**Command:** "basic"
|
||||
|
||||
Documented basic.
|
||||
|
||||
Documented full help.
|
||||
]],
|
||||
['hack/docs/docs/tools/subdir/scriptname.txt']=[[
|
||||
subdir/scriptname
|
||||
*****************
|
||||
|
||||
**Tags:** map
|
||||
|
||||
**Command:** "subdir/scriptname"
|
||||
|
||||
Documented subdir/scriptname.
|
||||
|
||||
Documented full help.
|
||||
]],
|
||||
['scripts/scriptpath/basic.lua']=[[
|
||||
-- in-file short description for basic
|
||||
-- [====[
|
||||
basic
|
||||
=====
|
||||
|
||||
**Tags:** map
|
||||
|
||||
**Command:** "basic"
|
||||
|
||||
in-file basic.
|
||||
|
||||
Documented full help.
|
||||
]====]
|
||||
script contents
|
||||
]],
|
||||
['scripts/scriptpath/subdir/scriptname.lua']=[[
|
||||
-- in-file short description for scriptname
|
||||
-- [====[
|
||||
subdir/scriptname
|
||||
=================
|
||||
|
||||
**Tags:** map
|
||||
|
||||
**Command:** "subdir/scriptname"
|
||||
|
||||
in-file scriptname.
|
||||
|
||||
Documented full help.
|
||||
]====]
|
||||
script contents
|
||||
]],
|
||||
['scripts/scriptpath/inscript_docs.lua']=[[
|
||||
-- in-file short description for inscript_docs
|
||||
-- [====[
|
||||
inscript_docs
|
||||
=============
|
||||
|
||||
**Tags:** map | badtag
|
||||
|
||||
**Command:** "inscript_docs"
|
||||
|
||||
in-file inscript_docs.
|
||||
|
||||
Documented full help.
|
||||
]====]
|
||||
script contents
|
||||
]],
|
||||
['scripts/scriptpath/nodocs_script.lua']=[[
|
||||
script contents
|
||||
]],
|
||||
['scripts/scriptpath/inscript_short_only.lua']=[[
|
||||
-- inscript short desc.
|
||||
|
||||
script contents
|
||||
]],
|
||||
['other/scriptpath/basic.lua']=[[
|
||||
-- in-file short description for basic (other)
|
||||
-- [====[
|
||||
basic
|
||||
=====
|
||||
|
||||
**Tags:** map
|
||||
|
||||
**Command:** "basic"
|
||||
|
||||
in-file basic (other).
|
||||
|
||||
Documented full help.
|
||||
]====]
|
||||
script contents
|
||||
]],
|
||||
['other/scriptpath/subdir/scriptname.lua']=[[
|
||||
-- in-file short description for scriptname (other)
|
||||
-- [====[
|
||||
subdir/scriptname
|
||||
=================
|
||||
|
||||
**Tags:** map
|
||||
|
||||
**Command:** "subdir/scriptname"
|
||||
|
||||
in-file scriptname (other).
|
||||
|
||||
Documented full help.
|
||||
]====]
|
||||
script contents
|
||||
]],
|
||||
['other/scriptpath/inscript_docs.lua']=[[
|
||||
-- in-file short description for inscript_docs (other)
|
||||
-- [====[
|
||||
inscript_docs
|
||||
=============
|
||||
|
||||
**Tags:** map
|
||||
|
||||
**Command:** "inscript_docs"
|
||||
|
||||
in-file inscript_docs (other).
|
||||
|
||||
Documented full help.
|
||||
]====]
|
||||
script contents
|
||||
]],
|
||||
}
|
||||
|
||||
local function mock_getCommandHelp(command)
|
||||
if mock_command_db[command] then
|
||||
return mock_command_db[command].help
|
||||
end
|
||||
end
|
||||
|
||||
local function mock_listPlugins()
|
||||
local list = {}
|
||||
for k in pairs(mock_plugin_db) do
|
||||
table.insert(list, k)
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
local function mock_listCommands(plugin)
|
||||
local list = {}
|
||||
for k,v in pairs(mock_plugin_db) do
|
||||
if k == plugin then
|
||||
for c in pairs(v) do
|
||||
table.insert(list, c)
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
local function mock_getCommandDescription(command)
|
||||
if mock_command_db[command] then
|
||||
return mock_command_db[command].description
|
||||
end
|
||||
end
|
||||
|
||||
local function mock_getScriptPaths()
|
||||
return {'scripts/scriptpath', 'other/scriptpath'}
|
||||
end
|
||||
|
||||
local function mock_mtime(path)
|
||||
if files[path] then return 1 end
|
||||
return -1
|
||||
end
|
||||
|
||||
local function mock_listdir_recursive(script_path)
|
||||
local list = {}
|
||||
for s in pairs(mock_script_db) do
|
||||
table.insert(list, {isdir=false, path=s..'.lua'})
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
local function mock_getTickCount()
|
||||
return 100000
|
||||
end
|
||||
|
||||
local function mock_pcall(fn, fname)
|
||||
if fn ~= io.lines then error('unexpected fn for pcall') end
|
||||
if not files[fname] then
|
||||
return false
|
||||
end
|
||||
return true, files[fname]:gmatch('([^\n]*)\n?')
|
||||
end
|
||||
|
||||
config.wrapper = function(test_fn)
|
||||
mock.patch({
|
||||
{h.dfhack.internal, 'getCommandHelp', mock_getCommandHelp},
|
||||
{h.dfhack.internal, 'listPlugins', mock_listPlugins},
|
||||
{h.dfhack.internal, 'listCommands', mock_listCommands},
|
||||
{h.dfhack.internal, 'getCommandDescription', mock_getCommandDescription},
|
||||
{h.dfhack.internal, 'getScriptPaths', mock_getScriptPaths},
|
||||
{h.dfhack.filesystem, 'mtime', mock_mtime},
|
||||
{h.dfhack.filesystem, 'listdir_recursive', mock_listdir_recursive},
|
||||
{h.dfhack, 'getTickCount', mock_getTickCount},
|
||||
{h, 'pcall', mock_pcall},
|
||||
}, test_fn)
|
||||
end
|
||||
|
||||
function test.is_entry()
|
||||
expect.true_(h.is_entry('ls'),
|
||||
'builtin commands get an entry')
|
||||
expect.true_(h.is_entry('hascommands'),
|
||||
'plugins whose names do not match their commands get an entry')
|
||||
expect.true_(h.is_entry('boxbinders'),
|
||||
'commands whose name does not match the host plugin get an entry')
|
||||
expect.true_(h.is_entry('samename'),
|
||||
'plugins that have a command with the same name get one entry')
|
||||
expect.true_(h.is_entry('nocommand'),
|
||||
'plugins that do not have commands get an entry')
|
||||
expect.true_(h.is_entry('basic'),
|
||||
'scripts in the script path get an entry')
|
||||
expect.true_(h.is_entry('subdir/scriptname'),
|
||||
'scripts in subdirs of a script path get an entry')
|
||||
|
||||
expect.true_(h.is_entry('nodocs_hascommands'),
|
||||
'plugins whose names do not match their commands get an entry')
|
||||
expect.true_(h.is_entry('nodoc_command'),
|
||||
'commands whose name does not match the host plugin get an entry')
|
||||
expect.true_(h.is_entry('nodocs_samename'),
|
||||
'plugins that have a command with the same name get one entry')
|
||||
expect.true_(h.is_entry('nodocs_nocommand'),
|
||||
'plugins that do not have commands get an entry')
|
||||
expect.true_(h.is_entry('inscript_docs'),
|
||||
'scripts in the script path get an entry')
|
||||
expect.true_(h.is_entry('nodocs_script'),
|
||||
'scripts in the script path get an entry')
|
||||
expect.true_(h.is_entry('inscript_short_only'),
|
||||
'scripts in the script path get an entry')
|
||||
|
||||
expect.false_(h.is_entry(nil),
|
||||
'nil is not an entry')
|
||||
expect.false_(h.is_entry(''),
|
||||
'blank is not an entry')
|
||||
expect.false_(h.is_entry('notanentryname'),
|
||||
'strings that are neither plugin names nor command names do not get an entry')
|
||||
|
||||
expect.true_(h.is_entry({'hascommands', 'boxbinders', 'nocommand'}),
|
||||
'list of valid entries')
|
||||
expect.false_(h.is_entry({'hascommands', 'notanentryname'}),
|
||||
'list contains an element that is not an entry')
|
||||
end
|
||||
|
||||
function test.get_entry_types()
|
||||
expect.table_eq({builtin=true, command=true}, h.get_entry_types('ls'))
|
||||
|
||||
expect.table_eq({plugin=true}, h.get_entry_types('hascommands'))
|
||||
expect.table_eq({command=true}, h.get_entry_types('boxbinders'))
|
||||
expect.table_eq({plugin=true, command=true}, h.get_entry_types('samename'))
|
||||
expect.table_eq({plugin=true}, h.get_entry_types('nocommand'))
|
||||
expect.table_eq({command=true}, h.get_entry_types('basic'))
|
||||
expect.table_eq({command=true}, h.get_entry_types('subdir/scriptname'))
|
||||
|
||||
expect.table_eq({plugin=true}, h.get_entry_types('nodocs_hascommands'))
|
||||
expect.table_eq({command=true}, h.get_entry_types('nodoc_command'))
|
||||
expect.table_eq({plugin=true, command=true}, h.get_entry_types('nodocs_samename'))
|
||||
expect.table_eq({plugin=true}, h.get_entry_types('nodocs_nocommand'))
|
||||
expect.table_eq({command=true}, h.get_entry_types('nodocs_script'))
|
||||
expect.table_eq({command=true}, h.get_entry_types('inscript_docs'))
|
||||
expect.table_eq({command=true}, h.get_entry_types('inscript_short_only'))
|
||||
|
||||
expect.error_match('entry not found', function()
|
||||
h.get_entry_types('notanentry') end)
|
||||
end
|
||||
|
||||
function test.get_entry_short_help()
|
||||
expect.eq('No help available.', h.get_entry_short_help('ls'),
|
||||
'no docs for builtin fn result in default short description')
|
||||
|
||||
expect.eq('Documented a plugin that has commands.', h.get_entry_short_help('hascommands'))
|
||||
expect.eq('Box your binders.', h.get_entry_short_help('boxbinders'),
|
||||
'should get short help from command description')
|
||||
expect.eq('Samename.', h.get_entry_short_help('samename'),
|
||||
'should get short help from command description')
|
||||
expect.eq('Documented nocommand.', h.get_entry_short_help('nocommand'))
|
||||
expect.eq('Documented basic.', h.get_entry_short_help('basic'))
|
||||
expect.eq('Documented subdir/scriptname.', h.get_entry_short_help('subdir/scriptname'))
|
||||
|
||||
expect.eq('No help available.', h.get_entry_short_help('nodocs_hascommands'))
|
||||
expect.eq('cpp description.', h.get_entry_short_help('nodoc_command'),
|
||||
'should get short help from command description')
|
||||
expect.eq('Nodocs samename.', h.get_entry_short_help('nodocs_samename'),
|
||||
'should get short help from command description')
|
||||
expect.eq('No help available.', h.get_entry_short_help('nodocs_nocommand'))
|
||||
expect.eq('in-file short description for inscript_docs.',
|
||||
h.get_entry_short_help('inscript_docs'),
|
||||
'should get short help from header comment')
|
||||
expect.eq('No help available.',
|
||||
h.get_entry_short_help('nodocs_script'))
|
||||
expect.eq('inscript short desc.',
|
||||
h.get_entry_short_help('inscript_short_only'),
|
||||
'should get short help from header comment')
|
||||
end
|
||||
|
||||
function test.get_entry_long_help()
|
||||
-- long help for plugins/commands that have doc files should match the
|
||||
-- contents of those files exactly
|
||||
expect.eq(files['hack/docs/docs/tools/hascommands.txt'],
|
||||
h.get_entry_long_help('hascommands'))
|
||||
expect.eq(files['hack/docs/docs/tools/hascommands.txt'],
|
||||
h.get_entry_long_help('boxbinders'))
|
||||
expect.eq(files['hack/docs/docs/tools/samename.txt'],
|
||||
h.get_entry_long_help('samename'))
|
||||
expect.eq(files['hack/docs/docs/tools/nocommand.txt'],
|
||||
h.get_entry_long_help('nocommand'))
|
||||
expect.eq(files['hack/docs/docs/tools/basic.txt'],
|
||||
h.get_entry_long_help('basic'))
|
||||
expect.eq(files['hack/docs/docs/tools/subdir/scriptname.txt'],
|
||||
h.get_entry_long_help('subdir/scriptname'))
|
||||
|
||||
-- plugins/commands that have no doc files get the default template
|
||||
expect.eq([[ls
|
||||
**
|
||||
|
||||
No help available.
|
||||
]], h.get_entry_long_help('ls'))
|
||||
expect.eq([[nodocs_hascommands
|
||||
******************
|
||||
|
||||
No help available.
|
||||
]], h.get_entry_long_help('nodocs_hascommands'))
|
||||
expect.eq([[nodocs_hascommands
|
||||
******************
|
||||
|
||||
No help available.
|
||||
]], h.get_entry_long_help('nodoc_command'))
|
||||
expect.eq([[Nodocs samename.
|
||||
This command has the same name as its host plugin but no rst docs.]], h.get_entry_long_help('nodocs_samename'))
|
||||
expect.eq([[nodocs_nocommand
|
||||
****************
|
||||
|
||||
No help available.
|
||||
]], h.get_entry_long_help('nodocs_nocommand'))
|
||||
expect.eq([[nodocs_script
|
||||
*************
|
||||
|
||||
No help available.
|
||||
]], h.get_entry_long_help('nodocs_script'))
|
||||
expect.eq([[inscript_short_only
|
||||
*******************
|
||||
|
||||
No help available.
|
||||
]], h.get_entry_long_help('inscript_short_only'))
|
||||
|
||||
-- scripts that have no doc files get the docs from the script lua source
|
||||
expect.eq([[inscript_docs
|
||||
=============
|
||||
|
||||
**Tags:** map | badtag
|
||||
|
||||
**Command:** "inscript_docs"
|
||||
|
||||
in-file inscript_docs.
|
||||
|
||||
Documented full help.]], h.get_entry_long_help('inscript_docs'))
|
||||
end
|
||||
|
||||
function test.get_entry_tags()
|
||||
expect.table_eq({fort=true, armok=true, units=true},
|
||||
h.get_entry_tags('hascommands'))
|
||||
expect.table_eq({fort=true, armok=true, units=true},
|
||||
h.get_entry_tags('samename'))
|
||||
expect.table_eq({fort=true, armok=true, units=true},
|
||||
h.get_entry_tags('nocommand'))
|
||||
expect.table_eq({map=true}, h.get_entry_tags('basic'))
|
||||
expect.table_eq({map=true}, h.get_entry_tags('inscript_docs'),
|
||||
'bad tags should get filtered out')
|
||||
end
|
||||
|
||||
function test.is_tag()
|
||||
-- see tags defined in the Tags.txt files entry above
|
||||
expect.true_(h.is_tag('map'))
|
||||
expect.true_(h.is_tag({'map', 'armok'}))
|
||||
|
||||
expect.false_(h.is_tag(nil))
|
||||
expect.false_(h.is_tag(''))
|
||||
expect.false_(h.is_tag('not_tag'))
|
||||
expect.false_(h.is_tag({'map', 'not_tag', 'armok'}))
|
||||
end
|
||||
|
||||
function test.get_tags()
|
||||
expect.table_eq({'armok', 'fort', 'map', 'nomembers', 'units'},
|
||||
h.get_tags())
|
||||
end
|
||||
|
||||
function test.get_tag_data()
|
||||
local tag_data = h.get_tag_data('armok')
|
||||
table.sort(tag_data)
|
||||
expect.table_eq({description='Tools that give you complete control over an aspect of the game or provide access to information that the game intentionally keeps hidden.',
|
||||
'bindboxers', 'boxbinders', 'hascommands', 'nocommand', 'samename'},
|
||||
tag_data,
|
||||
'multi-line descriptions should get joined into a single line.')
|
||||
|
||||
tag_data = h.get_tag_data('fort')
|
||||
table.sort(tag_data)
|
||||
expect.table_eq({description='Tools that are useful while in fort mode.',
|
||||
'bindboxers', 'boxbinders', 'hascommands', 'nocommand', 'samename'},
|
||||
tag_data)
|
||||
|
||||
tag_data = h.get_tag_data('units')
|
||||
table.sort(tag_data)
|
||||
expect.table_eq({description='Tools that interact with units.',
|
||||
'bindboxers', 'boxbinders', 'hascommands', 'nocommand', 'samename'},
|
||||
tag_data)
|
||||
|
||||
tag_data = h.get_tag_data('map')
|
||||
table.sort(tag_data)
|
||||
expect.table_eq({description='Tools that interact with the game map.',
|
||||
'basic', 'inscript_docs', 'subdir/scriptname'},
|
||||
tag_data)
|
||||
|
||||
expect.table_eq({description='Nothing is tagged with this.'}, h.get_tag_data('nomembers'))
|
||||
|
||||
expect.error_match('tag not found',
|
||||
function() h.get_tag_data('notatag') end)
|
||||
end
|
||||
|
||||
function test.search_entries()
|
||||
-- all entries, in alphabetical order by last path component
|
||||
local expected = {'?', 'alias', 'basic', 'bindboxers', 'boxbinders',
|
||||
'clear', 'cls', 'die', 'dir', 'disable', 'devel/dump-rpc', 'enable',
|
||||
'fpause', 'hascommands', 'help', 'hide', 'inscript_docs',
|
||||
'inscript_short_only', 'keybinding', 'kill-lua', 'load', 'ls', 'man',
|
||||
'nocommand', 'nodoc_command', 'nodocs_hascommands', 'nodocs_nocommand',
|
||||
'nodocs_samename', 'nodocs_script', 'plug', 'reload', 'samename',
|
||||
'script', 'subdir/scriptname', 'sc-script', 'show', 'tags', 'type',
|
||||
'unload'}
|
||||
table.sort(expected, h.sort_by_basename)
|
||||
expect.table_eq(expected, h.search_entries())
|
||||
expect.table_eq(expected, h.search_entries({}))
|
||||
expect.table_eq(expected, h.search_entries(nil, {}))
|
||||
expect.table_eq(expected, h.search_entries({}, {}))
|
||||
|
||||
expected = {'inscript_docs', 'subdir/scriptname'}
|
||||
expect.table_eq(expected, h.search_entries({tag='map', str='script'}))
|
||||
|
||||
expected = {'script', 'sc-script'}
|
||||
table.sort(expected, h.sort_by_basename)
|
||||
expect.table_eq(expected, h.search_entries({str='script',
|
||||
entry_type='builtin'}))
|
||||
|
||||
expected = {'inscript_docs', 'inscript_short_only','nodocs_script',
|
||||
'subdir/scriptname'}
|
||||
expect.table_eq(expected, h.search_entries({str='script'},
|
||||
{entry_type='builtin'}))
|
||||
|
||||
expected = {'bindboxers', 'boxbinders'}
|
||||
expect.table_eq(expected, h.search_entries({str='box'}))
|
||||
end
|
||||
|
||||
function test.get_commands()
|
||||
local expected = {'?', 'alias', 'basic', 'bindboxers', 'boxbinders',
|
||||
'clear', 'cls', 'die', 'dir', 'disable', 'devel/dump-rpc', 'enable',
|
||||
'fpause', 'help', 'hide', 'inscript_docs', 'inscript_short_only',
|
||||
'keybinding', 'kill-lua', 'load', 'ls', 'man', 'nodoc_command',
|
||||
'nodocs_samename', 'nodocs_script', 'plug', 'reload', 'samename',
|
||||
'script', 'subdir/scriptname', 'sc-script', 'show', 'tags', 'type',
|
||||
'unload'}
|
||||
table.sort(expected, h.sort_by_basename)
|
||||
expect.table_eq(expected, h.get_commands())
|
||||
end
|
||||
|
||||
function test.is_builtin()
|
||||
expect.true_(h.is_builtin('ls'))
|
||||
expect.false_(h.is_builtin('basic'))
|
||||
expect.false_(h.is_builtin('notanentry'))
|
||||
end
|
||||
|
||||
function test.help()
|
||||
expect.printerr_match('No help entry found', function() h.help('blarg') end)
|
||||
|
||||
local mock_print = mock.func()
|
||||
mock.patch(h, 'print', mock_print, function()
|
||||
h.help('nocommand')
|
||||
expect.eq(1, mock_print.call_count)
|
||||
expect.eq(files['hack/docs/docs/tools/nocommand.txt'],
|
||||
mock_print.call_args[1][1])
|
||||
end)
|
||||
end
|
||||
|
||||
function test.tags()
|
||||
local mock_print = mock.func()
|
||||
mock.patch(h, 'print', mock_print, function()
|
||||
h.tags()
|
||||
expect.eq(7, mock_print.call_count)
|
||||
expect.eq('armok Tools that give you complete control over an aspect of the',
|
||||
mock_print.call_args[1][1])
|
||||
expect.eq(' game or provide access to information that the game',
|
||||
mock_print.call_args[2][1])
|
||||
expect.eq(' intentionally keeps hidden.',
|
||||
mock_print.call_args[3][1])
|
||||
expect.eq('fort Tools that are useful while in fort mode.',
|
||||
mock_print.call_args[4][1])
|
||||
expect.eq('map Tools that interact with the game map.',
|
||||
mock_print.call_args[5][1])
|
||||
expect.eq('nomembers Nothing is tagged with this.',
|
||||
mock_print.call_args[6][1])
|
||||
expect.eq('units Tools that interact with units.',
|
||||
mock_print.call_args[7][1])
|
||||
end)
|
||||
end
|
||||
|
||||
function test.ls()
|
||||
local mock_print = mock.func()
|
||||
mock.patch(h, 'print', mock_print, function()
|
||||
h.ls('doc') -- interpreted as a string
|
||||
expect.eq(5, mock_print.call_count)
|
||||
expect.eq('inscript_docs in-file short description for inscript_docs.',
|
||||
mock_print.call_args[1][1])
|
||||
expect.eq(' tags: map', mock_print.call_args[2][1])
|
||||
expect.eq('nodoc_command cpp description.',
|
||||
mock_print.call_args[3][1])
|
||||
expect.eq('nodocs_samename Nodocs samename.',
|
||||
mock_print.call_args[4][1])
|
||||
expect.eq('nodocs_script No help available.',
|
||||
mock_print.call_args[5][1])
|
||||
end)
|
||||
|
||||
mock_print = mock.func()
|
||||
mock.patch(h, 'print', mock_print, function()
|
||||
h.ls('armok') -- interpreted as a tag
|
||||
expect.eq(6, mock_print.call_count)
|
||||
expect.eq('bindboxers Bind your boxers.',
|
||||
mock_print.call_args[1][1])
|
||||
expect.eq(' tags: armok, fort, units',
|
||||
mock_print.call_args[2][1])
|
||||
expect.eq('boxbinders Box your binders.',
|
||||
mock_print.call_args[3][1])
|
||||
expect.eq(' tags: armok, fort, units',
|
||||
mock_print.call_args[4][1])
|
||||
expect.eq('samename Samename.',
|
||||
mock_print.call_args[5][1])
|
||||
expect.eq(' tags: armok, fort, units',
|
||||
mock_print.call_args[6][1])
|
||||
end)
|
||||
|
||||
mock_print = mock.func()
|
||||
mock.patch(h, 'print', mock_print, function()
|
||||
h.ls('not a match')
|
||||
expect.eq(1, mock_print.call_count)
|
||||
expect.eq('No matches.', mock_print.call_args[1][1])
|
||||
end)
|
||||
end
|
Loading…
Reference in New Issue