Skip to content

Commit

Permalink
Item14237: Started documenting the new specs format.
Browse files Browse the repository at this point in the history
  • Loading branch information
vrurg committed May 12, 2017
1 parent 7d13baf commit 319e7b6
Show file tree
Hide file tree
Showing 2 changed files with 262 additions and 1 deletion.
2 changes: 1 addition & 1 deletion DBConfigExtension/data/System/DBConfigExtension.txt
@@ -1,4 +1,4 @@
%META:TOPICINFO{author="ProjectContributor" date="1494032249" format="1.1" version="1"}%
%META:TOPICINFO{author="ProjectContributor" date="1494295588" format="1.1" version="1"}%
%META:TOPICPARENT{name="Plugins"}%
---+!! Database Config Extension

Expand Down
261 changes: 261 additions & 0 deletions core/data/System/SpecFileFormat.txt
@@ -0,0 +1,261 @@
%META:TOPICINFO{author="ProjectContributor" date="1494556778" format="1.1" version="1"}%
---+!! Spec Files v2
%TOC%

This topic is about further development of %WIKITOOLNAME% spec format. Read
about the history and the reasoning behind the specs in
[[Development.HowToWriteASpecFile]]. This document has more focus on specs supported
by %WIKITOOLNAME% v3.

%X% *NOTE* This document might not be a comprehensive description of the specs and
may require further improvements.

#BasicConcepts
---++ Basic Concepts

Specs v2 are based upon a simple Perl structure of "list of lists". The structure could further
be extended to higher-level formats if considered reasonable. But for most cases Perl data should
be sufficient. A typical spec file would have the following look:

<verbatim>
#!data
-section => "Extensions" => [
-section => "DBConfigExtension" => [
-text => <<EOT,
Database storage backend for LSC
EOT
"Extensions.DBConfigExtension.Connection" => [
Host => STRING => [
-default => 'localhost',
-label => 'Database host',
-text => 'Where the database server is located',
],
Port => NUMBER => [
-default => undef,
-label => 'Database port',
-text => 'Port number where database server is listening',
],
...,
EnableSSL => [
-type => BOOLEAN,
-default => 1,
-label => "DSN",
],
],
],
],
</verbatim>

Essentially this is just a Perl array which will be enclosed into square brackets
and fed to =Foswiki::Config= =spec()= method. The array contains several kinds of
elements like section or key definition, etc.

---++ Directories and files.

Lets assume that =FOSWIKI_LIBS= shell environment variable contains the path to
%WIKITOOLNAME% libraries. In this case the following directories are used to
look for spec files:

| *Directory* | *Description* |
| =$FOSWIKI_LIBS= | To load =Foswiki.spec= |
| =$FOSWIKI_LIBS/Foswiki/{Plugins&#124;Contrib&#124;Extension}= | To search for custom spec files. Directories are scanned recursively for files matching =Spec.*= or =*.spec= globs |
| =$FOSWIKI_LIBS/.specCache= | To store specs cached data |

Each file found could contain different spec formats. More about formats is in [[#SpecFormats][corresponding section]] of this topic. Here it will be
explained how the format is determined for a file:

1. The file is checked for shebang. Shebang format is a commonly used =#!= prefix followed with a format name defined by a single word (see =\w= character class in [[https://perldoc.perl.org/perlre.html][Perl regular expressions documentation]]). Example:
<verbatim>#!legacy</verbatim>
1. If file name is matching =Spec.*= glob then its extension defines the format. Example: =Spec.data=
1. If the file contains TML section formatting string in a =#= comment string and a line starting with _$Foswiki::cfg{_ or _1;_ strings then it's format is guessed as legacy.
1. The default format is data.

#SpecFormats
---++ Formats

There're three Spec formats supported out of the box: =data=, =perl=, and =legacy=. =data= and =perl= are just variants of v2 specs. =legacy=
is the one used in pre-%WIKITOOLNAME% v3 era. Sure enough, it is supported for compatibility matters but its use is
discouraged.

=data= format is the basic and the simplest one. Its use is described in the [[#BasicConcepts][Basic Concepts]] section.

=perl= format is somewhat more advanced as file content is considered to be a body of a method called against a object
of a class with =Foswiki::AppObject= and =Foswiki::CfgObject= roles applied. The actual class name is irrelevant and may
vary depending on implementation. The method will have variable =$this= available for use. It must return a list with
valid spec data.

=perl= format is good for a situation if some complex processing is necessary before spec data could be formed.

---++ Data Format

Spec data is a list of items of different kinds:

* a option is a item prefixed with dash
* a string
* an array ref defining a element body
* a value of any type possibly following a option.

Elements are the bones of spec body. Currently there are three of them:

1. Sections
1. Branch nodes
1. Leaf nodes

The order of the list defines the order of inclusion. I.e. sections may
contain other sections and branch nodes; branch nodes may contain other
branch nodes and leaf nodes. Leaf nodes only contain values. The example in
the [[#BasicConcepts][Basic Concepts]] section defines a top-level section
_Extensions_, a second level section _DBConfigExtension_, a branch node
_Connection_, and a leaf node _Host_, particularly. Note that it is also
implicitly defines branch nodes _Extensions_ and _DBConfigExtension_ as key
_Extensions.DBConfigExtension.Connection_ gets split into subkeys.

---+++ Section

Sections are a way of grouping configuration settings into logically tied
sets aimed at easing user perception of the configuration. In other words,
a section "Extensions" is expected to contain per-extension config items;
a subsection "Permissions" of "Files" section is about ownership/ACLs applied
to application data files; etc.

A section definition is always started with =-section= option. As a matter
of fact it's the only element defined by a option. It's done this way to
distinguish key definitions from sections within a section body.

The option is then followed by section name string and section body represented
by an array ref:

<verbatim>
-section => "Section Name" => [
# Section body
...
]
</verbatim>

Each section has a level associated with it. There is implicitly defined _Root_
section with level 0. The top-level section (or sections) of any given spec
file correspondingly would be assigned with level 1; any of it's subsections –
level 2; and so on.

Two sections at the same level with same name define the same section as long
as their respective parents define the same section. For example, if there is a
definition:

<verbatim>
-section => "Sec1" => [
-expert => 1,
-section => "Sec2" => [
...
]
]
</verbatim>

and the following is found in another spec file:

<verbatim>
-section => "Sec1" => [
-section => "Sec2" => [
...
]
]
</verbatim>

then these are the same section. Note that presence/absence of "-expert"
option doesn't affect this logic. Even more: any subsequent declaration can
change option values defined earlier. This may have some undesirable side
effects and this behavior could change in the future. So far it is advised
to avoid use of any options in a section declaration if it is known to be
declared somewhere else.

As an opposite example to the previous one, the following definitions are
declaring different sections:

<verbatim>
-section => "Sec" => [
-section => "Sec2" => [
...
],
],
-section => "Sec1" => [
-section => "Sec" => [
-section => "Sec2" => [
...
]
]
]
</verbatim>

_Sec2_ are two new sections here while _Sec1_ is extending _Sec1_ from the
previous two specs.

Note the comma between two =-section= declaration in the latter example.
Remember that this is a Perl list and declaration of _Sec1_ in nothing but
another items in this list.

---+++ Nodes

Nodes are keys in =Foswiki::Config= data hash. Depending on their location
in the hash tree they're separated into two categories: *branch* and *leaf* nodes.
Branch nodes are those not storing any value but serving as a container for
their sub-nodes. On the contrary, a leaf node is terminal in a path from
top-level node to a value the key represents.

For example, in path _From.Top.To.Bottom_ subkeys _From_, _Top_, and _To_
are branches and _Bottom_ is the leaf.

A node type could be determined in a few possible ways:

* If data type or default value is defined for a node – it's a leaf
* If an option which is either leaf or branch only specific is used - the node is of the type for which the option is valid
* If a sub-key definition detected – it's a branch

Mixing incompatible attributes within single node definition raises a fatal
exception. For example, if a node has type assigned to it and a sub-key
definition is found.

A node declaration can only be found within a section body. The following spec is illegal:

<verbatim>
Key => [
...
]
</verbatim>

A declaration consist of:

1. Node name of path
1. Optional data type
1. Node body represented by an arrayref

Example of valid declarations:

<verbatim>
Key1 => [
SubKey => [
-type => 'STRING(30)',
],
],
SomeNumber => 'NUMBER(10)' => [
-default => 3.1415926,
...
],
SomeURL => STRING => [
-default => "http://trygooglethis.com",
-label => "I don't know what is this for",
-text => <<EOT,
Let's expect a user to guess what is this value for and how the system is
using it. That'd be fun!
EOT
],
</verbatim>

#ImplDetail
---++ Implementation

Behind the scenes specs are handled by =Foswiki::Config= class code. An object of
this class can exists in two modes: _data_ or _specs_. In _data_ mode the object operates
on a simple data hash of configuration keys similar to the legacy =%Foswiki::cfg=.
In _specs_ mode the data hash is tied to =Foswiki::Config::DataHash= class.

-- Main.AdminUser - 11 May 2017

0 comments on commit 319e7b6

Please sign in to comment.