Skip to content

Commit

Permalink
Configy: Synchronize with upstream
Browse files Browse the repository at this point in the history
Bring in some documentation improvements and PR48 bugfix.
  • Loading branch information
Geod24 authored and dlang-bot committed Jan 12, 2024
1 parent 5ee7d6b commit a59e456
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 12 deletions.
28 changes: 27 additions & 1 deletion source/dub/internal/configy/Attributes.d
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,19 @@ public auto converter (FT) (FT func)
return Converter!RType(func);
}

/*******************************************************************************
Interface that is passed to `fromYAML` hook
The `ConfigParser` exposes the raw YAML node (`see `node` method),
the path within the file (`path` method), and a simple ability to recurse
via `parseAs`.
Params:
T = The type of the structure which defines a `fromYAML` hook
*******************************************************************************/

public interface ConfigParser (T)
{
import dub.internal.dyaml.node;
Expand All @@ -301,7 +314,20 @@ public interface ConfigParser (T)
/// Returns: current location we are parsing
public string path () const @safe pure nothrow @nogc;

///
/***************************************************************************
Parse this struct as another type
This allows implementing union-like behavior, where a `struct` which
implements `fromYAML` can parse a simple representation as one type,
and one more advanced as another type.
Params:
OtherType = The type to parse as
defaultValue = The instance to use as a default value for fields
***************************************************************************/

public final auto parseAs (OtherType)
(auto ref OtherType defaultValue = OtherType.init)
{
Expand Down
9 changes: 8 additions & 1 deletion source/dub/internal/configy/FieldRef.d
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,14 @@ unittest
static assert(FieldRefTuple!(FieldRefTuple!(Config3)[0].Type)[1].Name == "notStr2");
}

/// A pseudo `FieldRef` used for structs which are not fields (top-level)
/**
* A pseudo `FieldRef` used for structs which are not fields (top-level)
*
* Params:
* ST = Type for which this pseudo-FieldRef is
* DefaultName = A name to give to this FieldRef, default to `null`,
* but required to prevent forward references in `parseAs`.
*/
package template StructFieldRef (ST, string DefaultName = null)
{
///
Expand Down
23 changes: 13 additions & 10 deletions source/dub/internal/configy/Read.d
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,17 @@
To mark a field as optional even with its default value,
use the `Optional` UDA: `@Optional int count = 0;`.
Converter:
Because config structs may contain complex types such as
a Phobos type, a user-defined `Amount`, or Vibe.d's `URL`,
one may need to apply a converter to a struct's field.
Converters are functions that take a YAML `Node` as argument
and return a type that is implicitly convertible to the field type
(usually just the field type). They offer the most power to users,
as they can inspect the YAML structure, but should be used as a last resort.
fromYAML:
Because config structs may contain complex types outside of the project's
control (e.g. a Phobos type, Vibe.d's `URL`, etc...) or one may want
the config format to be more dynamic (e.g. by exposing union-like behavior),
one may need to apply more custom logic than what Configy does.
For this use case, one can define a `fromYAML` static method in the type:
`static S fromYAML(scope ConfigParser!S parser)`, where `S` is the type of
the enclosing structure. Structs with `fromYAML` will have this method
called instead of going through the normal parsing rules.
The `ConfigParser` exposes the current path of the field, as well as the
raw YAML `Node` itself, allowing for maximum flexibility.
Composite_Types:
Processing starts from a `struct` at the top level, and recurse into
Expand Down Expand Up @@ -400,8 +403,8 @@ public T parseConfig (T) (
fullyQualifiedName!T,
strict == StrictMode.Warn ?
strict.paint(Yellow) : strict.paintIf(!!strict, Green, Red));
return node.parseMapping!(StructFieldRef!T)(
null, T.init, const(Context)(cmdln, strict), null);
return node.parseField!(StructFieldRef!T)(
null, T.init, const(Context)(cmdln, strict));
case NodeID.sequence:
case NodeID.scalar:
case NodeID.invalid:
Expand Down
124 changes: 124 additions & 0 deletions source/dub/internal/configy/Test.d
Original file line number Diff line number Diff line change
Expand Up @@ -692,3 +692,127 @@ unittest
catch (Exception exc)
assert(exc.toString() == `/dev/null(2:6): data.array[0]: Parsing failed!`);
}

/// Test for error message: Has to be versioned out, uncomment to check manually
unittest
{
static struct Nested
{
int field1;

private this (string arg) {}
}

static struct Config
{
Nested nested;
}

static struct Config2
{
Nested nested;
alias nested this;
}

version(none) auto c1 = parseConfigString!Config(null, null);
version(none) auto c2 = parseConfigString!Config2(null, null);
}

/// Test support for `fromYAML` hook
unittest
{
static struct PackageDef
{
string name;
@Optional string target;
int build = 42;
}

static struct Package
{
string path;
PackageDef def;

public static Package fromYAML (scope ConfigParser!Package parser)
{
if (parser.node.nodeID == NodeID.mapping)
return Package(null, parser.parseAs!PackageDef);
else
return Package(parser.parseAs!string);
}
}

static struct Config
{
string name;
Package[] deps;
}

auto c = parseConfigString!Config(
`
name: myPkg
deps:
- /foo/bar
- name: foo
target: bar
build: 24
- name: fur
- /one/last/path
`, "/dev/null");
assert(c.name == "myPkg");
assert(c.deps.length == 4);
assert(c.deps[0] == Package("/foo/bar"));
assert(c.deps[1] == Package(null, PackageDef("foo", "bar", 24)));
assert(c.deps[2] == Package(null, PackageDef("fur", null, 42)));
assert(c.deps[3] == Package("/one/last/path"));
}

/// Test top level hook (fromYAML / fromString)
unittest
{
static struct Version1 {
uint fileVersion;
uint value;
}

static struct Version2 {
uint fileVersion;
string str;
}

static struct Config
{
uint fileVersion;
union {
Version1 v1;
Version2 v2;
}
static Config fromYAML (scope ConfigParser!Config parser)
{
static struct OnlyVersion { uint fileVersion; }
auto vers = parseConfig!OnlyVersion(
CLIArgs.init, parser.node, StrictMode.Ignore);
switch (vers.fileVersion) {
case 1:
return Config(1, parser.parseAs!Version1);
case 2:
Config conf = Config(2);
conf.v2 = parser.parseAs!Version2;
return conf;
default:
assert(0);
}
}
}

auto v1 = parseConfigString!Config("fileVersion: 1\nvalue: 42", "/dev/null");
auto v2 = parseConfigString!Config("fileVersion: 2\nstr: hello world", "/dev/null");

assert(v1.fileVersion == 1);
assert(v1.v1.fileVersion == 1);
assert(v1.v1.value == 42);

assert(v2.fileVersion == 2);
assert(v2.v2.fileVersion == 2);
assert(v2.v2.str == "hello world");
}

0 comments on commit a59e456

Please sign in to comment.