given
This commit is contained in:
commit
ef85bb4e4b
58 changed files with 1226 additions and 0 deletions
BIN
libzork/client/client.tar
Normal file
BIN
libzork/client/client.tar
Normal file
Binary file not shown.
27
libzork/client/flake.lock
generated
Normal file
27
libzork/client/flake.lock
generated
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1751984180,
|
||||
"narHash": "sha256-LwWRsENAZJKUdD3SpLluwDmdXY9F45ZEgCb0X+xgOL0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9807714d6944a957c2e036f84b0ff8caf9930bc0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
23
libzork/client/flake.nix
Normal file
23
libzork/client/flake.nix
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
description = "Flake for libzork";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs }: {
|
||||
devShells.x86_64-linux.default = with nixpkgs.legacyPackages.x86_64-linux; mkShell {
|
||||
buildInputs = [
|
||||
gcc
|
||||
cmake
|
||||
yaml-cpp
|
||||
curl
|
||||
nlohmann_json
|
||||
openssl
|
||||
cmake
|
||||
pkg-config
|
||||
];
|
||||
|
||||
};
|
||||
};
|
||||
}
|
||||
23
libzork/libzork/include/libzork/exceptions.hh
Normal file
23
libzork/libzork/include/libzork/exceptions.hh
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace libzork
|
||||
{
|
||||
|
||||
class RunnerQuit : public std::exception
|
||||
{
|
||||
public:
|
||||
RunnerQuit() = default;
|
||||
~RunnerQuit() override = default;
|
||||
};
|
||||
|
||||
class RunnerInterrupt : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
using super_type = std::runtime_error;
|
||||
using super_type::super_type;
|
||||
~RunnerInterrupt() override = default;
|
||||
};
|
||||
|
||||
} // namespace libzork
|
||||
21
libzork/libzork/include/libzork/runner/choice.hh
Normal file
21
libzork/libzork/include/libzork/runner/choice.hh
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <libzork/runner/interactive.hh>
|
||||
|
||||
namespace libzork::runner
|
||||
{
|
||||
|
||||
class ChoiceRunner : public InteractiveRunner
|
||||
{
|
||||
public:
|
||||
using InteractiveRunner::InteractiveRunner;
|
||||
|
||||
~ChoiceRunner() override = default;
|
||||
};
|
||||
|
||||
std::unique_ptr<ChoiceRunner>
|
||||
make_choice_runner(std::unique_ptr<story::Story> story,
|
||||
std::istream& is = std::cin,
|
||||
std::ostream& os = std::cout);
|
||||
|
||||
} // namespace libzork::runner
|
||||
20
libzork/libzork/include/libzork/runner/html.hh
Normal file
20
libzork/libzork/include/libzork/runner/html.hh
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include <libzork/runner/runner.hh>
|
||||
|
||||
namespace libzork::runner
|
||||
{
|
||||
|
||||
class HTMLRunner : public Runner
|
||||
{
|
||||
public:
|
||||
using Runner::Runner;
|
||||
|
||||
~HTMLRunner() override = default;
|
||||
};
|
||||
|
||||
std::unique_ptr<HTMLRunner>
|
||||
make_html_runner(std::unique_ptr<story::Story> story,
|
||||
const fs::path& output_dir);
|
||||
|
||||
} // namespace libzork::runner
|
||||
30
libzork/libzork/include/libzork/runner/interactive.hh
Normal file
30
libzork/libzork/include/libzork/runner/interactive.hh
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <libzork/runner/runner.hh>
|
||||
|
||||
namespace libzork::runner
|
||||
{
|
||||
|
||||
class InteractiveRunner : public Runner
|
||||
{
|
||||
public:
|
||||
using Runner::Runner;
|
||||
|
||||
InteractiveRunner(std::unique_ptr<story::Story> story,
|
||||
std::istream& is = std::cin,
|
||||
std::ostream& os = std::cout);
|
||||
|
||||
~InteractiveRunner() override = default;
|
||||
|
||||
void run() override;
|
||||
|
||||
virtual void print_script() const;
|
||||
virtual void process_input() = 0;
|
||||
|
||||
protected:
|
||||
std::istream& is_;
|
||||
std::ostream& os_;
|
||||
};
|
||||
|
||||
} // namespace libzork::runner
|
||||
21
libzork/libzork/include/libzork/runner/runner.hh
Normal file
21
libzork/libzork/include/libzork/runner/runner.hh
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <libzork/story/story.hh>
|
||||
|
||||
namespace libzork::runner
|
||||
{
|
||||
|
||||
class Runner
|
||||
{
|
||||
public:
|
||||
Runner(std::unique_ptr<story::Story> story);
|
||||
|
||||
virtual ~Runner() = default;
|
||||
|
||||
virtual void run() = 0;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<story::Story> story_;
|
||||
};
|
||||
|
||||
} // namespace libzork::runner
|
||||
27
libzork/libzork/include/libzork/runner/smart.hh
Normal file
27
libzork/libzork/include/libzork/runner/smart.hh
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <libzork/runner/interactive.hh>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace libzork::runner
|
||||
{
|
||||
|
||||
class SmartRunner : public InteractiveRunner
|
||||
{
|
||||
public:
|
||||
using InteractiveRunner::InteractiveRunner;
|
||||
|
||||
~SmartRunner() override = default;
|
||||
|
||||
virtual std::unordered_set<std::string>
|
||||
tokenize(const std::string& str) const = 0;
|
||||
virtual bool has_unmatched_token(
|
||||
const std::unordered_set<std::string>& user_tokens,
|
||||
const std::unordered_set<std::string>& choice_tokens) const = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<SmartRunner> make_smart_runner(
|
||||
std::unique_ptr<story::Story> story, const fs::path& synonyms_path,
|
||||
std::istream& is = std::cin, std::ostream& os = std::cout);
|
||||
|
||||
} // namespace libzork::runner
|
||||
55
libzork/libzork/include/libzork/store/store.hh
Normal file
55
libzork/libzork/include/libzork/store/store.hh
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace libzork::story
|
||||
{
|
||||
|
||||
// Forward declaration because of the recursive dependencies
|
||||
// store.hh -> node.hh -> (action|condition).hh -> store.hh
|
||||
class Node;
|
||||
class Story;
|
||||
|
||||
} // namespace libzork::story
|
||||
|
||||
namespace libzork::store
|
||||
{
|
||||
|
||||
class Store
|
||||
{
|
||||
public:
|
||||
virtual ~Store() = default;
|
||||
|
||||
virtual const story::Node* get_active_node() const = 0;
|
||||
virtual void set_active_node(const story::Node* node) = 0;
|
||||
|
||||
virtual bool has_variable(const std::string& name) const = 0;
|
||||
virtual int get_variable(const std::string& name) const = 0;
|
||||
virtual void set_variable(const std::string& name, int value) = 0;
|
||||
virtual std::map<std::string, int> get_inventory() const = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<Store> make_store();
|
||||
|
||||
class UndoStore
|
||||
{
|
||||
public:
|
||||
virtual ~UndoStore() = default;
|
||||
|
||||
virtual void new_state() = 0;
|
||||
virtual bool undo() = 0;
|
||||
virtual bool redo() = 0;
|
||||
};
|
||||
|
||||
class SaveStore
|
||||
{
|
||||
public:
|
||||
virtual ~SaveStore() = default;
|
||||
|
||||
virtual void save(std::ostream& os) const = 0;
|
||||
virtual void restore(std::istream& is, const story::Story& story) = 0;
|
||||
};
|
||||
|
||||
} // namespace libzork::store
|
||||
36
libzork/libzork/include/libzork/story/node.hh
Normal file
36
libzork/libzork/include/libzork/story/node.hh
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <libzork/vars/action.hh>
|
||||
#include <libzork/vars/condition.hh>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace libzork::story
|
||||
{
|
||||
|
||||
class Node
|
||||
{
|
||||
public:
|
||||
virtual ~Node() = default;
|
||||
|
||||
virtual const std::string& get_name() const = 0;
|
||||
virtual const std::string& get_text() const = 0;
|
||||
|
||||
virtual const Node* get_choice(std::size_t index,
|
||||
bool check_conditions = true) const = 0;
|
||||
virtual std::vector<std::string>
|
||||
list_choices(bool check_conditions = true) const = 0;
|
||||
virtual void add_choice(
|
||||
const Node* other, const std::string& text,
|
||||
std::vector<std::unique_ptr<vars::Condition>> conditions = {},
|
||||
std::vector<std::unique_ptr<vars::Action>> actions = {}) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<Node> make_node(const std::string& name,
|
||||
const fs::path& script_path);
|
||||
|
||||
} // namespace libzork::story
|
||||
26
libzork/libzork/include/libzork/story/story.hh
Normal file
26
libzork/libzork/include/libzork/story/story.hh
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <libzork/story/node.hh>
|
||||
#include <memory>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace libzork::story
|
||||
{
|
||||
|
||||
class Story
|
||||
{
|
||||
public:
|
||||
virtual ~Story() = default;
|
||||
|
||||
virtual const std::string& get_title() const = 0;
|
||||
virtual const Node* get_current() const = 0;
|
||||
virtual void set_current(const Node* node) = 0;
|
||||
virtual const store::Store* get_store() const = 0;
|
||||
virtual std::ostream& display(std::ostream& os) const = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<Story> make_story(const fs::path& path);
|
||||
|
||||
} // namespace libzork::story
|
||||
21
libzork/libzork/include/libzork/vars/action.hh
Normal file
21
libzork/libzork/include/libzork/vars/action.hh
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <libzork/store/store.hh>
|
||||
#include <string>
|
||||
|
||||
namespace libzork::vars
|
||||
{
|
||||
|
||||
class Action
|
||||
{
|
||||
public:
|
||||
virtual ~Action() = default;
|
||||
|
||||
virtual void apply() const = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<Action> make_action(store::Store& store,
|
||||
const std::string& variable,
|
||||
const std::string& action, int value);
|
||||
|
||||
} // namespace libzork::vars
|
||||
23
libzork/libzork/include/libzork/vars/condition.hh
Normal file
23
libzork/libzork/include/libzork/vars/condition.hh
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include <libzork/store/store.hh>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace libzork::vars
|
||||
{
|
||||
|
||||
class Condition
|
||||
{
|
||||
public:
|
||||
virtual bool apply() const = 0;
|
||||
|
||||
virtual ~Condition() = default;
|
||||
};
|
||||
|
||||
std::unique_ptr<Condition> make_condition(const store::Store& store,
|
||||
const std::string& variable,
|
||||
const std::string& comparison,
|
||||
int value);
|
||||
|
||||
} // namespace libzork::vars
|
||||
6
libzork/libzork/src/exceptions.cc
Normal file
6
libzork/libzork/src/exceptions.cc
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#include "exceptions.hh"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace libzork
|
||||
{} // namespace libzork
|
||||
19
libzork/libzork/src/exceptions.hh
Normal file
19
libzork/libzork/src/exceptions.hh
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef EXCEPTIONS_HH
|
||||
#define EXCEPTIONS_HH
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace libzork
|
||||
{
|
||||
class NotImplemented : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
NotImplemented(const char* file = __builtin_FILE(),
|
||||
int line = __builtin_LINE())
|
||||
: std::runtime_error(std::string{ "Unimplemented function: " }
|
||||
+ file + ":" + std::to_string(line))
|
||||
{}
|
||||
};
|
||||
|
||||
} // namespace libzork
|
||||
#endif // !EXCEPTIONS_HH
|
||||
19
libzork/libzork/src/runner/choice.cc
Normal file
19
libzork/libzork/src/runner/choice.cc
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#include <libzork/runner/choice.hh>
|
||||
|
||||
#include "exceptions.hh"
|
||||
#include "runner/choice_impl.hh"
|
||||
|
||||
namespace libzork::runner
|
||||
{
|
||||
|
||||
std::unique_ptr<ChoiceRunner>
|
||||
make_choice_runner(std::unique_ptr<story::Story> story, std::istream& is,
|
||||
std::ostream& os)
|
||||
{
|
||||
(void)story;
|
||||
(void)is;
|
||||
(void)os;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
} // namespace libzork::runner
|
||||
21
libzork/libzork/src/runner/choice_impl.cc
Normal file
21
libzork/libzork/src/runner/choice_impl.cc
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#include "runner/choice_impl.hh"
|
||||
|
||||
#include <libzork/exceptions.hh>
|
||||
#include <sstream>
|
||||
|
||||
#include "exceptions.hh"
|
||||
|
||||
namespace libzork::runner
|
||||
{
|
||||
|
||||
void ChoiceRunnerImpl::print_script() const
|
||||
{
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
void ChoiceRunnerImpl::process_input()
|
||||
{
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
} // namespace libzork::runner
|
||||
19
libzork/libzork/src/runner/choice_impl.hh
Normal file
19
libzork/libzork/src/runner/choice_impl.hh
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef CHOICE_IMPL_HH
|
||||
#define CHOICE_IMPL_HH
|
||||
|
||||
#include <libzork/runner/choice.hh>
|
||||
|
||||
namespace libzork::runner
|
||||
{
|
||||
|
||||
class ChoiceRunnerImpl : public ChoiceRunner
|
||||
{
|
||||
public:
|
||||
~ChoiceRunnerImpl() override = default;
|
||||
|
||||
void print_script() const override;
|
||||
void process_input() override;
|
||||
};
|
||||
|
||||
} // namespace libzork::runner
|
||||
#endif // !CHOICE_IMPL_HH
|
||||
18
libzork/libzork/src/runner/html.cc
Normal file
18
libzork/libzork/src/runner/html.cc
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#include <libzork/runner/html.hh>
|
||||
|
||||
#include "exceptions.hh"
|
||||
#include "runner/html_impl.hh"
|
||||
|
||||
namespace libzork::runner
|
||||
{
|
||||
|
||||
std::unique_ptr<HTMLRunner>
|
||||
make_html_runner(std::unique_ptr<story::Story> story,
|
||||
const fs::path& output_dir)
|
||||
{
|
||||
(void)story;
|
||||
(void)output_dir;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
} // namespace libzork::runner
|
||||
13
libzork/libzork/src/runner/html_impl.cc
Normal file
13
libzork/libzork/src/runner/html_impl.cc
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#include "runner/html_impl.hh"
|
||||
|
||||
#include "exceptions.hh"
|
||||
|
||||
namespace libzork::runner
|
||||
{
|
||||
|
||||
void HTMLRunnerImpl::run()
|
||||
{
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
} // namespace libzork::runner
|
||||
18
libzork/libzork/src/runner/html_impl.hh
Normal file
18
libzork/libzork/src/runner/html_impl.hh
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef HTML_IMPL_HH
|
||||
#define HTML_IMPL_HH
|
||||
|
||||
#include <libzork/runner/html.hh>
|
||||
|
||||
namespace libzork::runner
|
||||
{
|
||||
|
||||
class HTMLRunnerImpl : public HTMLRunner
|
||||
{
|
||||
public:
|
||||
~HTMLRunnerImpl() override = default;
|
||||
|
||||
void run() override;
|
||||
};
|
||||
|
||||
} // namespace libzork::runner
|
||||
#endif // !HTML_IMPL_HH
|
||||
26
libzork/libzork/src/runner/interactive.cc
Normal file
26
libzork/libzork/src/runner/interactive.cc
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#include <libzork/exceptions.hh>
|
||||
#include <libzork/runner/interactive.hh>
|
||||
|
||||
#include "exceptions.hh"
|
||||
|
||||
namespace libzork::runner
|
||||
{
|
||||
|
||||
InteractiveRunner::InteractiveRunner(std::unique_ptr<story::Story> story,
|
||||
std::istream& is, std::ostream& os)
|
||||
: Runner(std::move(story))
|
||||
, is_(is)
|
||||
, os_(os)
|
||||
{}
|
||||
|
||||
void InteractiveRunner::print_script() const
|
||||
{
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
void InteractiveRunner::run()
|
||||
{
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
} // namespace libzork::runner
|
||||
14
libzork/libzork/src/runner/runner.cc
Normal file
14
libzork/libzork/src/runner/runner.cc
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#include <libzork/runner/runner.hh>
|
||||
|
||||
#include "exceptions.hh"
|
||||
|
||||
namespace libzork::runner
|
||||
{
|
||||
|
||||
Runner::Runner(std::unique_ptr<story::Story> story)
|
||||
{
|
||||
(void)story;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
} // namespace libzork::runner
|
||||
21
libzork/libzork/src/runner/smart.cc
Normal file
21
libzork/libzork/src/runner/smart.cc
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#include <libzork/runner/smart.hh>
|
||||
|
||||
#include "exceptions.hh"
|
||||
#include "runner/smart_impl.hh"
|
||||
|
||||
namespace libzork::runner
|
||||
{
|
||||
|
||||
std::unique_ptr<SmartRunner>
|
||||
make_smart_runner(std::unique_ptr<story::Story> story,
|
||||
const fs::path& synonyms_path, std::istream& is,
|
||||
std::ostream& os)
|
||||
{
|
||||
(void)story;
|
||||
(void)synonyms_path;
|
||||
(void)is;
|
||||
(void)os;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
} // namespace libzork::runner
|
||||
29
libzork/libzork/src/runner/smart_impl.cc
Normal file
29
libzork/libzork/src/runner/smart_impl.cc
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#include "runner/smart_impl.hh"
|
||||
|
||||
#include "exceptions.hh"
|
||||
|
||||
namespace libzork::runner
|
||||
{
|
||||
|
||||
void SmartRunnerImpl::process_input()
|
||||
{
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
std::unordered_set<std::string>
|
||||
SmartRunnerImpl::tokenize(const std::string& str) const
|
||||
{
|
||||
(void)str;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
bool SmartRunnerImpl::has_unmatched_token(
|
||||
const std::unordered_set<std::string>& user_tokens,
|
||||
const std::unordered_set<std::string>& choice_tokens) const
|
||||
{
|
||||
(void)user_tokens;
|
||||
(void)choice_tokens;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
} // namespace libzork::runner
|
||||
22
libzork/libzork/src/runner/smart_impl.hh
Normal file
22
libzork/libzork/src/runner/smart_impl.hh
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef SMART_IMPL_HH
|
||||
#define SMART_IMPL_HH
|
||||
|
||||
#include <libzork/runner/smart.hh>
|
||||
|
||||
namespace libzork::runner
|
||||
{
|
||||
|
||||
class SmartRunnerImpl : public SmartRunner
|
||||
{
|
||||
public:
|
||||
void process_input() override;
|
||||
virtual std::unordered_set<std::string>
|
||||
tokenize(const std::string& str) const override;
|
||||
virtual bool
|
||||
has_unmatched_token(const std::unordered_set<std::string>& user_tokens,
|
||||
const std::unordered_set<std::string>&
|
||||
choice_tokens) const override;
|
||||
};
|
||||
|
||||
} // namespace libzork::runner
|
||||
#endif // !SMART_IMPL_HH
|
||||
14
libzork/libzork/src/store/store.cc
Normal file
14
libzork/libzork/src/store/store.cc
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#include <libzork/store/store.hh>
|
||||
|
||||
#include "exceptions.hh"
|
||||
#include "store/store_impl.hh"
|
||||
|
||||
namespace libzork::store
|
||||
{
|
||||
|
||||
std::unique_ptr<Store> make_store()
|
||||
{
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
} // namespace libzork::store
|
||||
43
libzork/libzork/src/store/store_impl.cc
Normal file
43
libzork/libzork/src/store/store_impl.cc
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#include "store/store_impl.hh"
|
||||
|
||||
#include "exceptions.hh"
|
||||
|
||||
namespace libzork::store
|
||||
{
|
||||
|
||||
const story::Node* StoreImpl::get_active_node() const
|
||||
{
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
void StoreImpl::set_active_node(const story::Node* node)
|
||||
{
|
||||
(void)node;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
bool StoreImpl::has_variable(const std::string& name) const
|
||||
{
|
||||
(void)name;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
int StoreImpl::get_variable(const std::string& name) const
|
||||
{
|
||||
(void)name;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
void StoreImpl::set_variable(const std::string& name, int value)
|
||||
{
|
||||
(void)name;
|
||||
(void)value;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
std::map<std::string, int> StoreImpl::get_inventory() const
|
||||
{
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
} // namespace libzork::store
|
||||
24
libzork/libzork/src/store/store_impl.hh
Normal file
24
libzork/libzork/src/store/store_impl.hh
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef STORE_IMPL_HH
|
||||
#define STORE_IMPL_HH
|
||||
|
||||
#include <libzork/store/store.hh>
|
||||
|
||||
namespace libzork::store
|
||||
{
|
||||
|
||||
class StoreImpl : public Store
|
||||
{
|
||||
public:
|
||||
~StoreImpl() override = default;
|
||||
|
||||
const story::Node* get_active_node() const override;
|
||||
void set_active_node(const story::Node* node) override;
|
||||
|
||||
bool has_variable(const std::string& name) const override;
|
||||
int get_variable(const std::string& name) const override;
|
||||
void set_variable(const std::string& name, int value) override;
|
||||
std::map<std::string, int> get_inventory() const override;
|
||||
};
|
||||
|
||||
} // namespace libzork::store
|
||||
#endif // !STORE_IMPL_HH
|
||||
4
libzork/libzork/src/story/choice.cc
Normal file
4
libzork/libzork/src/story/choice.cc
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
#include "story/choice.hh"
|
||||
|
||||
namespace libzork::story
|
||||
{} // namespace libzork::story
|
||||
11
libzork/libzork/src/story/choice.hh
Normal file
11
libzork/libzork/src/story/choice.hh
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#ifndef CHOICE_HH
|
||||
#define CHOICE_HH
|
||||
|
||||
namespace libzork::story
|
||||
{
|
||||
|
||||
class Choice
|
||||
{};
|
||||
|
||||
} // namespace libzork::story
|
||||
#endif // !CHOICE_HH
|
||||
17
libzork/libzork/src/story/node.cc
Normal file
17
libzork/libzork/src/story/node.cc
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#include <libzork/story/node.hh>
|
||||
|
||||
#include "exceptions.hh"
|
||||
#include "story/node_impl.hh"
|
||||
|
||||
namespace libzork::story
|
||||
{
|
||||
|
||||
std::unique_ptr<Node> make_node(const std::string& name,
|
||||
const fs::path& script_path)
|
||||
{
|
||||
(void)name;
|
||||
(void)script_path;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
} // namespace libzork::story
|
||||
56
libzork/libzork/src/story/node_impl.cc
Normal file
56
libzork/libzork/src/story/node_impl.cc
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#include "story/node_impl.hh"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "exceptions.hh"
|
||||
|
||||
namespace libzork::story
|
||||
{
|
||||
const std::string& NodeImpl::get_name() const
|
||||
{
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
const std::string& NodeImpl::get_text() const
|
||||
{
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
const Node* NodeImpl::get_choice(size_t index, bool check_conditions) const
|
||||
{
|
||||
(void)check_conditions;
|
||||
(void)index;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
std::vector<std::string> NodeImpl::list_choices(bool check_conditions) const
|
||||
{
|
||||
(void)check_conditions;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
void NodeImpl::add_choice(
|
||||
const Node* other, const std::string& text,
|
||||
std::vector<std::unique_ptr<vars::Condition>> conditions,
|
||||
std::vector<std::unique_ptr<vars::Action>> actions)
|
||||
{
|
||||
(void)other;
|
||||
(void)text;
|
||||
(void)conditions;
|
||||
(void)actions;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
const NodeImpl& to_impl(const Node& node)
|
||||
{
|
||||
(void)node;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
NodeImpl& to_impl(Node& node)
|
||||
{
|
||||
(void)node;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
} // namespace libzork::story
|
||||
31
libzork/libzork/src/story/node_impl.hh
Normal file
31
libzork/libzork/src/story/node_impl.hh
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef NODE_IMPL_HH
|
||||
#define NODE_IMPL_HH
|
||||
|
||||
#include <libzork/story/node.hh>
|
||||
|
||||
namespace libzork::story
|
||||
{
|
||||
|
||||
class NodeImpl : public Node
|
||||
{
|
||||
public:
|
||||
~NodeImpl() override = default;
|
||||
|
||||
const std::string& get_name() const override;
|
||||
const std::string& get_text() const override;
|
||||
|
||||
const Node* get_choice(std::size_t index,
|
||||
bool check_conditions = true) const override;
|
||||
std::vector<std::string>
|
||||
list_choices(bool check_conditions = true) const override;
|
||||
void add_choice(
|
||||
const Node* other, const std::string& text,
|
||||
std::vector<std::unique_ptr<vars::Condition>> conditions = {},
|
||||
std::vector<std::unique_ptr<vars::Action>> actions = {}) override;
|
||||
};
|
||||
|
||||
const NodeImpl& to_impl(const Node& node);
|
||||
NodeImpl& to_impl(Node& node);
|
||||
|
||||
} // namespace libzork::story
|
||||
#endif // !NODE_IMPL_HH
|
||||
15
libzork/libzork/src/story/story.cc
Normal file
15
libzork/libzork/src/story/story.cc
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#include <libzork/story/story.hh>
|
||||
|
||||
#include "exceptions.hh"
|
||||
#include "story/story_impl.hh"
|
||||
|
||||
namespace libzork::story
|
||||
{
|
||||
|
||||
std::unique_ptr<Story> make_story(const fs::path& path)
|
||||
{
|
||||
(void)path;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
} // namespace libzork::story
|
||||
35
libzork/libzork/src/story/story_impl.cc
Normal file
35
libzork/libzork/src/story/story_impl.cc
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#include "story/story_impl.hh"
|
||||
|
||||
#include "exceptions.hh"
|
||||
|
||||
namespace libzork::story
|
||||
{
|
||||
|
||||
const std::string& StoryImpl::get_title() const
|
||||
{
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
const Node* StoryImpl::get_current() const
|
||||
{
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
void StoryImpl::set_current(const Node* node)
|
||||
{
|
||||
(void)node;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
const store::Store* StoryImpl::get_store() const
|
||||
{
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
std::ostream& StoryImpl::display(std::ostream& os) const
|
||||
{
|
||||
(void)os;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
} // namespace libzork::story
|
||||
25
libzork/libzork/src/story/story_impl.hh
Normal file
25
libzork/libzork/src/story/story_impl.hh
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef STORY_IMPL_HH
|
||||
#define STORY_IMPL_HH
|
||||
|
||||
#include <libzork/story/story.hh>
|
||||
|
||||
namespace libzork::story
|
||||
{
|
||||
|
||||
class StoryImpl : public Story
|
||||
{
|
||||
public:
|
||||
~StoryImpl() override = default;
|
||||
|
||||
const std::string& get_title() const override;
|
||||
const Node* get_current() const override;
|
||||
void set_current(const Node* node) override;
|
||||
const store::Store* get_store() const override;
|
||||
std::ostream& display(std::ostream& os) const override;
|
||||
};
|
||||
|
||||
const StoryImpl& to_impl(const Story& story);
|
||||
StoryImpl& to_impl(Story& story);
|
||||
|
||||
} // namespace libzork::story
|
||||
#endif // !STORY_IMPL_HH
|
||||
20
libzork/libzork/src/vars/action.cc
Normal file
20
libzork/libzork/src/vars/action.cc
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#include <libzork/vars/action.hh>
|
||||
|
||||
#include "exceptions.hh"
|
||||
#include "vars/action_impl.hh"
|
||||
|
||||
namespace libzork::vars
|
||||
{
|
||||
|
||||
std::unique_ptr<Action> make_action(store::Store& store,
|
||||
const std::string& variable,
|
||||
const std::string& action, int value)
|
||||
{
|
||||
(void)store;
|
||||
(void)variable;
|
||||
(void)action;
|
||||
(void)value;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
} // namespace libzork::vars
|
||||
13
libzork/libzork/src/vars/action_impl.cc
Normal file
13
libzork/libzork/src/vars/action_impl.cc
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#include "vars/action_impl.hh"
|
||||
|
||||
#include "exceptions.hh"
|
||||
|
||||
namespace libzork::vars
|
||||
{
|
||||
|
||||
void ActionImpl::apply() const
|
||||
{
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
} // namespace libzork::vars
|
||||
18
libzork/libzork/src/vars/action_impl.hh
Normal file
18
libzork/libzork/src/vars/action_impl.hh
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef ACTION_IMPL_HH
|
||||
#define ACTION_IMPL_HH
|
||||
|
||||
#include <libzork/vars/action.hh>
|
||||
|
||||
namespace libzork::vars
|
||||
{
|
||||
|
||||
class ActionImpl : public Action
|
||||
{
|
||||
public:
|
||||
~ActionImpl() override = default;
|
||||
|
||||
void apply() const override;
|
||||
};
|
||||
|
||||
} // namespace libzork::vars
|
||||
#endif // !ACTION_IMPL_HH
|
||||
21
libzork/libzork/src/vars/condition.cc
Normal file
21
libzork/libzork/src/vars/condition.cc
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#include <libzork/store/store.hh>
|
||||
#include <libzork/vars/condition.hh>
|
||||
|
||||
#include "exceptions.hh"
|
||||
|
||||
namespace libzork::vars
|
||||
{
|
||||
|
||||
std::unique_ptr<Condition> make_condition(const store::Store& store,
|
||||
const std::string& variable,
|
||||
const std::string& comparison,
|
||||
int value)
|
||||
{
|
||||
(void)store;
|
||||
(void)variable;
|
||||
(void)comparison;
|
||||
(void)value;
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
} // namespace libzork::vars
|
||||
16
libzork/libzork/src/vars/condition_impl.cc
Normal file
16
libzork/libzork/src/vars/condition_impl.cc
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#include "vars/condition_impl.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
#include "exceptions.hh"
|
||||
|
||||
namespace libzork::vars
|
||||
{
|
||||
|
||||
bool ConditionImpl::apply() const
|
||||
{
|
||||
throw NotImplemented();
|
||||
}
|
||||
|
||||
} // namespace libzork::vars
|
||||
18
libzork/libzork/src/vars/condition_impl.hh
Normal file
18
libzork/libzork/src/vars/condition_impl.hh
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef CONDITION_IMPL_HH
|
||||
#define CONDITION_IMPL_HH
|
||||
|
||||
#include <libzork/vars/condition.hh>
|
||||
|
||||
namespace libzork::vars
|
||||
{
|
||||
|
||||
class ConditionImpl : public Condition
|
||||
{
|
||||
public:
|
||||
~ConditionImpl() override = default;
|
||||
|
||||
bool apply() const override;
|
||||
};
|
||||
|
||||
} // namespace libzork::vars
|
||||
#endif // !CONDITION_IMPL_HH
|
||||
59
libzork/src/main.cc
Normal file
59
libzork/src/main.cc
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#include <iostream>
|
||||
#include <libzork/exceptions.hh>
|
||||
#include <libzork/runner/choice.hh>
|
||||
#include <libzork/runner/html.hh>
|
||||
#include <libzork/runner/smart.hh>
|
||||
|
||||
#include "options.hh"
|
||||
|
||||
std::unique_ptr<libzork::story::Story> get_story(const Config& config)
|
||||
{
|
||||
return libzork::story::make_story(config.story_path);
|
||||
}
|
||||
|
||||
std::unique_ptr<libzork::runner::Runner>
|
||||
get_runner(const Config& config, std::unique_ptr<libzork::story::Story> story)
|
||||
{
|
||||
switch (config.story_type)
|
||||
{
|
||||
case StoryType::Choice:
|
||||
return libzork::runner::make_choice_runner(std::move(story));
|
||||
case StoryType::Smart:
|
||||
return libzork::runner::make_smart_runner(std::move(story),
|
||||
config.story_arg);
|
||||
case StoryType::HTML:
|
||||
return libzork::runner::make_html_runner(std::move(story),
|
||||
config.story_arg);
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
Config config;
|
||||
try
|
||||
{
|
||||
config = parse_options(argc, argv);
|
||||
}
|
||||
catch (const std::invalid_argument& exc)
|
||||
{
|
||||
std::cerr << "invalid options: " << exc.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::unique_ptr<libzork::runner::Runner> runner;
|
||||
|
||||
auto story = get_story(config);
|
||||
story->display(std::cout);
|
||||
// runner = get_runner(config, std::move(story));
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// runner->run();
|
||||
// }
|
||||
// catch (const libzork::RunnerQuit&)
|
||||
// {}
|
||||
|
||||
return 0;
|
||||
}
|
||||
57
libzork/src/options.cc
Normal file
57
libzork/src/options.cc
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#include "options.hh"
|
||||
|
||||
#include <getopt.h>
|
||||
#include <iostream>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr option options[] = {
|
||||
{ "story", required_argument, nullptr, 's' },
|
||||
{ "smart", required_argument, nullptr, 'm' },
|
||||
{ "html", required_argument, nullptr, 'h' },
|
||||
};
|
||||
|
||||
std::string usage(const std::string& name)
|
||||
{
|
||||
return "usage: " + name
|
||||
+ " (--story <story.yml>)"
|
||||
" [--smart <synonyms.yml> | --html <directory/>]";
|
||||
};
|
||||
} // namespace
|
||||
|
||||
Config parse_options(int argc, char** argv)
|
||||
{
|
||||
Config config;
|
||||
int opt;
|
||||
while ((opt = getopt_long(argc, argv, "s:m:h:t:u:o:n:r:", options, nullptr))
|
||||
!= -1)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
case 's': // --story
|
||||
config.story_path = optarg;
|
||||
break;
|
||||
case 'm': // --smart
|
||||
if (config.story_type == StoryType::HTML)
|
||||
throw std::invalid_argument(
|
||||
"incompatble options: `--smart` and `--html`");
|
||||
config.story_type = StoryType::Smart;
|
||||
config.story_arg = optarg;
|
||||
break;
|
||||
case 'h': // --html
|
||||
if (config.story_type == StoryType::Smart)
|
||||
throw std::invalid_argument(
|
||||
"incompatible options: `--smart` and `--html`");
|
||||
config.story_type = StoryType::HTML;
|
||||
config.story_arg = optarg;
|
||||
break;
|
||||
default:
|
||||
throw std::invalid_argument(usage(argv[0]));
|
||||
}
|
||||
};
|
||||
|
||||
if (config.story_path.empty())
|
||||
throw std::invalid_argument("option '--story' is mandatory");
|
||||
|
||||
return config;
|
||||
}
|
||||
22
libzork/src/options.hh
Normal file
22
libzork/src/options.hh
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
enum class StoryType
|
||||
{
|
||||
Choice,
|
||||
Smart,
|
||||
HTML,
|
||||
};
|
||||
|
||||
struct Config
|
||||
{
|
||||
std::filesystem::path story_path;
|
||||
StoryType story_type = StoryType::Choice;
|
||||
fs::path story_arg; /** Undefined if story_type is StoryType::BASIC */
|
||||
};
|
||||
|
||||
Config parse_options(int argc, char** argv);
|
||||
3
libzork/tests/scripts/little_quest/castle.txt
Normal file
3
libzork/tests/scripts/little_quest/castle.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
You enter the castle.
|
||||
A large dragon blocks your path, preventing you from proceeding further.
|
||||
A sword is on your left.
|
||||
3
libzork/tests/scripts/little_quest/castle_weapon.txt
Normal file
3
libzork/tests/scripts/little_quest/castle_weapon.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
You enter the castle.
|
||||
A large dragon blocks your path, preventing you from proceeding further.
|
||||
To your right, there is a forge. Perhaps there are weapons there ?
|
||||
4
libzork/tests/scripts/little_quest/forest.txt
Normal file
4
libzork/tests/scripts/little_quest/forest.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
You find yourself in a vast, lush forest.
|
||||
In the distance, you spot a prince perched atop a tower of a castle.
|
||||
He appears to be in desperate need of assistance.
|
||||
To your right lies a house.
|
||||
2
libzork/tests/scripts/little_quest/house.txt
Normal file
2
libzork/tests/scripts/little_quest/house.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
There is a bed, worn out from fatigue, you collapse onto it and take a nap.
|
||||
The end.
|
||||
2
libzork/tests/scripts/little_quest/tower.txt
Normal file
2
libzork/tests/scripts/little_quest/tower.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
You slay the dragon, climb the tower, and find the prince safe and sound.
|
||||
The end.
|
||||
1
libzork/tests/scripts/short_static/cave.txt
Normal file
1
libzork/tests/scripts/short_static/cave.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
This is a dark cave
|
||||
1
libzork/tests/scripts/short_static/forest.txt
Normal file
1
libzork/tests/scripts/short_static/forest.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
This is a large forest
|
||||
1
libzork/tests/scripts/short_static/welcome.txt
Normal file
1
libzork/tests/scripts/short_static/welcome.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
Hello, world!
|
||||
51
libzork/tests/stories/dynamic/story.yml
Normal file
51
libzork/tests/stories/dynamic/story.yml
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
title: A dynamic story
|
||||
scripts-path: ../../scripts/little_quest
|
||||
variables:
|
||||
- name: health
|
||||
value: 10
|
||||
- name: sword_taken
|
||||
value: 0
|
||||
story:
|
||||
- name: forest
|
||||
script: forest.txt
|
||||
choices:
|
||||
- text: Enter the house
|
||||
target: house
|
||||
- text: Enter the castle
|
||||
target: castle
|
||||
- text: Eat a fruit
|
||||
target: forest
|
||||
actions:
|
||||
- name: health
|
||||
operation: add
|
||||
value: 10
|
||||
- name: house
|
||||
script: house.txt
|
||||
choices: []
|
||||
- name: castle
|
||||
script: castle_weapon.txt
|
||||
choices:
|
||||
- text: Flee
|
||||
target: forest
|
||||
- text: Take the sword
|
||||
target: castle
|
||||
conditions:
|
||||
- name: sword_taken
|
||||
comparison: equal
|
||||
value: 0
|
||||
actions:
|
||||
- name: sword_taken
|
||||
operation: add
|
||||
value: 1
|
||||
- text: Kill the dragon
|
||||
target: tower
|
||||
conditions:
|
||||
- name: sword_taken
|
||||
comparison: equal
|
||||
value: 1
|
||||
- name: health
|
||||
comparison: greater
|
||||
value: 10
|
||||
- name: tower
|
||||
script: tower.txt
|
||||
choices: []
|
||||
25
libzork/tests/stories/long_static/story.yml
Normal file
25
libzork/tests/stories/long_static/story.yml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
title: A long static story
|
||||
scripts-path: ../../scripts/little_quest
|
||||
story:
|
||||
- name: forest
|
||||
script: forest.txt
|
||||
choices:
|
||||
- text: Enter the house
|
||||
target: house
|
||||
- text: Enter the castle
|
||||
target: castle
|
||||
- text: Eat a fruit
|
||||
target: forest
|
||||
- name: house
|
||||
script: house.txt
|
||||
choices: []
|
||||
- name: castle
|
||||
script: castle.txt
|
||||
choices:
|
||||
- text: Flee
|
||||
target: forest
|
||||
- text: Kill the dragon
|
||||
target: tower
|
||||
- name: tower
|
||||
script: tower.txt
|
||||
choices: []
|
||||
16
libzork/tests/stories/short_static/story.yml
Normal file
16
libzork/tests/stories/short_static/story.yml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
title: A short static story
|
||||
scripts-path: ../../scripts/short_static
|
||||
story:
|
||||
- name: welcome
|
||||
script: welcome.txt
|
||||
choices:
|
||||
- text: Explore the cave
|
||||
target: cave
|
||||
- text: Go in the forest
|
||||
target: forest
|
||||
- name: cave
|
||||
script: cave.txt
|
||||
choices: []
|
||||
- name: forest
|
||||
script: forest.txt
|
||||
choices: []
|
||||
Loading…
Add table
Add a link
Reference in a new issue