README.md in clerq-0.2.0 vs README.md in clerq-0.3.0

- old
+ new

@@ -45,11 +45,11 @@ #### Files The first convention is the scheme how a markdown content becomes the `Node` entity. -``` +```markdown # [p2] Part two {{parent: top}} Body ``` @@ -60,10 +60,17 @@ * `[p1]` is an optional identifier that becomes `node.id`; * `Part two` is an optional `node.title`; * `{{parent: top}}` in an optional metadata section that becomes `node.meta`; * and finally `Body` is an optional `node.body`. +```markdown +# Part two +{{id: p1, parent: top}} + +Body +``` + Every new header (`#`) at any level indicates a new node. When a file contains headers of different levels, the nodes will be created in a natural hierarchy based on header levels. So as the result of reading the content below, the Clerq will create the natural hierarchy with root node `Top` that holds two child nodes `First` and `Second`. ```markdown # Top ## First @@ -72,33 +79,49 @@ One more extra thing is links. You can place links to other nodes in the body section of the file content by using `[[<id>]]` macro. It can be handled in templates. #### IDs -Each node must have its own unique id so that you can refer to it in other parts of the project. That's why the recommended practice is to put the id straight into the header `# [node id] node title`. +To be able to build a hierarchy or to refer to other nodes, one needs each node to have its unique id. And you can pass it straight into markdown header `# [node id] node title` or provide it through `{{id: }}`. -ID can start with one dot, like `[.suffix]`, and clerq will add id of parent node. For the followed example, `[.fm]` will be translated to `[cm.fm]`. +ID can start with one dot, like `[.suffix]`, and clerq will build the node id as `node.parent_id + node.id`. -``` -# 3 Function requirements +When and ID is not provided, the Clerq will generate it automatically. Let's see the example of node: + +```markdown +# User requirements +## Requirement 1 +## Requirement 2 +# Function requirements ## [cm] Components ### [.fm] File manager ### Logger ``` -When an id is not provided, Clerq will generate it automatically, and you can freely combine nodes that have id and that has not. For the example above, the `Logger` will be identified as `[cm.01] Logger`. +According to rules mentioned above the example will be translated as followed: +```markdown +# [01] User requirements +## [01.01] Requirement 1 +## [01.02] Requirement 2 +# [02] Function requirements +## [cm] Components +### [cm.fm] File manager +### [cm.01] Logger +``` + #### Meta -The excerpt, the text in brackets `{{ }}` that follows by the header, contains node attributes. And the second convention mentioned in [Writing](#writing) section is two magic metadata attributes that specify parameters of a hierarchy: +The excerpt, the text in brackets `{{ }}` that follows by the header, contains node attributes. And the second convention mentioned in [Writing](#writing) section is the followed magic metadata attributes that specify parameters of a hierarchy: -1. `parent: <id>` indicates that the node belongs to a node with specified `id`; -2. `order_index: <id1> <id2>` indicates that child nodes must be lined up in specified order. +1. `id: <id>` specifies id through metadata; when in provided together with `# [<id>]`, the last has priority; +2. `parent: <id>` indicates that the node belongs to a node with specified `id`; +3. `order_index: <id1> <id2>` indicates that child nodes must be lined up in specified order. You can place in metadata any simple string that suitable for providing additional information like status, originator, author, priority, etc. E.g. -``` +```markdown # [r.1] {{parent: r, status: draft}} # [r.2] {{parent: r @@ -195,101 +218,155 @@ A usual scenario will consist of two simple steps: 1. Get data hierarchy from the repository. 2. Do some processing of the hierarchy. -Instead of adding extra scripts files somewhere in the project, you can write tasks to `<project>.thor` file and access to them through `thor <project>:<your-task> [<params>]`. - #### Node class The [Writing](#writing) section provides the basic knowledge to understand Clerq, and now it is the right time to see the [Node class](https://github.com/nvoynov/clerq/blob/master/lib/clerq/entities/node.rb). It implements the Composite pattern. -#### Interactors +#### Services -Clerq provides five followed interactors: +Clerq provides the following main service objects: -* `QueryAssembly` provides assembly of repository as root Node; +* `LoadAssembly` loads whole repository to Node class; * `CheckAssembly` checks the assembly for errors (ids and links); -* `RenderAssembly` render assembly by provided erb-template; +* `QueryNode` provides ability to query nodes from assembly; +* `QueryTemplate` return template by the template name; * `CreateNode` crates new node in the repository; -* `QueryTemplate` provides text of the template provided as parameter. +* `RenderNode` return text rendered by ERB. -The first part of each repository related task is to get repository assembly. It can be performed through `NodeRepository#assemble` or `QueryAssembly.call`. Each of these methods returns Node that provides [Enumerable](https://ruby-doc.org/core-2.6.5/Enumerable.html) interface. +The first part of each repository related task is to get repository assembly. It can be performed through `NodeRepository#assemble` or `LoadAssembly.call()`. Each of these methods returns Node that provides [Enumerable](https://ruby-doc.org/core-2.6.5/Enumerable.html) interface. -Let's invent some advanced scenario. Assume that you develop a "User requirements document" and the project policy requires that each user requirement must have the parameter called `originator`. You can write the policy as followed: +Let's see an example. Assume that you are developing a "User requirements document" and the project policy requires that each user requirement must have the parameter called `originator`. You can write the policy as followed: ```ruby require 'clerq' -include Clerk::Interactors +include Clerq::Services # supposed you have something like user requirements document -node = QueryAssembly.("node.title == 'User requirements'") +node = LoadAssembly.() +node = QueryNode.(node: node, query: "node.title == 'User requirements'") miss = node.drop(1).select{|n| n[:originator].empty? } unless miss.empty? - errmsg = "`Originator` is missed for the next nodes:\n" + errmsg = "`Originator` is missed for the following nodes:\n" errmsg << miss.map(&:id).join(', ') raise Error, errmsg end ``` +Instead of adding extra scripts files somewhere in the project, you can write tasks in `<project>.thor` (see [Automating](#automating) section for details.) + #### Root Node A hierarchy starts form root node and Clerq provides the root node with parameter `title` specified in `clerq.yml` file. The subject is a bit tricky actually and there are few extra considerations I try to explain below (and you can always see tests) When your repository stills empty, the Clerq will provide you with the root node. From one point it resembles the NullObject. When you have a few root nodes in your repository, those become direct childs of the root node. But when your repository contains single root node, the Clerq will return the single node as root node. The following example does not provide root node and it causes adding root node from `clerq.yml`. -``` +```markdown # User requirements # Functional requirements ``` -But this one provides, and root node will be `Product SRS` according to rule 1. +But this one provides, and the root node will be `Product SRS`. -``` +```markdown # Product SRS ## User requirements ## Functional requirements ``` -The QueryAssembly.call(query) follow similar logic +The QueryAssembly follows the similar logic * When query result is empty, the Clerq will provide result with QueryNullNode (node.title == `Query`, node[:query] == QUERY_STRING) * When query result contains single node, it becomes a root query node. * When query result contains more than one, those becomes a child of root query node. +### Automating + +The Clerq creates `<project>.thor` where you can place your project-specific tasks. It is a standard [Thor](https://github.com/erikhuda/thor) that brings you all script automation power through CLI and to dive deeper just spend a few minutes reading [the poject wiki](https://github.com/erikhuda/thor/wiki). + +Let's move the code from [Scripting](#scripting) section to the `<project>.thor` file: + +```ruby +require 'clerq' +include Clerq::Services + +class MyDocument < Thor + namespace :mydoc + + no_commands { + def stop_on_error!(errmsg) + raise Thor::Error, errmsg + end + } + + desc 'check_originator', 'Check :originator' + def check_originator + node = LoadAssembly.() + node = QueryAssembly.(node: node, query: "node.title == 'User requirements'") + miss = node.drop(1).select{|n| n[:originator].empty? } + unless miss.empty? + errmsg = "`Originator` is missed for the following nodes:\n" + errmsg << miss.map(&:id).join(', ') + stop_on_error!(errmsg) + end + end +end +``` + +And then you can run the task by + + $ thor mydoc:check_originator + +This example is just very basic and your automation scripts could be much more complex. + +Another quick example is [clerq.thor] (https://github.com/nvoynov/clerq/blob/master/clerq.thor) file that was created just to overcome handling curly bracket `{{}}` in Jekyll and now I run `thor clerqsrc:docs` every time after changing this file. + ### Templating The Clerq provides the ability to precise adjusting the output for `clerq build` command by erb-templates and gives you two basic templates from the box. * [default.md.erb](https://github.com/nvoynov/clerq/blob/master/lib/assets/tt/default.md.erb) that just combines all nodes to one markdown document; * [pandoc.md.erb](https://github.com/nvoynov/clerq/blob/master/lib/assets/tt/pandoc.md.erb) is more advanced, it produces [Pandoc's Markdown](https://pandoc.org/MANUAL.html#pandocs-markdown) and provides three followed macros for node body: - * `{{@@list}}` - replaces the macro with the list of child requirements; - * `{{@@tree}}` - replaces the macro with the tree of child requirements; + * `{{@@list}}` - replaces the macro with the list of child nodes; + * `{{@@tree}}` - replaces the macro with the tree of child nodes; * `{{@@skip}}` - skip all content inside the brackets. +### Publishing + +In addition to the `clerq build` command in [lib/clerq_doc.thor](https://github.com/nvoynov/clerq/blob/master/lib/assets/lib/clerq_doc.rb) I provided the example of basic documents management tasks (it will be placed in new project `lib` folder). You can find there two example of commands that you can start your own publishing automation. + +* `thor clerq:doc:publish` will create `<project>.docx` and `<project>.html`; +* `thor clerq:doc:grab` will import provided document into the current project repository. + ## Known issues ### Thor version The one issue I certain in is when you are using different version of thor, your custom scripts won't work. +### Test suite + +Because `default.md.erb` and `pandoc.md.erb` have inside the same class `MarkupNode`, sometimes one of `default_spec.rb` or `pandoc_spec.rb` fails. + ## Some considerations ### Some obvious things Use modern text editor that provides projects tree. like Atom, Sublime, etc. -Hold your projects in Git +Hold your projects in Git. Use pandoc for generating output in different formats ### MarkupNode -Don't like the current dirty solution with templates and incorporated MarkupNode that does all that stuff with macro. It is the first attempt to provide template that can skipp comments +Don't like the current dirty solution with templates and incorporated MarkupNode that does all that stuff with macro. It is the first attempt to provide template that can skip comments. ### Several artifacts Because Clerq has `-q/--query QUERY_STRING` option you can be interested in developing several different artifacts in one project.