diff --git a/.gitignore b/.gitignore
index e743624..f492aaf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,6 @@ w_dev
*.os
*.o
.sconsign.dblite
+site
+*.pyc
+wayward_synth.plugin
diff --git a/.gitmodules b/.gitmodules
index affcc3b..bc19368 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -3,4 +3,4 @@
url = https://github.com/simonask/libevhtp.git
[submodule "3rdparty/synth"]
path = 3rdparty/synth
- url = https://github.com/ajg/synth.git
+ url = https://github.com/simonask/synth.git
diff --git a/3rdparty/synth b/3rdparty/synth
index 7540072..51af35e 160000
--- a/3rdparty/synth
+++ b/3rdparty/synth
@@ -1 +1 @@
-Subproject commit 7540072bde2ea9c8258c2dca69d2ed3bd62fb991
+Subproject commit 51af35e7bf50c069a64b2c8894cb4bd755d3f928
diff --git a/SConstruct b/SConstruct
index eeb7db0..92fff2c 100644
--- a/SConstruct
+++ b/SConstruct
@@ -2,13 +2,15 @@ from wayward_build import *
import os
wayward_support_sources = Split("""
+ wayward/support/any.cpp
+ wayward/support/benchmark.cpp
wayward/support/format.cpp
wayward/support/uri.cpp
- wayward/support/node.cpp
wayward/support/json.cpp
wayward/support/datetime/datetime.cpp
wayward/support/datetime/clock.cpp
wayward/support/datetime/interval.cpp
+ wayward/support/datetime/type.cpp
wayward/support/logger.cpp
wayward/support/error.cpp
wayward/support/command_line_options.cpp
@@ -17,29 +19,98 @@ wayward_support_sources = Split("""
wayward/support/http.cpp
wayward/support/teamwork.cpp
wayward/support/plugin.cpp
- wayward/support/mutable_node.cpp
+ wayward/support/data_franca/spectator.cpp
+ wayward/support/data_franca/mutator.cpp
+ wayward/support/data_franca/object.cpp
+ wayward/support/string.cpp
+ wayward/support/types.cpp
3rdparty/libevhtp/evhtp.c
3rdparty/libevhtp/htparse/htparse.c
3rdparty/libevhtp/evthr/evthr.c
""")
+wayward_support_headers = Split("""
+ wayward/support/uri.hpp
+ wayward/support/types.hpp
+ wayward/support/type_info.hpp
+ wayward/support/type.hpp
+ wayward/support/thread_local.hpp
+ wayward/support/teamwork.hpp
+ wayward/support/string.hpp
+ wayward/support/result.hpp
+ wayward/support/plugin.hpp
+ wayward/support/options.hpp
+ wayward/support/monad.hpp
+ wayward/support/meta.hpp
+ wayward/support/maybe.hpp
+ wayward/support/logger.hpp
+ wayward/support/json.hpp
+ wayward/support/intrusive_list.hpp
+ wayward/support/http.hpp
+ wayward/support/format.hpp
+ wayward/support/fiber.hpp
+ wayward/support/event_loop.hpp
+ wayward/support/error.hpp
+ wayward/support/either.hpp
+ wayward/support/datetime.hpp
+ wayward/support/data_visitor.hpp
+ wayward/support/data_franca.hpp
+ wayward/support/command_line_options.hpp
+ wayward/support/cloning_ptr.hpp
+ wayward/support/bitflags.hpp
+ wayward/support/benchmark.hpp
+ wayward/support/any.hpp
+ wayward/support/data_franca/adapter.hpp
+ wayward/support/data_franca/adapters.hpp
+ wayward/support/data_franca/mutator.hpp
+ wayward/support/data_franca/node.hpp
+ wayward/support/data_franca/object.hpp
+ wayward/support/data_franca/reader.hpp
+ wayward/support/data_franca/spectator.hpp
+ wayward/support/data_franca/types.hpp
+ wayward/support/data_franca/writer.hpp
+ wayward/support/datetime/clock.hpp
+ wayward/support/datetime/datetime.hpp
+ wayward/support/datetime/duration_units.hpp
+ wayward/support/datetime/interval.hpp
+ wayward/support/datetime/timezone.hpp
+ wayward/support/datetime/type.hpp
+ """)
+
wayward_sources = Split("""
wayward/app.cpp
wayward/render.cpp
wayward/log.cpp
wayward/template_engine.cpp
+ wayward/respond_to.cpp
+ wayward/content_type.cpp
+ """)
+
+wayward_headers = Split("""
+ wayward/content_type.hpp
+ wayward/respond_to.hpp
+ wayward/routes.hpp
+ wayward/session.hpp
+ wayward/template_engine.hpp
+ wayward/w.hpp
+ w
""")
w_util_sources = Split("""
w_util/server.cpp
w_util/recompiler.cpp
w_util/main.cpp
+ w_util/init.cpp
""")
wayward_testing_sources = Split("""
wayward/testing/time_machine.cpp
""")
+wayward_testing_headers = Split("""
+ wayward/testing/time_machine.hpp
+ """)
+
persistence_sources = Split("""
persistence/connection_provider.cpp
persistence/connection_retainer.cpp
@@ -47,12 +118,57 @@ persistence_sources = Split("""
persistence/data_store.cpp
persistence/connection_pool.cpp
persistence/p.cpp
- persistence/postgresql.cpp
- persistence/postgresql_renderers.cpp
+ persistence/adapters/postgresql/connection.cpp
+ persistence/adapters/postgresql/renderers.cpp
persistence/primary_key.cpp
persistence/relational_algebra.cpp
- persistence/types.cpp
- persistence/datetime.cpp
+ persistence/insert.cpp
+ persistence/property.cpp
+ persistence/data_as_literal.cpp
+ persistence/projection.cpp
+ persistence/column.cpp
+ persistence/assign_attributes.cpp
+ """)
+
+persistence_headers = Split("""
+ persistence/adapters/postgresql/connection.hpp
+ persistence/adapters/postgresql/renderers.hpp
+ persistence/adapter.hpp
+ persistence/assign_attributes.hpp
+ persistence/association.hpp
+ persistence/ast.hpp
+ persistence/belongs_to.hpp
+ persistence/column.hpp
+ persistence/column_abilities.hpp
+ persistence/column_traits.hpp
+ persistence/connection.hpp
+ persistence/connection_pool.hpp
+ persistence/connection_provider.hpp
+ persistence/connection_retainer.hpp
+ persistence/context.hpp
+ persistence/create.hpp
+ persistence/data_as_literal.hpp
+ persistence/data_store.hpp
+ persistence/datetime.hpp
+ persistence/destroy.hpp
+ persistence/has_many.hpp
+ persistence/has_one.hpp
+ persistence/insert.hpp
+ persistence/p.hpp
+ persistence/persistence_macro.hpp
+ persistence/primary_key.hpp
+ persistence/projection.hpp
+ persistence/projection_as_structured_data.hpp
+ persistence/property.hpp
+ persistence/record.hpp
+ persistence/record_as_structured_data.hpp
+ persistence/record_ptr.hpp
+ persistence/record_type.hpp
+ persistence/record_type_builder.hpp
+ persistence/relational_algebra.hpp
+ persistence/result_set.hpp
+ persistence/validation_errors.hpp
+ p
""")
wayward_synth_sources = Split("""
@@ -61,14 +177,14 @@ wayward_synth_sources = Split("""
env = WaywardEnvironment(DefaultEnvironment())
-wayward_support = WaywardLibrary(env, 'wayward_support', wayward_support_sources)
+wayward_support = WaywardLibrary(env, 'wayward_support', wayward_support_sources, wayward_support_headers)
env_with_ws = env.Clone()
env_with_ws.Append(LIBS = [wayward_support])
-wayward = WaywardLibrary(env_with_ws, 'wayward', wayward_sources)
-wayward_testing = WaywardLibrary(env_with_ws, 'wayward_testing', wayward_testing_sources)
+wayward = WaywardLibrary(env_with_ws, 'wayward', wayward_sources, wayward_headers)
+wayward_testing = WaywardLibrary(env_with_ws, 'wayward_testing', wayward_testing_sources, wayward_headers)
env_for_synth = env.Clone()
-env_for_synth.Append(CPPPATH = Split('3rdparty/synth'))
+env_for_synth.Append(CPPPATH = Split('3rdparty/synth 3rdparty/synth/external/boost'))
wayward_synth = WaywardPlugin(env_for_synth, 'wayward_synth', wayward_synth_sources)
if platform.system() == 'Linux':
@@ -79,13 +195,15 @@ else:
persistence_env.Append(CCFLAGS = Split(libpq_cflags))
persistence_env.Append(LINKFLAGS = Split(libpq_libs))
-persistence = WaywardLibrary(persistence_env, 'persistence', persistence_sources)
+persistence = WaywardLibrary(persistence_env, 'persistence', persistence_sources, persistence_headers)
WaywardAddDefaultLibraries([wayward, persistence, wayward_support])
w_dev = WaywardProgram(env_with_ws, 'w_dev', w_util_sources, rpaths = ['.'])
-Export('env', 'wayward', 'wayward_support', 'persistence', 'wayward_testing', 'WaywardProgram', 'WaywardLibrary', 'WaywardEnvironment')
+env.Alias('install', ['install-bin', 'install-lib', 'install-inc'])
+
+Export('env', 'wayward', 'wayward_support', 'persistence', 'wayward_testing', 'WaywardProgram', 'WaywardInternalProgram', 'WaywardLibrary', 'WaywardEnvironment')
build_tests = ARGUMENTS.get('build_tests', 'yes')
if build_tests == 'yes':
diff --git a/TODO.md b/TODO.md
index cd7cc59..82921dc 100644
--- a/TODO.md
+++ b/TODO.md
@@ -13,8 +13,6 @@ Persistence
Wayward
-------
-- Params de-/restructuralization
-- Dictionary class that returns Maybe for non-existing values.
- Make sure that plugin architecture (and Synth) is working on Linux.
Wayward Support
@@ -24,3 +22,4 @@ Wayward Support
- Path utility functions (maybe a path class).
- A string class that does proper UTF-8 handling.
- A HTML class representing a string that is unescaped HTML.
+- Find better names for everything in data_franca.
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..bef22c6
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,70 @@
+# Wayward Overview
+
+[GitHub](https://github.com/simonask/w)
+
+Did your dad ever tell you: "Writing web apps in C++ is dangerous and stupid! You should know better!"?
+
+Well he's not the boss of you! You can do whatever you want!
+
+Wayward ("``") is a web framework for writing HTTP-aware apps in C++. It uses [libevent](http://libevent.org/) behind
+the scenes to create a compact HTTP server that can act as the backend for your app.
+
+`` is heavily inspired by the Ruby framework Sinatra, and aims to keep things as simple as possible.
+It uses the C++ Standard Template Library extensively.
+
+***WARNING!*** Wayward is as of yet still in early alpha, and should not, I repeat *NOT* be used in production just yet. Several big pieces are missing, and the rest is a semi-explosive mess that is probably unfit for human *and* animal consumption. Don't say you weren't warned. If you're looking for production-quality code, come back in a few releases, I'm sure we'll have something cooking for you.
+
+
+## Getting Started
+
+Wayward is both a library and a command-line utility, named `w_dev`. Wayward apps can exist without `w_dev`, but it does make life for developers a lot easier. `w_dev` creates projects, generates standardized components in projects, and has a built-in development server that automatically rebuilds and restarts your app when you make changes to it, allowing for live "Rails-style" coding.
+
+### Building `w_dev`
+
+1. Make sure dependencies are installed (git, scons, clang, libevent).
+2. `git clone --recursive https://github.com/simonask/w.git`
+3. `cd w`
+4. `scons -j8 w_dev` (This will only build the necessary components for `w_dev`. Running with `-jN` makes scons run tasks in parallel.)
+
+### Creating a Project
+
+1. `cd ~/your/path/to/projects`
+2. `path/to/w_dev new my_project`
+3. Wait for `w_dev` to finish installing dependencies in your project dir.
+4. You now have a project named `my_project` in `~/your/path/to/projects`.
+
+### Building and Running
+
+1. `cd my_project`
+3. `path/to/w_dev server my_project`
+4. Browse to `localhost:3000`. The first request in the lifetime of the project will take some time.
+
+On the first request to the app server, `w_dev` will build your app. This may take about a minute the very first time because it also needs to build Wayward and dependencies, but subsequent requests will only cause a rebuild of the things that have changed in your own project. Normal turnaround for an incremental build should be very short.
+
+
+## Sample "Hello World"
+
+ #include
+
+ int main(int argc, char** argv) {
+ w::App app { argc, argv };
+
+ app.get("/", [](w::Request& req) {
+ return w::render_text("Hello, World!");
+ });
+
+ return app.run();
+ }
+
+## Dependencies
+
+Dependency management in C++ apps is a pain, so `` tries to keep it to a minimum.
+
+- [libevent 2.0.x](http://libevent.org/)
+- [Clang](http://clang.llvm.org/) and [libc++](http://libcxx.llvm.org/) — GCC will be supported in the future, but there are some disagreements between Clang++ and g++ that are holding back that development
+- SCons 2.3.0 or newer.
+- For Persistence: `libpq` (from PostgreSQL).
+
+`` has been tested on the following platforms:
+
+- OS X 10.9.2 with Apple Clang-503.0.40
diff --git a/docs/persistence.md b/docs/persistence.md
new file mode 100644
index 0000000..10f48da
--- /dev/null
+++ b/docs/persistence.md
@@ -0,0 +1 @@
+# Persistence ORM Framework
diff --git a/docs/wayward/advanced_routes.md b/docs/wayward/advanced_routes.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/wayward/overview.md b/docs/wayward/overview.md
new file mode 100644
index 0000000..897e010
--- /dev/null
+++ b/docs/wayward/overview.md
@@ -0,0 +1,37 @@
+# Wayward Web Framework
+
+Wayward lives in the namespace `wayward`, which by default is aliased as `w`. To disable this, compile your app with `WAYWARD_NO_SHORTHAND_NAMESPACE` defined. Every time you see `w::`, it can be substituted for `wayward::`.
+
+The core of a Wayward app is an instance of `App`, which is used to define the routes and bring up a listening HTTP server.
+
+The simplest Wayward app looks like this:
+
+ #include
+
+ int main(int argc, char** argv) {
+ w::App app { argc, argv };
+
+ app.get("/", [](w::Request& req) {
+ return w::render_text("Hello, World!");
+ });
+
+ return app.run();
+ }
+
+A slightly more advanced example could include a parameter in the route definition:
+
+ #include
+
+ int main(int argc, char** argv) {
+ w::App app { argc, argv };
+
+ app.get("/hello/:person", [](w::Request& req) {
+ return w::render_text("Hello, {0}!", req.params["person"]);
+ });
+ }
+
+Going to `/hello/Steve` will then render the text "Hello, Steve!" in the browser.
+
+See the documentation for [Routing](routing.md) for more ways to define routes.
+
+See the [Advanced Routes](advanced_routes.md) for ways to define more complex route handlers.
diff --git a/docs/wayward/request.md b/docs/wayward/request.md
new file mode 100644
index 0000000..7d10f5d
--- /dev/null
+++ b/docs/wayward/request.md
@@ -0,0 +1,9 @@
+# Requests
+
+All route handlers get called with a reference to a `w::Request` object. The `Request` object is a simple structure representing an HTTP request.
+
+See also: [Wayward Support: HTTP Library](support/http.md)
+
+From the perspective of a route handler, the most interesting parts of a request are the headers, the params, the [URI](support/uri.md), and the request body, if one is provided.
+
+Params defined in the route with the `:` syntax will appear in the `params` member, as well as any GET and POST parameters passed from the client.
diff --git a/docs/wayward/response.md b/docs/wayward/response.md
new file mode 100644
index 0000000..d43f5f8
--- /dev/null
+++ b/docs/wayward/response.md
@@ -0,0 +1,23 @@
+# Rendering Responses
+
+Wayward comes with a number of predefined ways to render a quick HTTP response.
+
+## w::not_found
+
+Returns a generic 404 Not Found with Content-Type `text/plain`.
+
+## w::render_text
+
+Invoke: `render_text(format, ...)`
+
+Returns a 200 OK response with Content-Type `text/plain`. The body of the response is the interpolation of the arguments. `w::render_text` treats its arguments equivalent to [`w::format`](support/format.md).
+
+## w::redirect
+
+Invoke: `redirect(new_url, [status])`
+
+Returns a 302 Found response (or a different status code, if `status` is set), with the `Location` header set to `new_url`.
+
+## w::render
+
+See: [Templates](templates.md)
diff --git a/docs/wayward/routing.md b/docs/wayward/routing.md
new file mode 100644
index 0000000..36c6b2a
--- /dev/null
+++ b/docs/wayward/routing.md
@@ -0,0 +1,42 @@
+# Routing
+
+Routes in Wayward are essentially functions that take a HTTP request as input and return a HTTP response.
+
+The basic way to define a route is to simply call app.get/post/put/etc. with the desired path and a function object that can respond to request matching the path.
+
+The `App` class does the heavy lifting in routes, and has the following interface:
+
+
+---
+
+# App
+
+---
+
+[wayward/w.hpp](https://github.com/simonask/w/blob/master/wayward/w.hpp)
+
+## assets
+
+Invoke: `assets(uri_path, filesystem_path)`
+
+Define how and where static assets are located and accessed.
+
+## get, put, patch, post, del, head, options
+
+Invoke: `method(route, handler)`
+
+Handler is a function with the signature `Response(Request&)`.
+
+Route is a slash-separated path. Path elements beginning with `:` will be interpreted as input parameters to the request handler. Example:
+
+ app.post("/foo/:bar", handler)
+
+This route will respond to any path starting with "/foo/", and whatever comes after that will be passed to the handler in `request.params["bar"]`.
+
+The method `del` defines a route that responds to DELETE requests, and is abbreviated to avoid collision with the C++ keyword `delete`.
+
+## run
+
+Invoke: `run()`
+
+Starts listening and serving requests.
diff --git a/docs/wayward/support/any.md b/docs/wayward/support/any.md
new file mode 100644
index 0000000..8593b4c
--- /dev/null
+++ b/docs/wayward/support/any.md
@@ -0,0 +1,84 @@
+# Class: Any
+
+[wayward/support/any.hpp](https://github.com/simonask/w/blob/master/wayward/support/any.hpp)
+
+`Any` can contain any object by value. When an `Any` is constructed, the input value is copied or moved into the `Any` instance's private storage as well as a piece of unique type information.
+
+The underlying type of an `Any` instance can be queried with the method [`is_a()`](#is_a), which returns a boolean. The held value can be accessed with [`get()`](#get), which returns a [`Maybe`](maybe.md). If the held value is not of type `T`, `Nothing` is returned.
+
+The held value can also be accessed by reference by passing a closure to [`when(...)`](#whenclosure), where the closure will only be run if the type matches. The argument to the closure will be a reference to the held value.
+
+The two related classes `AnyRef` and `AnyConstRef` behave identical to `Any`, except they don't own the value they refer to. Instead, they hold a reference and a const reference, respectively. Care must be taken to ensure that the lifetime of the `AnyRef` or `AnyConstRef` does not exceed the lifetime of the object they refer to (as a rule of thumb: use `AnyRef`/`AnyConstRef` in method signatures only, and never store the reference).
+
+---
+
+# Constructors
+
+---
+
+## Any(T&& value)
+
+Construct an `Any` by moving `value`.
+
+## Any(const T& value)
+
+Construct an `Any` by copying `value`.
+
+## Any(NothingType)
+
+Construct an empty `Any`.
+
+## Any()
+
+Identical to `Any(NothingType)`.
+
+## Any(Any&&) and Any(const Any&)
+
+Move or copy an `Any`, if the underlying type supports the respective operation.
+
+If the underlying type does not support moving, the value will be copied instead. If the underlying type does not support copying, and a copy is requested, an exception is thrown.
+
+---
+
+# Methods
+
+---
+
+## is_a
+
+Invoke: `is_a()`
+
+Returns: `bool`
+
+Takes a template parameter `T`. Returns true if the held value has type `T`.
+
+## get
+
+Invoke: `get()`
+
+Returns: [`Maybe`](maybe.md)
+
+Takes a template parameter `T`. Returns a `Maybe` containing the value if the value has type `T`. Otherwise, returns `Nothing`.
+
+## when(closure)
+
+Invoke: `when(closure)`
+
+Returns: `Maybe`, where `U` is the return type of `closure`.
+
+Takes a template parameter `T` and a generic closure/callback. If the held value is of type `T`, the closure is called with a reference to the held value.
+
+Example:
+
+ Any any { (int)123 };
+ any.when([](const int& number) {
+ std::cout << "Any was an int: " << number << std::endl;
+ });
+
+## type_info
+
+Invoke: `type_info()`
+
+Returns: A pointer to a [TypeInfo](reflection.md#typeinfo).
+
+
diff --git a/docs/wayward/support/command_line_options.md b/docs/wayward/support/command_line_options.md
new file mode 100644
index 0000000..7c6dcdb
--- /dev/null
+++ b/docs/wayward/support/command_line_options.md
@@ -0,0 +1,89 @@
+# Class: CommandLineOptions
+
+[wayward/support/command_line_options.hpp](https://github.com/simonask/w/blob/master/wayward/support/command_line_options.hpp)
+
+`CommandLineOptions` is a very simple command line option parser.
+
+Example usage:
+
+ CommandLineOptions cmd;
+
+ cmd.description("Provide option 'foo'.");
+ cmd.option("--foo", "-f", []() { ... });
+
+ cmd.description("Set the number with --number=123 or -n 123.");
+ cmd.option("--number", "-n", [](int64_t) { ... });
+
+ cmd.description("Provide a string with --string=foo or -s foo");
+ cmd.option("--string", "-s", [](std::string) { ... });
+
+ cmd.usage();
+ cmd.parse(argc, argv);
+
+`CommandLineOptions` recognizes the `--help` and `-h` options by default after the call to [`usage()`](#usage), and displays all available options with the associated description (if any). The closures provided with the [`option`](#option) are run when the option is encountered during [`parse`](#parse).
+
+---
+
+# Constructors
+
+## CommandLineOptions()
+
+Empty `CommandLineOptions`.
+
+---
+
+# Methods
+
+---
+
+## program_name
+
+Invoke: `program_name(std::string)`
+
+Set the program name. Defaults to `argv[0]`. If a program name is provided manually, `argv[0]` is interpreted as an option instead of the binary path.
+
+## description
+
+Invoke: `description(std::string)`
+
+Set the description of the next option that is defined. Each successive call to `description` overwrites the previous value.
+
+## option
+
+Invoke: `option(long_form, short_form, callback)` or `option(long_form, callback)`
+
+`callback` is either an `std::function`, or an `std::function`, or an `std::function`. If the callback takes no parameters, the option is assumed to not take a value argument. If the callback takes a string argument, the value argument is passed directly. If the callback takes an integer argument, the value argument is converted to an integer first.
+
+## usage
+
+Invoke: `usage(long_form, short_form)` or `usage(action, long_form, short_form)`
+
+Configures the "usage" display. By default, `CommandLineOptions` interprets the options `--help` and `-h` and calls [`display_usage_and_exit()`](#displayusageandexit).
+
+## unrecognized
+
+Invoke: `unrecognized(callback)`
+
+`callback` is an `std::function`, and it will be invoked for each unrecognized option. By default, `CommandLineOptions` does nothing when encountering an unrecognized option.
+
+## display_usage
+
+Invoke: `display_usage()`
+
+Print usage text to `std::cout`.
+
+## display_usage_and_exit
+
+Invoke: `dispay_usage_and_exit()`
+
+Print usage text to `std::cout` and call `std::exit(1)`.
+
+## parse
+
+Invoke: `parse(argc, argv)`
+
+Returns: `std::vector` (a list of unrecognized command line options)
+
+Parse `argc` arguments of `argv` and execute the relevant actions.
+
+
diff --git a/docs/wayward/support/data_franca.md b/docs/wayward/support/data_franca.md
new file mode 100644
index 0000000..6f6795d
--- /dev/null
+++ b/docs/wayward/support/data_franca.md
@@ -0,0 +1,246 @@
+# Data Franca API
+
+Github: [wayward/support/data_franca](https://github.com/simonask/w/tree/master/wayward/support/data_franca)
+
+Namespace: `wayward::data_franca`
+
+Data Franca is a data observation/mutation library. It provides a single, unified interface to querying and modifying arbitrary data structures.
+
+Possible use cases:
+
+- Serialization
+- Data binding
+- Interfacing with scripting languages, namely template languages
+
+Data Franca is based around 4 core concepts:
+
+- **[Spectators](#spectators):** Query objects providing a read-only interface to any data structure.
+- **[Mutators](#mutators):** Same as spectators, except they can also modify data structures.
+- **[Adapters](#adapters):** Interface classes that bind the spectator/mutator interface to arbitrary data types. Simply put, to be able to inspect or mutate a custom object, an adapter needs to be defined for it.
+- **[Objects](#objects):** Opaque, mutable object structure that always supports all operations. This is handy for building structured data on the fly, e.g. constructing a JSON document. Objects behave like mutators, except they own their data.
+
+---
+
+# Spectators
+
+---
+
+Making a spectator can be constructed from a reference to any object that has an [adapter](#adapters).
+
+ std::map dict = {{"foo", "bar"}, {"baz", "car"}};
+
+ Spectator spec { dict };
+ std::string result;
+ if (spec["foo"] >> result) {
+ std::cout << result; // Will print "bar".
+ }
+
+Scalar values (strings, numbers) are extracted with `operator>>`, which returns a boolean that is true if the value could be successfully extracted to the requested type. Extraction attempts to convert the underlying value to the requested type, so if the underlying type is a string, but an integer or float is requested, it will be interpreted as a string representation of a number. Extraction as string always succeeds, unless the accessed value is a dictionary or list.
+
+Compound values (dictionaries, lists) are accessed with `operator[]`. If called with a string argument, the value is assumed to be a dictionary, and if called with an integer argument, it is assumed to be a list. If the underlying type is not the requested container type (e.g. if a list or scalar is accessed with a string subscript), an empty spectator representing `Nothing` will be returned.
+
+## type
+
+Returns: `DataType`
+
+One of `DataType::Nothing`, `DataType::Boolean`, `DataType::Integer`, `DataType::Real`, `DataType::String`, `DataType::List`, `DataType::Dictionary`.
+
+## is_nothing
+
+Returns: `bool`
+
+True if the represented value is conceptually "nothing" (`Nothing`, `NULL`, etc.).
+
+## operator bool
+
+Returns: `bool`
+
+True if the represented value is not conceptually "nothing". Reverse of [is_nothing](#isnothing).
+
+## operator>>(Boolean&)
+
+Returns: `bool`
+
+Extract boolean value. Returns true if the underlying type is a boolean, or could be converted to a boolean.
+
+## operator>>(Integer&)
+
+Returns: `bool`
+
+Extract integer value. Returns true if the underlying type is an integer, or could be converted to an integer.
+
+## operator>>(Real&)
+
+Returns: `bool`
+
+Extract float value. Returns true if the underlying type is a float, or could be converted to a float.
+
+## operator>>(String&)
+
+Returns: `bool`
+
+Extract string value. Returns true if the underlying type is a string, or could be converted to a string.
+
+## operator[](std::string)
+
+Returns: `Spectator`
+
+Access value as a dictionary. If the value is not a dictionary, or the key does not exist in the dictionary, a spectator representing "nothing" is returned.
+
+## operator[](size_t)
+
+Returns: `Spectator`
+
+Access value as a list. If the value is not a list, or the index is out of bounds, a spectator representing "nothing" is returned.
+
+## has_key
+
+Invoke: `has_key(key)`
+
+Returns: `bool`
+
+True if the underlying value is a dictionary and has key `key`.
+
+## length
+
+Invoke: `length()`
+
+Returns: `size_t`
+
+If the underlying value is a dictionary or list, returns the number of pairs or elements. Otherwise, returns 0.
+
+## begin
+
+Invoke: `begin()`
+
+Returns: An iterator that can be used to enumerate all elements or pairs of the underlying list or dictionary, or "end" if they are empty, or "end" if the underlying value is not a container.
+
+## end
+
+Invoke: `end()`
+
+Returns: An iterator pointing to the conceptual "end" (nothing). May never be dereferenced.
+
+---
+
+# Mutators
+
+---
+
+Mutators are used like [Spectators](#spectators), except they can also modify the underlying data structures.
+
+Example:
+
+ std::map dict = {{"foo", "bar"}, {"baz", "car"}};
+
+ Mutator mut { dict };
+ if (mut["foo"] << "lol") {
+ // dict["foo"] == "lol"
+ }
+
+In contrast to spectators, type coercion is enforced at the adapter level — some adapters allow changing the type of represented values at runtime, while other don't, so the mutator interface can't make the decision. Therefore, setting a value can fail, and the return value of `operator<<` determines if the write was successful.
+
+In addition to the interface provided by [Spectators](#spectators), they implement the following:
+
+## operator<<(NothingType)
+
+Returns: `bool`
+
+Set the represented value to `Nothing`.
+
+## operator<<(Boolean)
+
+Returns: `bool`
+
+Set the represented value to the boolean value.
+
+## operator<<(Integer)
+
+Returns: `bool`
+
+Set the represented value to the integer value.
+
+## operator<<(Real)
+
+Returns: `bool`
+
+Set the represented value to the floating-point value.
+
+## operator<<(String)
+
+Returns: `bool`
+
+Set the represented value to the string value.
+
+## operator[](std::string)
+
+Returns: `Mutator`
+
+Access dictionary value. Some adapters may coerce the underlying type to become a dictionary.
+
+## operator[](size_t)
+
+Returns: `Mutator`
+
+Access list element. Some adapters may coerce the underlying type to become a list.
+
+## erase
+
+Invoke: `erase(std::string)`
+
+Returns: `bool`
+
+Erase the dictionary element. Returns true if the erase was successful.
+
+## push_back
+
+Invoke: `push_back(value)`
+
+Returns: `bool`
+
+Append a value to the end of a list. Some adapters may coerce the underlying type to become a list.
+
+
+---
+
+# Adapters
+
+---
+
+## Implementing custom adapters
+
+To allow inspection of a custom type, implement `Adapter`, where `T` is your custom type.
+
+`Adapter` must implement the interface `IAdapter`, which implies `IReader` and `IWriter`.
+
+Relevant interfaces:
+
+- IAdapter: [wayward/support/data_franca/adapter.hpp](https://github.com/simonask/w/blob/master/wayward/support/data_franca/adapter.hpp)
+- IReader: [wayward/support/data_franca/reader.hpp](https://github.com/simonask/w/blob/master/wayward/support/data_franca/reader.hpp)
+- IWriter: [wayward/support/data_franca/writer.hpp](https://github.com/simonask/w/blob/master/wayward/support/data_franca/writer.hpp)
+
+---
+
+# Objects
+
+---
+
+A Data Franca Object implements the [Mutator](#mutators) interface (including the [Spectator](#spectators) interface), but owns its own data.
+
+Write-operations on objects always convert the internal type of the object to match the corresponding write operation. Therefore, a write operation on an Object never fails.
+
+Example:
+
+ Object o;
+ o["foo"]["bar"] << 123;
+ o["lol"].push_back("baz");
+ o["lol"].push_back("boo");
+
+If `o` in the above were converted to, say, JSON, it would look like this:
+
+ {
+ "foo": {
+ "bar": 123
+ },
+ "lol": ["baz", "boo"]
+ }
diff --git a/docs/wayward/support/datetime.md b/docs/wayward/support/datetime.md
new file mode 100644
index 0000000..5fefa73
--- /dev/null
+++ b/docs/wayward/support/datetime.md
@@ -0,0 +1,29 @@
+# Library: DateTime
+
+[wayward/support/datetime](https://github.com/simonask/w/tree/master/wayward/support/datetime)
+
+TODO: Overview
+
+---
+
+# Time points
+
+---
+
+---
+
+# Intervals
+
+---
+
+---
+
+# Parsing
+
+---
+
+---
+
+# Formatting
+
+---
diff --git a/docs/wayward/support/either.md b/docs/wayward/support/either.md
new file mode 100644
index 0000000..a1f4f7c
--- /dev/null
+++ b/docs/wayward/support/either.md
@@ -0,0 +1,18 @@
+# Class: Either
+
+[wayward/support/either.hpp](https://github.com/simonask/w/blob/master/wayward/support/either.hpp)
+
+TODO: Overview
+
+---
+
+# Constructors
+
+---
+
+---
+
+# Methods
+
+---
+
diff --git a/docs/wayward/support/error.md b/docs/wayward/support/error.md
new file mode 100644
index 0000000..9238180
--- /dev/null
+++ b/docs/wayward/support/error.md
@@ -0,0 +1,35 @@
+# Class: Error
+
+[wayward/support/error.hpp](https://github.com/simonask/w/blob/master/wayward/support/error.hpp)
+
+`Error` is a base class for all exceptions that want to capture a stack trace.
+
+It derives from `std::runtime_error` so as to be compatible with standard C++ error handling.
+
+---
+
+# Constructors
+
+---
+
+## Error(const std::string& message)
+
+Construct an `Error` with a `message`.
+
+---
+
+# Methods
+
+---
+
+## what
+
+Invoke: `what()`
+
+Returns: `const char*` pointing to an error message describing the message.
+
+## backtrace
+
+Invoke: `backtrace()`
+
+Returns: `std::vector` — a list of stack trace entries (demangled where applicable), in descending order from call site to program entry.
diff --git a/docs/wayward/support/fiber.md b/docs/wayward/support/fiber.md
new file mode 100644
index 0000000..e1c98f1
--- /dev/null
+++ b/docs/wayward/support/fiber.md
@@ -0,0 +1,69 @@
+# Class: Fiber
+
+[wayward/support/fiber.hpp](https://github.com/simonask/w/blob/master/wayward/support/fiber.hpp)
+
+Fiber is an implementation of coroutines. It uses `setjmp`/`longjmp` internally to manage execution contexts, but supports exceptions and stack unwinding.
+
+---
+
+# Types
+
+---
+
+## FiberPtr
+
+An opaque pointer with value semantics representing a reference to a fiber/coroutine.
+
+It is currently defined in terms of `std::shared_ptr`. When the last reference to a fiber is destroyed, the fiber is terminated and destroyed as well.
+
+## fiber::Function
+
+Defined as `std::function` — The entry point of a fiber.
+
+## fiber::ErrorHandler
+
+Defined as `std::function` — A callback that will be called if an exception is uncaught and the stack unwinds beyond a fiber boundary.
+
+---
+
+# Functions
+
+---
+
+## fiber::current
+
+Returns: A `FiberPtr` representing the currently running fiber. If a thread has not launched any fibers yet, a special `FiberPtr` representing the thread is returned. There is no difference between this and regular fibers, except all threads including the main thread always have an implicit reference to the main thread fiber, so it never goes out of scope before the thread finishes.
+
+## fiber::create
+
+Invoke: `fiber::create(Function)` or `fiber::create(Function, ErrorHandler)`
+
+Create a new fiber (optionally with an error handler) without starting it.
+
+If an [`ErrorHandler`](#fiberErrorHandler) is provided, it will be called if the fiber throws an exception that remains uncaught at the fiber boundary. The default error handler terminates the program (similar to how an uncaught exception outside of fibers behave).
+
+## fiber::resume
+
+Invoke: `fiber::resume(FiberPtr)`
+
+Start or resume a fiber.
+
+## fiber::create
+
+Invoke: `fiber::start(Function)`
+
+Equivalent to [`fiber::create`](#fibercreate) + [`fiber::resume`](#fiberresume)
+
+## fiber::terminate
+
+Invoke: `fiber::terminate(FiberPtr)`
+
+Terminates a fiber, unwinding its stack and running all destructors.
+
+Internally, this resumes the fiber with a signal that tells the fiber to throw a special exception handled silently at the fiber boundary before termination.
+
+## fiber::yield
+
+Invoke: `fiber::yield()`
+
+Resumes the fiber that resumed the current fiber. Throws an exception if the current fiber is orphaned (i.e., was never resumed by anybody else, i.e. is most likely the main fiber).
diff --git a/docs/wayward/support/format.md b/docs/wayward/support/format.md
new file mode 100644
index 0000000..46af952
--- /dev/null
+++ b/docs/wayward/support/format.md
@@ -0,0 +1,32 @@
+# Library: format
+
+[wayward/support/format.hpp](https://github.com/simonask/w/blob/master/wayward/support/format.hpp)
+
+`w::format` is the main entry point for string interpolation in Wayward. It has two main modes of operation: Numbered and Named interpolation.
+
+`w::format` uses the standard `operator<<(std::ostream&)` interface internally to convert arguments to strings. This may or may not change in the future.
+
+# Numbered Interpolation
+
+This performs positional interpolation, and expects string placeholders in the format *{n}*, where *n* is a number starting at 0.
+
+Example:
+
+ w::format("Hello, {0}!", "World"); // => "Hello, World!"
+
+Example with numbers:
+
+ w::format("{0} + {1} = {2}", 123, 456, 579); // => "123 + 456 = 579"
+
+# Named Interpolation
+
+Instead of positional arguments, `w::format` also supports named arguments, like so:
+
+ w::format("Hello, {entity}! I am {subject}.", {
+ {"entity", "World"},
+ {"subject", "Simon"}
+ }); // => "Hello, World! I am Simon."
+
+# Output formatting
+
+TODO (NIY)
diff --git a/docs/wayward/support/http.md b/docs/wayward/support/http.md
new file mode 100644
index 0000000..8fbcd30
--- /dev/null
+++ b/docs/wayward/support/http.md
@@ -0,0 +1,5 @@
+# Library: HTTP
+
+# HTTPServer
+
+# HTTPClient
diff --git a/docs/wayward/support/json.md b/docs/wayward/support/json.md
new file mode 100644
index 0000000..ad799c8
--- /dev/null
+++ b/docs/wayward/support/json.md
@@ -0,0 +1,3 @@
+# Library: JSON
+
+TODO
diff --git a/docs/wayward/support/logger.md b/docs/wayward/support/logger.md
new file mode 100644
index 0000000..5797170
--- /dev/null
+++ b/docs/wayward/support/logger.md
@@ -0,0 +1,3 @@
+# Library: Logger
+
+TODO
diff --git a/docs/wayward/support/maybe.md b/docs/wayward/support/maybe.md
new file mode 100644
index 0000000..4efce64
--- /dev/null
+++ b/docs/wayward/support/maybe.md
@@ -0,0 +1,83 @@
+# Class: Maybe
+
+`Maybe` is an option type that either holds a value of type `T` or `Nothing`.
+
+The contents of a `Maybe` can be accessed either with an "unsafe" API equivalent to `boost::optional` (or `std::optional` from C++14), or with a safer "monadic" interface that prevents errors through the type system.
+
+If the contents of a `Maybe` are accessed when the `Maybe` contains `Nothing`, an exception is thrown of type `EmptyMaybeDereference`.
+
+`Maybe` is specialized to support reference-like `Maybe`s, in which case they behave exactly like `Maybe>`.
+
+`Maybe` is also specialized for all ["pointer-like" types](static.md#ispointerlike), so a `Maybe>` takes exactly the same memory as an `std::unique_ptr`, and treats the NULL pointer as the "Nothing" value.
+
+---
+
+# Constructors
+
+---
+
+## Maybe(T value)
+
+Construct a `Maybe` containing `value`.
+
+## Maybe(NothingType)
+
+Construct an empty `Maybe`.
+
+## Maybe(const Maybe&)
+
+Copy-constructor.
+
+## Maybe(Maybe&&)
+
+Move-constructor.
+
+---
+
+# Methods
+
+---
+
+## get
+
+Returns a point to the object. WARNING: Throws exception if the Maybe is empty.
+
+## operator bool
+
+Returns true if the Maybe has an object, otherwise false.
+
+## operator->
+
+Access the object as a pointer. WARNING: Throws exception if the Maybe is empty.
+
+## operator*
+
+Access the object by dereferencing. WARNING: Throws exception if the Maybe is empty.
+
+## swap(Maybe&)
+
+Swaps the contents of this Maybe with another of the same type.
+
+---
+
+Functions
+
+---
+
+## Just
+
+Invoke: `Just(x)`
+
+Returns: `Maybe`
+
+Convenience function for constructing `Maybe` on the go without typing out the full type name for the constructor.
+
+## monad::fmap
+
+Invoke: `fmap(maybe, closure)`
+
+Returns: `Maybe`, where `U` is the return type of `closure`.
+
+The safer way to access the internals of a `Maybe` — the closure will be invoked when the `Maybe` contains a value, and the return value is another `Maybe` with the return value of the closure.
+
+See also: [Monads/fmap](monads.md#monadfmap)
diff --git a/docs/wayward/support/monads.md b/docs/wayward/support/monads.md
new file mode 100644
index 0000000..52cd669
--- /dev/null
+++ b/docs/wayward/support/monads.md
@@ -0,0 +1,3 @@
+# Monads
+
+TODO
diff --git a/docs/wayward/support/overview.md b/docs/wayward/support/overview.md
new file mode 100644
index 0000000..61f0f83
--- /dev/null
+++ b/docs/wayward/support/overview.md
@@ -0,0 +1,29 @@
+# Wayward Support API
+
+Wayward Support is a collection of classes and functions designed to work in addition to the C++ Standard Library.
+
+Some classes in Wayward Support are simpler implementations of equivalents in Boost without as much template cruft, while implement concepts that are unique to Wayward.
+
+## Classes
+
+- [Any](any.md) — Hold any value.
+- [CommandLineOptions](command_line_options.md) — Simple command line option parser.
+- [Either](either.md) — Hold one of a predefined set of value types (tagged union).
+- [Error](error.md) — Base class for all exceptions that captures a stack trace.
+- [Fiber](fiber.md) — Coroutine implementation for cooperative multitasking.
+- [Logger](logger.md) — Logging interface.
+- [Maybe](maybe.md) — Option type, equivalent to `boost::optional` with some monadic extensions.
+- [Result](result.md) — Either a value or an Error.
+- [URI](uri.md) — Structured representation of a Uniform Resource Identifier.
+
+## Libraries
+
+- [Data Franca library](data_franca.md) — Data observation library. Provides unified access to any data type that implements an adapter, allowing users to query arbitrary data structures. The main use is to provide a way for templating languages to access structured data at runtime.
+- [DateTime library](datetime.md) — The DateTime library provides an intuitive interface to POSIX date/time functions.
+- [Format library](format.md) — String formatting and interpolation library.
+- [HTTP library](http.md) — Evented HTTP Server and Client classes.
+- [JSON library](json.md) — Convert arbitrary data structures (via [reflection](reflection.md) or [Data Franca](data_franca.md)) to and from JSON.
+- [Monads](monads.md) — Common "monadic" idioms that can make some code patterns safer and easier to use.
+- [Reflection library](reflection.md) — Runtime type reflection library.
+- [Static Metaprogramming library](static.md) — Compile-time type reflection library (various extensions to the standard ``).
+- [String utilities](string.md) — Commonly used string functions missing from the C++ Standard Library.
diff --git a/docs/wayward/support/reflection.md b/docs/wayward/support/reflection.md
new file mode 100644
index 0000000..f94882f
--- /dev/null
+++ b/docs/wayward/support/reflection.md
@@ -0,0 +1,3 @@
+# Reflection
+
+TODO
diff --git a/docs/wayward/support/result.md b/docs/wayward/support/result.md
new file mode 100644
index 0000000..858ff08
--- /dev/null
+++ b/docs/wayward/support/result.md
@@ -0,0 +1,3 @@
+# Class: Result
+
+TODO
diff --git a/docs/wayward/support/static.md b/docs/wayward/support/static.md
new file mode 100644
index 0000000..7af0a93
--- /dev/null
+++ b/docs/wayward/support/static.md
@@ -0,0 +1,84 @@
+# Static Metaprogramming
+
+[wayward/support/meta.hpp](https://github.com/simonask/w/blob/master/wayward/support/meta.hpp)
+
+These are mainly supplements to the type traits supplied by the standard `` header. Admittedly, some of them are slightly idiosynchratic (I prefer CamelCased types, while the Standard Library always goes for snake\_case). Nevertheless, these utilities are used throughout Wayward Support, and some of them are generally handy.
+
+# Types
+
+## TrueType, FalseType
+
+Compile-time true/false.
+
+## TypeList
+
+Use: `TypeList<...>`
+
+A list of types.
+
+# Traits
+
+## IsPointerLike
+
+Use: `IsPointerLike::Value`
+
+True if `T` is a pointer-like type, i.e. if it is dereferenceable and nullable. Defaults to `std::is_pointer::value`, but specialized by default for `std::unique_ptr` and `std::shared_ptr` (as well as `CloningPtr`). This trait can be used to specialize container types that want to treat pointer-like types specially.
+
+
+## Contains
+
+Use: `Contains>::Value`
+
+True if `TypeList<...>` contains `T`.
+
+## IndexOf
+
+Use: `IndexOf>::Value`
+
+The index of `T` in `TypeList<...>`, or `SIZE_T_MAX` if it doesn't exist.
+
+## RemoveConstRef
+
+Use: `typedef RemoveConstRef::Type`
+
+Input | Output
+--------:|:------
+T | T
+T& | T
+const T& | T
+
+## MaxSize
+
+Use: `MaxSize>::Value`
+
+`Value` is the maximum size of all the types in the `TypeList<...>`.
+
+## MaxAlignment
+
+Use: `MaxAlignment>::Value`
+
+`Value` is the maximum alignment of all the types in the `TypeList<...>`.
+
+## AreAllCopyConstructible
+
+Use: `AreAllCopyConstructible>::Value`
+
+True if all types in the `TypeList<...>` are copy-constructible.
+
+## AreAllCopyAssignable
+
+Use: `AreAllCopyAssignable>::Value`
+
+True if all types in the `TypeList<...>` are copy-assignable.
+
+## AreAllMoveConstructible
+
+Use: `AreAllMoveConstructible>::Value`
+
+True if all types in the `TypeList<...>` are move-constructible.
+
+## AreAllMoveAssignable
+
+Use: `AreAllMoveAssignable>::Value`
+
+True if all types in the `TypeList<...>` are move-assignable.
diff --git a/docs/wayward/support/string.md b/docs/wayward/support/string.md
new file mode 100644
index 0000000..8595d50
--- /dev/null
+++ b/docs/wayward/support/string.md
@@ -0,0 +1,17 @@
+# Library: String utilities
+
+[wayward/support/string.hpp](https://github.com/simonask/w/blob/master/wayward/support/string.hpp)
+
+---
+
+# Functions
+
+---
+
+## split
+
+Use: `split(input, delimiter)` or `split(input, delimiter, max)`
+
+Returns: `std::vector`
+
+Splits the input string by delimiter and returns a vector of string, not including the delimiters. If `max` is provided, a maximum of `max` strings are returned.
diff --git a/docs/wayward/support/uri.md b/docs/wayward/support/uri.md
new file mode 100644
index 0000000..1c77aff
--- /dev/null
+++ b/docs/wayward/support/uri.md
@@ -0,0 +1,100 @@
+# Class: URI
+
+[wayward/support/uri.hpp](https://github.com/simonask/w/blob/master/wayward/support/uri.hpp)
+
+`URI` represents a [Uniform Resource Identifier](http://en.wikipedia.org/wiki/Uniform_resource_identifier).
+
+---
+
+# Constructors
+
+---
+
+## URI()
+
+An empty URI.
+
+## URI(scheme, host, port, path, query, fragment)
+
+An URI representing `scheme://host:port/path?query#fragment`. Each component, except for host and scheme, may be empty.
+
+## URI::parse
+
+Invoke: `URI::parse(input)`
+
+Returns: `Maybe`
+
+Parses the `input` string and returns a `Maybe`. If the input string failed to parse as an URI, the return value is `Nothing`.
+
+---
+
+# Members
+
+---
+
+## scheme
+
+std::string
+
+## username
+
+std::string
+
+## password
+
+std::string
+
+## host
+
+std::string
+
+## port
+
+int
+
+## path
+
+std::string
+
+## query
+
+std::string
+
+## fragment
+
+std::string
+
+
+---
+
+# Methods
+
+---
+
+## to_string
+
+Use: `to_string()`
+
+Returns: `std::string` — String representation of the URI.
+
+---
+
+# Static Methods
+
+---
+
+## URI::decode
+
+Use: `URI::decode(input)`
+
+Returns: `std::string` with UTF-8 encoded characters.
+
+Decodes URI entities.
+
+## URI::encode
+
+Use: `URI::encode(utf8)`
+
+Returns: `std::string` with URI-encoded characters.
+
+Encodes URI entities.
diff --git a/docs/wayward/templates.md b/docs/wayward/templates.md
new file mode 100644
index 0000000..e69de29
diff --git a/examples/blog/blog.cpp b/examples/blog/blog.cpp
index 4ff2c35..7326617 100644
--- a/examples/blog/blog.cpp
+++ b/examples/blog/blog.cpp
@@ -6,6 +6,12 @@
namespace app {
using p::RecordPtr;
+ using p::ValidationErrors;
+ using w::redirect;
+ using w::render;
+ using w::format;
+ using w::HTML;
+ using w::JSON;
struct PostsRoutes : w::Routes {
w::Response get_all_posts(w::Request& req) {
@@ -25,36 +31,53 @@ namespace app {
void before(w::Request& req) override {
int64_t id;
if (req.params["post_id"] >> id) {
- post = from().where(p::column(&Post::id) == id).first();
+ post = from().where(p::eq(id, &Post::id)).inner_join(&Post::author).first();
}
if (!post)
throw w::not_found();
}
w::Response get_post(w::Request& req) {
- return w::render("post.html", {{"post", post}});
+ return w::respond_to(req)
+ .when([&]() {
+ auto comments = post->comments.scope().left_outer_join(&Comment::author).order(&Comment::created_at);
+ return w::render("post.html", {{"post", post}, {"comments", comments}});
+ })
+ .when([&]() {
+ return w::render_json(post);
+ });
}
w::Response put_post(w::Request& req) {
p::assign_attributes(post, req.params["post"]);
p::save(post);
- return w::redirect(w::format("/posts/{0}", post->id));
+ return w::respond_to(req)
+ .when([&]() { return w::redirect(w::format("/posts/{0}", post->id)); })
+ .when([&]() { return w::render_json(post); });
}
w::Response delete_post(w::Request& req) {
- p::destroy(post);
+ // destroy(post);
return w::redirect(w::format("/posts"));
}
w::Response get_comments(w::Request& req) {
- return w::render("comments.html", {{"post", post}});
+ auto comments = post->comments.scope().left_outer_join(&Comment::author);
+ return w::render("comments.html", {{"post", post}, {"comments", comments}});
}
w::Response post_comment(w::Request& req) {
- auto comment = create(req["comment"]);
+ auto comment = create(req.params["comment"]);
comment->post = post;
- p::save(comment);
- return w::redirect(w::format("/posts/{0}", post->id);
+
+ auto r = p::save(comment);
+ if (r) {
+ return redirect(format("/posts/{0}", post->id));
+ } else {
+ throw *r.error();
+ //session.flash["error"] = r.error();
+ return redirect(format("/posts/{0}/comments", post->id));
+ }
}
};
@@ -65,7 +88,7 @@ namespace app {
PostRoutes::before(req);
int64_t id;
if (req.params["comment_id"] >> id) {
- comment = from().where(p::column(&Comment::post) == post->id && p::column(&Comment::id) == id).first();
+ comment = from().where(p::eq(&Comment::post, post->id) && p::eq(id, &Comment::id)).first();
}
}
diff --git a/examples/blog/models.hpp b/examples/blog/models.hpp
index 46f5e25..8c71841 100644
--- a/examples/blog/models.hpp
+++ b/examples/blog/models.hpp
@@ -48,8 +48,8 @@ PERSISTENCE(User) {
property(&User::password_salt, "password_salt");
property(&User::name, "name");
- has_many(&User::posts, "author_id");
- has_many(&User::comments, "author_id");
+ has_many(&User::posts, "posts", "author_id");
+ has_many(&User::comments, "comments", "author_id");
}
PERSISTENCE(Post) {
@@ -60,8 +60,8 @@ PERSISTENCE(Post) {
property(&Post::title, "title");
property(&Post::text, "text");
- belongs_to(&Post::author, "author_id");
- has_many(&Post::comments, "author_id");
+ belongs_to(&Post::author, "author");
+ has_many(&Post::comments, "comments", "post_id");
}
PERSISTENCE(Comment) {
@@ -70,6 +70,6 @@ PERSISTENCE(Comment) {
property(&Comment::updated_at, "updated_at");
property(&Comment::text, "text");
- belongs_to(&Comment::author, "author_id");
- belongs_to(&Comment::post, "post_id");
+ belongs_to(&Comment::author, "author");
+ belongs_to(&Comment::post, "post");
}
diff --git a/examples/blog/views/comments.html b/examples/blog/views/comments.html
new file mode 100644
index 0000000..a99949d
--- /dev/null
+++ b/examples/blog/views/comments.html
@@ -0,0 +1,10 @@
+{% extends "views/layout.html" %}
+{% block content %}
+
+{% for comment in comments %}
+
+{% endfor %}
+{% endblock %}
diff --git a/examples/blog/views/post.html b/examples/blog/views/post.html
index aa8f3c1..4ed1467 100644
--- a/examples/blog/views/post.html
+++ b/examples/blog/views/post.html
@@ -4,5 +4,20 @@
Back to posts
{{ post.title }}
+Author: {{ post.author.name }} ({{ post.author.email }})
{{ post.text }}
+
+Comments
+{% for comment in comments %}
+
+{% endfor %}
+
+
{% endblock %}
diff --git a/examples/blog/views/test.html b/examples/blog/views/test.html
index a25b749..f060212 100644
--- a/examples/blog/views/test.html
+++ b/examples/blog/views/test.html
@@ -1 +1,3 @@
-Synth message: {{ message }}
+Synth message: {{ message }}
+
+{{ params }}
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000..9ef222d
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,42 @@
+site_name: Wayward Documentation
+site_description: Wayward Web Framework documentation
+
+repo_url: https://github.com/simonask/w
+
+pages:
+# Base:
+- [index.md, "Overview"]
+
+
+# Wayward API:
+- ["wayward/overview.md", "Wayward API", "Overview"]
+- ["wayward/routing.md", "Wayward API", "Routing"]
+- ["wayward/request.md", "Wayward API", "Requests"]
+- ["wayward/response.md", "Wayward API", "Rendering Responses"]
+- ["wayward/advanced_routes.md", "Wayward API", "Advanced Routes"]
+- ["wayward/templates.md", "Wayward API", "Templates"]
+
+# Wayward Support API:
+- ["wayward/support/overview.md", "Wayward Support API", "Overview"]
+- ["wayward/support/any.md", "Wayward Support API", "Any"]
+- ["wayward/support/command_line_options.md", "Wayward Support API", "CommandLineOptions"]
+- ["wayward/support/data_franca.md", "Wayward Support API", "Data Franca"]
+- ["wayward/support/datetime.md", "Wayward Support API", "DateTime"]
+- ["wayward/support/either.md", "Wayward Support API", "Either"]
+- ["wayward/support/error.md", "Wayward Support API", "Error"]
+- ["wayward/support/fiber.md", "Wayward Support API", "Fiber"]
+- ["wayward/support/format.md", "Wayward Support API", "Format"]
+- ["wayward/support/http.md", "Wayward Support API", "HTTP"]
+- ["wayward/support/json.md", "Wayward Support API", "JSON"]
+- ["wayward/support/logger.md", "Wayward Support API", "Logger"]
+- ["wayward/support/maybe.md", "Wayward Support API", "Maybe"]
+- ["wayward/support/monads.md", "Wayward Support API", "Monads"]
+- ["wayward/support/reflection.md", "Wayward Support API", "Reflection"]
+- ["wayward/support/result.md", "Wayward Support API", "Result"]
+- ["wayward/support/static.md", "Wayward Support API", "Static Metaprogramming"]
+- ["wayward/support/string.md", "Wayward Support API", "String utilities"]
+- ["wayward/support/uri.md", "Wayward Support API", "URI"]
+
+# Persistence API:
+- [persistence.md, "Persistence API"]
+theme: flatly
diff --git a/persistence/postgresql.cpp b/persistence/adapters/postgresql/connection.cpp
similarity index 91%
rename from persistence/postgresql.cpp
rename to persistence/adapters/postgresql/connection.cpp
index d16c38d..87a95fd 100644
--- a/persistence/postgresql.cpp
+++ b/persistence/adapters/postgresql/connection.cpp
@@ -1,9 +1,11 @@
-#include
-#include
+#include "persistence/adapters/postgresql/connection.hpp"
+#include "persistence/adapters/postgresql/renderers.hpp"
#include
+#include
#include
#include
+#include
#include
#include
@@ -40,9 +42,14 @@ namespace persistence {
return PQgetisnull(result, row, idx);
}
- std::string get(size_t row, const std::string& col) const final {
+ Maybe get(size_t row, const std::string& col) const final {
auto idx = PQfnumber(result, col.c_str());
- return std::string{PQgetvalue(result, row, idx)};
+ bool is_null = PQgetisnull(result, row, idx);
+ if (!is_null) {
+ return std::string{PQgetvalue(result, row, idx)};
+ } else {
+ return wayward::Nothing;
+ }
}
std::vector columns() const final {
diff --git a/persistence/postgresql.hpp b/persistence/adapters/postgresql/connection.hpp
similarity index 88%
rename from persistence/postgresql.hpp
rename to persistence/adapters/postgresql/connection.hpp
index 376faf0..0094b60 100644
--- a/persistence/postgresql.hpp
+++ b/persistence/adapters/postgresql/connection.hpp
@@ -1,6 +1,6 @@
#pragma once
-#ifndef PERSISTENCE_POSTGRESQL_HPP_INCLUDED
-#define PERSISTENCE_POSTGRESQL_HPP_INCLUDED
+#ifndef PERSISTENCE_ADAPTERS_POSTGRESQL_CONNECTION_HPP_INCLUDED
+#define PERSISTENCE_ADAPTERS_POSTGRESQL_CONNECTION_HPP_INCLUDED
#include
#include
@@ -36,7 +36,6 @@ namespace persistence {
std::unique_ptr execute(std::string sql) final;
std::unique_ptr execute(const ast::IQuery& query, const relational_algebra::IResolveSymbolicRelation&) final;
- //
static std::unique_ptr
connect(std::string connection_string, std::string* out_error = nullptr);
private:
@@ -46,4 +45,4 @@ namespace persistence {
};
}
-#endif // PERSISTENCE_POSTGRESQL_HPP_INCLUDED
+#endif // PERSISTENCE_ADAPTERS_POSTGRESQL_CONNECTION_HPP_INCLUDED
diff --git a/persistence/postgresql_renderers.cpp b/persistence/adapters/postgresql/renderers.cpp
similarity index 88%
rename from persistence/postgresql_renderers.cpp
rename to persistence/adapters/postgresql/renderers.cpp
index 224a1af..d12379d 100644
--- a/persistence/postgresql_renderers.cpp
+++ b/persistence/adapters/postgresql/renderers.cpp
@@ -1,4 +1,4 @@
-#include
+#include "persistence/adapters/postgresql/renderers.hpp"
#include
#include
@@ -77,7 +77,38 @@ namespace persistence {
}
std::string PostgreSQLQueryRenderer::render(const InsertQuery& x) {
- return "NIY";
+ std::stringstream ss;
+ ss << "INSERT INTO " << x.relation << " (";
+ for (auto it = x.columns.begin(); it != x.columns.end();) {
+ ss << "\"" << *it << "\"";
+ ++it;
+ if (it != x.columns.end()) {
+ ss << ", ";
+ }
+ }
+ ss << ") VALUES (";
+ PostgreSQLValueRenderer vr { conn, symbolic_relation_resolver };
+ for (auto it = x.values.begin(); it != x.values.end();) {
+ ss << (*it)->to_sql(vr);
+ ++it;
+ if (it != x.values.end()) {
+ ss << ", ";
+ }
+ }
+ ss << ")";
+
+ if (x.returning_columns.size()) {
+ ss << " RETURNING ";
+ for (auto it = x.returning_columns.begin(); it != x.returning_columns.end();) {
+ ss << "\"" << *it << "\"";
+ ++it;
+ if (it != x.returning_columns.end()) {
+ ss << ", ";
+ }
+ }
+ }
+
+ return ss.str();
}
std::string PostgreSQLValueRenderer::render(const StarFrom& x) {
diff --git a/persistence/postgresql_renderers.hpp b/persistence/adapters/postgresql/renderers.hpp
similarity index 89%
rename from persistence/postgresql_renderers.hpp
rename to persistence/adapters/postgresql/renderers.hpp
index 2c95f10..472b037 100644
--- a/persistence/postgresql_renderers.hpp
+++ b/persistence/adapters/postgresql/renderers.hpp
@@ -1,10 +1,9 @@
#pragma once
-#ifndef PERSISTENCE_POSTGRESQL_RENDERERS_HPP_INCLUDED
-#define PERSISTENCE_POSTGRESQL_RENDERERS_HPP_INCLUDED
+#ifndef PERSISTENCE_ADAPTERS_POSTGRESQL_RENDERERS_HPP_INCLUDED
+#define PERSISTENCE_ADAPTERS_POSTGRESQL_RENDERERS_HPP_INCLUDED
#include
-#include
-
+#include
#include
namespace persistence {
@@ -46,4 +45,4 @@ namespace persistence {
};
}
-#endif // PERSISTENCE_POSTGRESQL_RENDERERS_HPP_INCLUDED
+#endif // PERSISTENCE_ADAPTERS_POSTGRESQL_RENDERERS_HPP_INCLUDED
diff --git a/persistence/assign_attributes.cpp b/persistence/assign_attributes.cpp
new file mode 100644
index 0000000..d2fb44e
--- /dev/null
+++ b/persistence/assign_attributes.cpp
@@ -0,0 +1,198 @@
+#include "persistence/assign_attributes.hpp"
+#include
+
+namespace persistence {
+ namespace {
+ using namespace wayward::data_franca;
+
+ struct BestFaithDeserializerDataVisitor : wayward::DataVisitor {
+ const Spectator& spec;
+ BestFaithDeserializerDataVisitor(const Spectator& spec) : spec(spec) {}
+
+ void visit_nil() final {
+ return;
+ }
+
+ void visit_boolean(bool& b) final {
+ if (spec >> b) return;
+ Integer n;
+ if (spec >> n) { b = n != 0; return; }
+ Real r;
+ if (spec >> r) { b = r != 0; return; }
+ String str;
+ if (spec >> str) { b = str != "false"; return; }
+ }
+
+ void visit_int8(std::int8_t& o) final {
+ Integer i;
+ if (spec >> i) { o = static_cast(i); }
+ Real r;
+ if (spec >> r) { o = static_cast(r); }
+ String str;
+ if (spec >> str) {
+ std::stringstream ss { std::move(str) };
+ ss >> o;
+ }
+ }
+
+ void visit_int16(std::int16_t& o) final {
+ Integer i;
+ if (spec >> i) { o = static_cast(i); }
+ Real r;
+ if (spec >> r) { o = static_cast(r); }
+ String str;
+ if (spec >> str) {
+ std::stringstream ss { std::move(str) };
+ ss >> o;
+ }
+ }
+
+ void visit_int32(std::int32_t& o) final {
+ Integer i;
+ if (spec >> i) { o = static_cast(i); }
+ Real r;
+ if (spec >> r) { o = static_cast(r); }
+ String str;
+ if (spec >> str) {
+ std::stringstream ss { std::move(str) };
+ ss >> o;
+ }
+ }
+
+ void visit_int64(std::int64_t& o) final {
+ Integer i;
+ if (spec >> i) { o = static_cast(i); }
+ Real r;
+ if (spec >> r) { o = static_cast(r); }
+ String str;
+ if (spec >> str) {
+ std::stringstream ss { std::move(str) };
+ ss >> o;
+ }
+ }
+
+ void visit_uint8(std::uint8_t& o) final {
+ Integer i;
+ if (spec >> i) { o = static_cast(i); }
+ Real r;
+ if (spec >> r) { o = static_cast(r); }
+ String str;
+ if (spec >> str) {
+ std::stringstream ss { std::move(str) };
+ ss >> o;
+ }
+ }
+
+ void visit_uint16(std::uint16_t& o) final {
+ Integer i;
+ if (spec >> i) { o = static_cast(i); }
+ Real r;
+ if (spec >> r) { o = static_cast(r); }
+ String str;
+ if (spec >> str) {
+ std::stringstream ss { std::move(str) };
+ ss >> o;
+ }
+ }
+
+ void visit_uint32(std::uint32_t& o) final {
+ Integer i;
+ if (spec >> i) { o = static_cast(i); }
+ Real r;
+ if (spec >> r) { o = static_cast(r); }
+ String str;
+ if (spec >> str) {
+ std::stringstream ss { std::move(str) };
+ ss >> o;
+ }
+ }
+
+ void visit_uint64(std::uint64_t& o) final {
+ Integer i;
+ if (spec >> i) { o = static_cast(i); }
+ Real r;
+ if (spec >> r) { o = static_cast(r); }
+ String str;
+ if (spec >> str) {
+ std::stringstream ss { std::move(str) };
+ ss >> o;
+ }
+ }
+
+ void visit_float(float& o) final {
+ Real r;
+ if (spec >> r) { o = static_cast(r); return; }
+ Integer i;
+ if (spec >> i) { o = static_cast(i); return; }
+ String str;
+ if (spec >> str) {
+ std::stringstream ss { std::move(str) };
+ ss >> o;
+ }
+ }
+
+ void visit_double(double& o) final {
+ Real r;
+ if (spec >> r) { o = static_cast(r); return; }
+ Integer i;
+ if (spec >> i) { o = static_cast(i); return; }
+ String str;
+ if (spec >> str) {
+ std::stringstream ss { std::move(str) };
+ ss >> o;
+ }
+ }
+
+ void visit_string(std::string& o) final {
+ Integer i;
+ if (spec >> i) {
+ std::stringstream ss;
+ ss << i;
+ o = ss.str();
+ return;
+ }
+ Real r;
+ if (spec >> r) {
+ std::stringstream ss;
+ ss << r;
+ o = ss.str();
+ return;
+ }
+
+ spec >> o;
+ }
+
+ void visit_key_value(const std::string& key, AnyRef data, const IType* type) final {
+ auto sub_spectator = spec[key];
+ BestFaithDeserializerDataVisitor sub_visitor { sub_spectator };
+ type->visit_data(data, sub_visitor);
+ }
+
+ void visit_element(std::int64_t idx, AnyRef data, const IType* type) final {
+ auto sub_spectator = spec[idx];
+ BestFaithDeserializerDataVisitor sub_visitor { sub_spectator };
+ type->visit_data(data, sub_visitor);
+ }
+
+ void visit_special(AnyRef data, const IType* type) final {
+ // XXX: TODO
+ }
+
+ bool can_modify() const {
+ return true;
+ }
+
+ bool is_nil_at_current() const {
+ return spec.type() == DataType::Nothing;
+ }
+
+ };
+ }
+
+ namespace detail {
+ void assign_attributes(AnyRef record, const IRecordType* record_type, const wayward::data_franca::Spectator& data) {
+ BestFaithDeserializerDataVisitor visitor { data };
+ record_type->visit_data(record, visitor);
+ }
+ }
+}
diff --git a/persistence/assign_attributes.hpp b/persistence/assign_attributes.hpp
index 3180062..a536943 100644
--- a/persistence/assign_attributes.hpp
+++ b/persistence/assign_attributes.hpp
@@ -3,13 +3,19 @@
#define PERSISTENCE_ASSIGN_ATTRIBUTES_HPP_INCLUDED
#include
-#include
+#include
namespace persistence {
- template
- void assign_attributes(RecordPtr ptr, const wayward::Node& data) {
- const IRecordType* record_type = get_type();
+ namespace detail {
+ using wayward::AnyRef;
+
+ void assign_attributes(AnyRef record, const IRecordType* record_type, const wayward::data_franca::Spectator& data);
+ }
+ template
+ void assign_attributes(RecordPtr ptr, const wayward::data_franca::Spectator& data) {
+ auto record_type = get_type();
+ detail::assign_attributes(*ptr, record_type, data);
}
}
diff --git a/persistence/association.hpp b/persistence/association.hpp
index 5e79920..094ec78 100644
--- a/persistence/association.hpp
+++ b/persistence/association.hpp
@@ -5,106 +5,160 @@
#include
#include
+#include
+
namespace persistence {
struct IRecordType;
+ struct Context;
+ struct IAssociationAnchor;
- /// Descriptors for the type reflection system:
+ struct AssociationError : wayward::Error {
+ AssociationError(const std::string& msg) : wayward::Error(msg) {}
+ };
+ // Descriptors for the type reflection system:
struct IAssociation {
virtual ~IAssociation() {}
- virtual const IRecordType& self_type() const = 0;
- virtual const IRecordType& foreign_type() const = 0;
+ virtual const IRecordType* self_type() const = 0;
+ virtual const IRecordType* foreign_type() const = 0;
virtual std::string foreign_key() const = 0;
+ virtual std::string name() const = 0;
+ virtual IAssociationAnchor* get_anchor(AnyRef) const = 0;
};
- template
- struct IAssociationTo {
- virtual ~IAssociationTo() {}
+ // Base class for things like BelongsTo<>, HasMany<>, etc.
+ struct IAssociationAnchor {
+ virtual ~IAssociationAnchor() {}
+ virtual void initialize(const IAssociation* association, Context* context) = 0;
+ virtual void load() = 0;
+ virtual bool is_loaded() const = 0;
+ virtual const IAssociation* association() const = 0;
+ virtual Context* context() const = 0;
};
- template
- struct ISingularAssociationTo : IAssociationTo {
- virtual ~ISingularAssociationTo() {}
+ template
+ struct ISingularAssociationAnchor : IAssociationAnchor {
+ using AssociatedType = T;
+ virtual void populate(RecordPtr record) = 0;
+ virtual RecordPtr get() const = 0;
};
- template
- struct IPluralAssociationTo : IAssociationTo {
- virtual ~IPluralAssociationTo() {}
+ template
+ struct IPluralAssociationAnchor : IAssociationAnchor {
+ using AssociatedType = T;
+ virtual void populate(std::vector> records) = 0;
+ virtual std::vector> get() = 0;
};
- template
- struct IAssociationFrom : IAssociation {
- virtual ~IAssociationFrom() {}
+ template
+ struct AssociationAnchorBase : Base {
+ const IAssociation* association_;
+ Context* context_;
- virtual void initialize_in_object(Owner& object) const = 0;
- };
+ const IAssociation* association() const final { return association_; }
+ Context* context() const final { return context_; }
- struct ISingularAssociationField;
- template
- struct ISingularAssociationFrom : IAssociationFrom {
- virtual ~ISingularAssociationFrom() {}
+ void initialize(const IAssociation* association, Context* context) override {
+ association_ = association;
+ context_ = context;
+ }
+ };
- virtual ISingularAssociationField* get_field(Owner& object) const = 0;
- virtual const ISingularAssociationField* get_field(const Owner& object) const = 0;
+ template
+ struct SingularAssociationAnchor : AssociationAnchorBase> {
};
- struct IPluralAssociationField;
- template
- struct IPluralAssociationFrom : IAssociationFrom {
- virtual ~IPluralAssociationFrom() {}
+ template
+ struct PluralAssociationAnchor : AssociationAnchorBase> {
+ };
- virtual IPluralAssociationField* get_field(Owner& object) const = 0;
- virtual const IPluralAssociationField* get_field(const Owner& object) const = 0;
+ template
+ struct IAssociationFrom : IAssociation {
+ virtual void initialize_in_object(Owner& object, Context* context) const = 0;
+ virtual IAssociationAnchor* get_anchor_known(Owner& object) const = 0;
+ virtual const IAssociationAnchor* get_anchor_known(const Owner& object) const = 0;
+ virtual wayward::data_franca::ReaderPtr get_member_reader(const Owner& object, wayward::Bitflags) const = 0;
+ virtual wayward::data_franca::AdapterPtr get_member_adapter(Owner& object, wayward::Bitflags) const = 0;
};
- template
- struct SingularAssociation : ISingularAssociationFrom, ISingularAssociationTo {
- explicit SingularAssociation(std::string key) : key_(std::move(key)) {}
- virtual ~SingularAssociation() {}
+ template
+ struct AssociationFrom : IAssociationFrom {
+ // XXX: For some reason, Clang doesn't see these, and complains that
+ // derived classes are abstract, unless they also get defined in AssociationBase. :()
+ const IRecordType* self_type() const { return get_type(); }
+ IAssociationAnchor* get_anchor(AnyRef record) const {
+ if (&record.type_info() != &get_type()->type_info()) {
+ throw wayward::TypeError{"Tried to get association with wrong object type."};
+ }
+ return get_anchor_known(*record.get());
+ }
+ };
- const IRecordType& self_type() const final { return *get_type(); }
- const IRecordType& foreign_type() const final { return *get_type(); }
- std::string foreign_key() const override { return key_; }
- protected:
- std::string key_;
+ template
+ struct AssociationBetween : IAssociationFrom {
+ using Owner = Owner_;
+ using AssociatedType = T_;
+ const IRecordType* foreign_type() const { return get_type(); }
};
- template
- struct PluralAssociation : IPluralAssociationFrom, IPluralAssociationTo {
- explicit PluralAssociation(std::string key) : key_(std::move(key)) {}
- virtual ~PluralAssociation() {}
+ template
+ struct AssociationBase : Base {
+ using Owner = typename Base::Owner;
+ using AssociatedType = typename Base::AssociatedType;
+ using MemberPtr = Anchor Owner::*;
+ AssociationBase(MemberPtr member, std::string name, std::string key) : member_(member), name_(std::move(name)), key_(std::move(key)) {}
- const IRecordType& self_type() const final { return *get_type(); }
- const IRecordType& foreign_type() const final { return *get_type(); }
- std::string foreign_key() const override { return key_; }
- protected:
- std::string key_;
- };
+ Anchor* get(Owner& object) const { return &(object.*member_); }
+ const Anchor* get(const Owner& object) const { return &(object.*member_); }
- /// Interface for struct members:
+ IAssociationAnchor* get_anchor_known(Owner& object) const final { return get(object); }
+ const IAssociationAnchor* get_anchor_known(const Owner& object) const final { return get(object); }
- struct ISingularAssociationField {
- virtual ~ISingularAssociationField() {}
- virtual const IRecordType& foreign_type() const = 0;
- };
+ void initialize_in_object(Owner& object, Context* context) const override {
+ get(object)->initialize(this, context);
+ }
- template
- struct ISingularAssociationFieldTo : ISingularAssociationField {
- virtual ~ISingularAssociationFieldTo() {}
- virtual void populate(RecordPtr record) = 0;
- };
+ wayward::data_franca::ReaderPtr get_member_reader(const Owner& object, wayward::Bitflags options) const final {
+ return wayward::data_franca::make_reader(*get(object), options);
+ }
+
+ wayward::data_franca::AdapterPtr get_member_adapter(Owner& object, wayward::Bitflags options) const final {
+ return wayward::data_franca::make_adapter(*get(object), options);
+ }
+
+ MemberPtr member_ptr() const { return member_; }
- struct IPluralAssociationField {
- virtual ~IPluralAssociationField() {}
- virtual const IRecordType& foreign_type() const = 0;
+ const IRecordType* foreign_type() const final { return get_type(); }
+ const IRecordType* self_type() const final { return get_type(); }
+ std::string name() const final { return name_; }
+ std::string foreign_key() const final { return key_; }
+
+ // See line 86.
+ IAssociationAnchor* get_anchor(AnyRef record) const final {
+ if (&record.type_info() != &get_type()->type_info()) {
+ throw wayward::TypeError{"Tried to get association with wrong object type."};
+ }
+ return get_anchor_known(*record.get());
+ }
+
+ protected:
+ std::string name_;
+ std::string key_;
+ MemberPtr member_;
};
- template
- struct IPluralAssociationFieldTo : IPluralAssociationField {
- virtual ~IPluralAssociationFieldTo() {}
+ template
+ struct SingularAssociationBase : AssociationBase> {
+ using BaseClass = AssociationBase>;
+ using MemberPtr = typename BaseClass::MemberPtr;
+ SingularAssociationBase(MemberPtr member, std::string name, std::string fkey) : BaseClass(member, std::move(name), std::move(fkey)) {}
+ };
- // TODO: Use a better RecordCollection type, that doesn't copy the context lifetime sentinel for each record.
- virtual void populate(std::vector> records) = 0;
+ template
+ struct PluralAssociationBase : AssociationBase> {
+ using BaseClass = AssociationBase>;
+ using MemberPtr = typename BaseClass::MemberPtr;
+ PluralAssociationBase(MemberPtr member, std::string name, std::string fkey) : BaseClass(member, std::move(name), std::move(fkey)) {}
};
}
diff --git a/persistence/ast.hpp b/persistence/ast.hpp
index b86dd56..0778d3b 100644
--- a/persistence/ast.hpp
+++ b/persistence/ast.hpp
@@ -15,6 +15,8 @@ namespace persistence {
using wayward::ICloneable;
using wayward::Maybe;
+ template using Ptr = CloningPtr;
+
struct StarFrom;
struct StringLiteral;
struct NumericLiteral;
@@ -104,6 +106,7 @@ namespace persistence {
struct BooleanLiteral : Cloneable {
bool value;
+ BooleanLiteral(bool b) : value(b) {}
std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); }
};
@@ -133,10 +136,10 @@ namespace persistence {
// function(arguments...)
struct Aggregate : Cloneable {
virtual ~Aggregate() {}
- Aggregate(std::string function, std::vector> arguments) : function(std::move(function)), arguments(std::move(arguments)) {}
+ Aggregate(std::string function, std::vector> arguments) : function(std::move(function)), arguments(std::move(arguments)) {}
std::string function;
- std::vector> arguments;
+ std::vector> arguments;
std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); }
};
@@ -144,7 +147,7 @@ namespace persistence {
// (elements...)
struct List : Cloneable {
virtual ~List() {}
- std::vector> elements;
+ std::vector> elements;
std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); }
};
@@ -155,12 +158,12 @@ namespace persistence {
// ELSE otherwise
// END
struct CaseSimple : Cloneable {
- CloningPtr value;
+ Ptr value;
struct When {
- CloningPtr when;
- CloningPtr then;
+ Ptr when;
+ Ptr then;
};
- CloningPtr otherwise;
+ Ptr otherwise;
std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); }
};
@@ -177,10 +180,10 @@ namespace persistence {
// END
struct Case : Cloneable {
struct When {
- CloningPtr cond;
- CloningPtr then;
+ Ptr cond;
+ Ptr then;
};
- CloningPtr otherwise;
+ Ptr otherwise;
std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); }
};
@@ -196,7 +199,7 @@ namespace persistence {
struct NotCondition : Cloneable {
virtual ~NotCondition() {}
- CloningPtr subcondition;
+ Ptr subcondition;
std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); }
};
@@ -214,7 +217,7 @@ namespace persistence {
IsUnknown,
IsNotUnknown,
};
- CloningPtr value;
+ Ptr value;
Cond op;
std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); }
@@ -238,10 +241,10 @@ namespace persistence {
};
virtual ~BinaryCondition() {}
- BinaryCondition(CloningPtr lhs, CloningPtr rhs, Cond op) : lhs(std::move(lhs)), rhs(std::move(rhs)), op(op) {}
+ BinaryCondition(Ptr lhs, Ptr rhs, Cond op) : lhs(std::move(lhs)), rhs(std::move(rhs)), op(op) {}
- CloningPtr lhs;
- CloningPtr rhs;
+ Ptr lhs;
+ Ptr rhs;
Cond op;
std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); }
@@ -250,9 +253,9 @@ namespace persistence {
// value BETWEEN lower_bound AND upper_bound
struct BetweenCondition : Cloneable {
virtual ~BetweenCondition() {}
- CloningPtr value;
- CloningPtr lower_bound;
- CloningPtr upper_bound;
+ Ptr value;
+ Ptr lower_bound;
+ Ptr upper_bound;
std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); }
};
@@ -265,10 +268,10 @@ namespace persistence {
};
virtual ~LogicalCondition() {}
- LogicalCondition(CloningPtr lhs, CloningPtr rhs, Cond op) : lhs(std::move(lhs)), rhs(std::move(rhs)), op(op) {}
+ LogicalCondition(Ptr lhs, Ptr rhs, Cond op) : lhs(std::move(lhs)), rhs(std::move(rhs)), op(op) {}
- CloningPtr lhs;
- CloningPtr rhs;
+ Ptr lhs;
+ Ptr rhs;
Cond op;
std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); }
@@ -291,24 +294,24 @@ namespace persistence {
Type type;
std::string relation;
std::string alias;
- CloningPtr on;
+ Ptr on;
- Join(Type type, std::string relation, std::string alias, CloningPtr on) : type(type), relation(std::move(relation)), alias(std::move(alias)), on(std::move(on)) {}
+ Join(Type type, std::string relation, std::string alias, Ptr on) : type(type), relation(std::move(relation)), alias(std::move(alias)), on(std::move(on)) {}
};
struct SelectAlias {
- CloningPtr value;
+ Ptr value;
wayward::Maybe alias;
SelectAlias() {}
- SelectAlias(CloningPtr val) : value(std::move(val)) {}
- SelectAlias(CloningPtr val, std::string alias) : value(std::move(val)), alias(std::move(alias)) {}
+ SelectAlias(Ptr val) : value(std::move(val)) {}
+ SelectAlias(Ptr val, std::string alias) : value(std::move(val)), alias(std::move(alias)) {}
SelectAlias(const SelectAlias&) = default;
SelectAlias(SelectAlias&&) = default;
};
struct Ordering {
- CloningPtr value;
+ Ptr value;
enum OrderingType {
Ascending,
Descending,
@@ -316,8 +319,8 @@ namespace persistence {
OrderingType ordering = Ascending;
Ordering() {}
- Ordering(CloningPtr val) : value(std::move(val)) {}
- Ordering(CloningPtr val, OrderingType ordering) : value(std::move(val)), ordering(ordering) {}
+ Ordering(Ptr val) : value(std::move(val)) {}
+ Ordering(Ptr val, OrderingType ordering) : value(std::move(val)), ordering(ordering) {}
Ordering(const Ordering&) = default;
Ordering(Ordering&&) = default;
};
@@ -329,9 +332,9 @@ namespace persistence {
std::vector select;
std::string relation;
Maybe relation_alias;
- CloningPtr where;
- std::vector> joins;
- std::vector> group;
+ Ptr where;
+ std::vector> joins;
+ std::vector> group;
std::vector order;
Maybe limit;
@@ -344,10 +347,10 @@ namespace persistence {
struct UpdateQuery : Cloneable, IQuery {
virtual ~UpdateQuery() {}
std::string relation;
- CloningPtr where;
+ Ptr where;
Maybe limit;
std::vector columns;
- std::vector> values;
+ std::vector> values;
std::string to_sql(ISQLQueryRenderer& visitor) const final { return visitor.render(*this); }
};
@@ -355,7 +358,7 @@ namespace persistence {
struct DeleteQuery : Cloneable, IQuery {
virtual ~DeleteQuery() {}
std::string relation;
- CloningPtr where;
+ Ptr where;
Maybe limit;
std::string to_sql(ISQLQueryRenderer& visitor) const final { return visitor.render(*this); }
@@ -365,7 +368,8 @@ namespace persistence {
virtual ~InsertQuery() {}
std::string relation;
std::vector columns;
- std::vector> values;
+ std::vector> values;
+ std::vector returning_columns;
std::string to_sql(ISQLQueryRenderer& visitor) const final { return visitor.render(*this); }
};
diff --git a/persistence/belongs_to.hpp b/persistence/belongs_to.hpp
index 62e9450..8a289c7 100644
--- a/persistence/belongs_to.hpp
+++ b/persistence/belongs_to.hpp
@@ -6,69 +6,202 @@
#include
#include
+#include
+
namespace persistence {
- template
- struct BelongsTo : ISingularAssociationFieldTo {
- using Type = AssociatedType;
- const ISingularAssociationTo* association_ = nullptr;
- RecordPtr ptr_;
- PrimaryKey id;
+ using wayward::Either;
- bool operator==(const RecordPtr& rhs) const { return ptr_ == rhs; }
- bool operator!=(const RecordPtr& rhs) const { return ptr_ != rhs; }
+ struct Context;
- AssociatedType* operator->() const { return ptr_.get(); } // TODO: Populate on-demand
+ template
+ RecordPtr find(Context& ctx, PrimaryKey key);
- // ISingularAssociationTo<> interface
- void populate(RecordPtr ptr) final {
- ptr_ = std::move(ptr);
+ template
+ const PrimaryKey* get_pk_for_record(const RecordPtr& record) {
+ auto pk = get_type()->primary_key();
+ if (pk) {
+ auto pk_typed = dynamic_cast*>(pk);
+ if (pk_typed) {
+ auto& pk_id = pk_typed->get_known(*record);
+ if (pk_id.is_persisted()) {
+ return &pk_id;
+ }
+ }
}
- const IRecordType& foreign_type() const final {
- return *get_type();
+ return nullptr;
+ }
+
+ template
+ struct BelongsTo : SingularAssociationAnchor {
+ using AssociatedType = T;
+
+ BelongsTo() : value_(PrimaryKey{}) {}
+ Either> value_;
+
+ bool operator==(const RecordPtr& rhs) const { return get() == rhs; }
+ bool operator!=(const RecordPtr& rhs) const { return !(*this == rhs); }
+
+ BelongsTo& operator=(RecordPtr ptr) {
+ value_ = ptr;
+ return *this;
}
- };
- template
- struct BelongsToAssociation : SingularAssociation {
- using MemberPointer = BelongsTo O::*;
- explicit BelongsToAssociation(std::string key, MemberPointer ptr) : SingularAssociation{std::move(key)}, ptr_(ptr) {}
- MemberPointer ptr_;
+ BelongsTo& operator=(int64_t id) {
+ value_ = PrimaryKey{id};
+ return *this;
+ }
+
+ PrimaryKey id() const {
+ auto ptr = id_ptr();
+ if (ptr) {
+ return *ptr;
+ } else {
+ return PrimaryKey{};
+ }
+ }
+
+ const PrimaryKey* id_ptr() const {
+ const PrimaryKey* ptr = nullptr;
+
+ value_.template when([&](const PrimaryKey& key) {
+ if (key.is_persisted()) {
+ ptr = &key;
+ }
+ });
+
+ value_.template when>([&](const RecordPtr& referenced) {
+ if (referenced) {
+ ptr = get_pk_for_record(referenced);
+ }
+ });
+
+ return ptr;
+ }
+
+ RecordPtr operator->() {
+ load();
+ return get();
+ }
- void initialize_in_object(O& object) const final {
- (object.*ptr_).association_ = this;
+ RecordPtr operator->() const {
+ return get();
}
- ISingularAssociationField* get_field(O& object) const final {
- return &(object.*ptr_);
+ bool is_set() const {
+ bool b = false;
+ value_.template when([&](const PrimaryKey& key) {
+ b = key.is_persisted();
+ });
+ value_.template when>([&](const RecordPtr& ptr) {
+ b = ptr != nullptr;
+ });
+ return b;
}
- const ISingularAssociationField* get_field(const O& object) const final {
- return &(object.*ptr_);
+
+ /// IAssociationAnchor interface:
+
+ bool is_loaded() const final {
+ return value_.template is_a>();
+ }
+
+ void load() final {
+ value_.template when([&](const PrimaryKey& key) {
+ if (key.is_persisted()) {
+ value_ = persistence::find(*this->context_, key);
+ }
+ });
+ }
+
+
+ /// ISingularAssociationAnchor interface:
+
+ void populate(RecordPtr
{{ comment.text }}
+Author: {{ comment.author.name }} ({{ comment.author.email }})
+