Crate diskplan_schema

source ·
Expand description

This crate provides the means to constuct a tree of SchemaNodes from text form (see parse_schema).

The language of the text form uses significant whitespace (four spaces) for indentation, distinguishes between files and directories by the presence of a /, and whether this is a symlink by presence of an -> (followed by its target path expression). That is, each indented node of the directory tree takes one of the following forms:

SyntaxDescription
strA file
str/A directory
str -> exprA symlink to a file
str/ -> exprA symlink to a directory

Properties of a given node are set using the following tags:

TagTypesDescription
:owner exprAllSets the owner of this file/directory/symlink target
:group exprAllSets the group of this file, directory or symlink target
:mode octalAllSets the permissions of this file/directory/symlink target
:source exprFileCopies content into this file from the path given by expr
:let ident = exprDirectorySets a variable at this level to be used by deeper levels
:def identDirectoryDefines a sub-schema that can be reused by :use
:use identDirectoryReuses a sub-schema defined by :def

Simple Schema

The top level of a schema describes a directory, whose attributes may be set by :owner, :group and :mode tags:

use diskplan_schema::*;

let schema_root = parse_schema("
    :owner person
    :group user
    :mode 777
")?;

assert!(matches!(schema_root.schema, SchemaType::Directory(_)));
assert_eq!(schema_root.attributes.owner.unwrap(), "person");
assert_eq!(schema_root.attributes.group.unwrap(), "user");
assert_eq!(schema_root.attributes.mode.unwrap(), 0o777);

A DirectorySchema may contain sub-directories and files:

// ...
"
    subdirectory/
        :owner admin
        :mode 700

    file_name
        :source content/example_file
"
// ...
assert_eq!(
    parse_schema(text)?
        .schema
        .as_directory()
        .expect("Not a directory")
        .entries()
        .len(),
    2
);

It may also contain symlinks to directories and files, whose own schemas will apply to the target:

// ...
"
    example_link/ -> /another/disk/example_target/
        :owner admin
        :mode 700

        file_to_create_at_target_end
            :source content/example_file
"
// ...
let (binding, node) = directory.entries().first().unwrap();
assert!(matches!(
    binding,
    Binding::Static(ref name) if name == &String::from("example_link")
));
assert_eq!(
    node.symlink.as_ref().unwrap().to_string(),
    String::from("/another/disk/example_target/")
);
assert!(matches!(node.schema, SchemaType::Directory(_)));

Variable Substitution

Variables can be used to drive construction, for example:

"
    :let asset_type = character
    :let asset_name = Monkey

    assets/
        $asset_type/
            $asset/
                reference/
"

Variables will also pick up on names already on disk (even if a :let provides a different value). For example, if we had assets/prop/Banana on disk already, $asset_type would match against and take the value “prop” (as well as “character”) and $asset would take the value “Banana” (as well as “Monkey”), producing:

assets
├── character
│   └── Monkey
│       └── reference
└── prop
    └── Banana
        └── reference

Pattern Matching

Any node of the schema can have a :match tag, which, via a Regular Expression, controls the possible values a variable can take.

IMPORTANT: No two variables can match the same value. If they do, an error will occur during execution, so be careful to ensure there is no overlap between patterns. The use of :avoid can help restrict the pattern matching and ensure proper partitioning.

Static names (without variables) always take precedence and do not need to be unique with respect to variable patterns (and vice versa).

For example, this is legal in the schema but will always error in practice:

$first/
$second/

For instance, when operating on the path /test, it yields:

Error: "test" matches multiple dynamic bindings "$first" and "$second" (Any)

A working example might be:

$first/
    :match [A-Z].*
$second/
    :match [^A-Z].*

Schema Reuse

Portions of a schema can be built from reusable definitions.

A definition is formed using the :def keyword, followed by its name and a body like any other schema node:

:def reusable/
    anything_inside/

It is used by adding the :use tag inside any other (same or deeper level) node:

reused_here/
    :use reusable

Multiple :use tags may be used. Attributes are resolved in the following order:

example/
    ## Attributes set here win (before or after any :use lines)
    :owner root

    ## First :use is next in precedence
    :use one

    ## Subsequent :use lines take lower precedence
    :use two

Structs

  • Owner, group and UNIX permissions
  • A DirectorySchema is a container of variables, definitions (named schemas) and a directory listing
  • A string expression made from one or more Tokens
  • A description of a file
  • The name given to a variable
  • A detailed error for an issue encountered during parsing
  • A node in an abstract directory hierarchy

Enums

  • How an entry is bound in a schema, either to a static fixed name or to a variable
  • File/directory specific aspects of a node in the tree
  • A choice of built-in variables that are used to provide context information during traversal
  • Part of an Expression; a constant string, or a variable for later expansion to a string

Functions