clerq 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/CHANGELOG.md +14 -2
- data/Gemfile.lock +24 -0
- data/README.md +107 -30
- data/clerq.thor +28 -0
- data/docs/README.md +408 -0
- data/lib/assets/knb/business-case.md +135 -0
- data/lib/assets/knb/requirement-life-cycle.md +47 -0
- data/lib/assets/knb/vision-document.md +191 -0
- data/lib/assets/lib/clerq_doc.thor +119 -0
- data/lib/assets/lib/colonize_repo.rb +82 -0
- data/lib/assets/lib/spec/colonize_repo_spec.rb +85 -0
- data/lib/assets/new/content.md.tt +1 -0
- data/lib/assets/tt/default.md.erb +1 -1
- data/lib/assets/tt/pandoc.md.erb +6 -6
- data/lib/clerq.rb +7 -2
- data/lib/clerq/cli.rb +33 -47
- data/lib/clerq/entities/node.rb +11 -5
- data/lib/clerq/repositories.rb +0 -1
- data/lib/clerq/repositories/file_repository.rb +1 -0
- data/lib/clerq/repositories/node_repository.rb +7 -6
- data/lib/clerq/services.rb +8 -0
- data/lib/clerq/services/check_assembly.rb +108 -0
- data/lib/clerq/{interactors → services}/create_node.rb +4 -5
- data/lib/clerq/services/load_assembly.rb +54 -0
- data/lib/clerq/{interactors/query_assembly.rb → services/query_node.rb} +15 -12
- data/lib/clerq/{interactors → services}/query_template.rb +3 -8
- data/lib/clerq/services/read_node.rb +98 -0
- data/lib/clerq/services/render_erb.rb +29 -0
- data/lib/clerq/{interactors/render_assembly.rb → services/render_node.rb} +8 -12
- data/lib/clerq/services/service.rb +19 -0
- data/lib/clerq/version.rb +1 -1
- metadata +21 -12
- data/TODO.md +0 -7
- data/lib/clerq/interactors.rb +0 -5
- data/lib/clerq/interactors/check_assembly.rb +0 -77
- data/lib/clerq/interactors/interactor.rb +0 -26
- data/lib/clerq/render_erb.rb +0 -33
- data/lib/clerq/repositories/node_reader.rb +0 -107
@@ -0,0 +1,8 @@
|
|
1
|
+
require_relative 'services/service'
|
2
|
+
require_relative 'services/check_assembly'
|
3
|
+
require_relative 'services/read_node'
|
4
|
+
require_relative 'services/load_assembly'
|
5
|
+
require_relative 'services/query_node'
|
6
|
+
require_relative 'services/query_template'
|
7
|
+
require_relative 'services/create_node'
|
8
|
+
require_relative 'services/render_node'
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require_relative 'service'
|
4
|
+
|
5
|
+
module Clerq
|
6
|
+
module Services
|
7
|
+
|
8
|
+
# Find errors in hierarchy and prints in console
|
9
|
+
class CheckAssembly < Service
|
10
|
+
|
11
|
+
private_class_method :new
|
12
|
+
|
13
|
+
def call
|
14
|
+
print_nonuniq_id
|
15
|
+
print_lost_roots
|
16
|
+
print_lost_index
|
17
|
+
print_lost_links
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def nonuniq_id
|
23
|
+
@node.inject({}) do |memo, node|
|
24
|
+
memo[node.id] ||= []
|
25
|
+
memo[node.id] << node
|
26
|
+
memo
|
27
|
+
end.select{|_, v| v.size > 1}
|
28
|
+
end
|
29
|
+
|
30
|
+
def print_nonuniq_id
|
31
|
+
errors = nonuniq_id
|
32
|
+
print "Checking for duplicates in node ids... "
|
33
|
+
puts errors.empty? ? 'OK' : "#{errors.size} found"
|
34
|
+
errors.each do |id, nodes|
|
35
|
+
occs = nodes.map{|n| n[:filename]}
|
36
|
+
.group_by{|i| i}
|
37
|
+
.map{|k,v| [v.size, k]}
|
38
|
+
.sort{|a, b| b.first <=> a.first}
|
39
|
+
.map{|i| n, src = i; "#{how_many_times(n)} in '#{src}'"}
|
40
|
+
occurrences = occs.join(occs.size == 2 ? ' and ' : ', ')
|
41
|
+
puts "- [#{id}] occured #{occurrences}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def print_lost_roots
|
46
|
+
lost = @node.select{|n| n[:parent] && n.parent.id != n[:parent]}
|
47
|
+
print "Checking for lost roots in node parents... "
|
48
|
+
puts lost.empty? ? 'OK' : "#{lost.size} found"
|
49
|
+
lost.each do |n|
|
50
|
+
puts "- {{parent: #{n[:parent]}}} of '#{n.id}' in '#{n[:filename]}'"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def print_lost_index
|
55
|
+
errors = @node
|
56
|
+
.reject{|n| n.order_index.empty?}
|
57
|
+
.inject({}) do |memo, n|
|
58
|
+
lost = n.order_index.reject{|o| n.item(o)}
|
59
|
+
memo[n] = lost unless lost.empty?
|
60
|
+
memo
|
61
|
+
end
|
62
|
+
|
63
|
+
print "Checking for lost items in order_index... "
|
64
|
+
puts errors.empty? ? 'OK' : "#{errors.size} found"
|
65
|
+
errors.each do |n, lost|
|
66
|
+
puts "- {{order_index: #{lost.join(' ')}}} not found of node '#{n.id}' in '#{n[:filename]}'"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def lost_links
|
71
|
+
index = @node.map(&:id).drop(1).uniq
|
72
|
+
@node.inject({}) do |memo, node|
|
73
|
+
node.links
|
74
|
+
.reject{ |lnk| index.include?(lnk) || @node.node(lnk)}
|
75
|
+
.each do |lnk|
|
76
|
+
memo[lnk] ||= []
|
77
|
+
memo[lnk] << node
|
78
|
+
end
|
79
|
+
memo
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def print_lost_links
|
84
|
+
errors = lost_links
|
85
|
+
print "Checking for lost links in nodes body... "
|
86
|
+
puts errors.empty? ? 'OK' : "#{errors.size} found"
|
87
|
+
errors.each do |link, arry|
|
88
|
+
where = arry.map{|n| "[#{n.id}] of '#{n[:filename]}'"}.join(', ')
|
89
|
+
puts "- [[#{link}]] in #{where}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# humanize number of how many times the error occurred in one file
|
94
|
+
def how_many_times(n)
|
95
|
+
case n
|
96
|
+
when 1 then 'once'
|
97
|
+
when 2 then 'twice'
|
98
|
+
else "#{n} times"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def initialize(node)
|
103
|
+
@node = node
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
@@ -1,24 +1,23 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
require_relative "
|
3
|
+
require_relative "service"
|
4
4
|
require_relative "query_template"
|
5
|
+
require_relative "../entities/node"
|
5
6
|
|
6
7
|
module Clerq
|
7
|
-
module
|
8
|
+
module Services
|
8
9
|
|
9
10
|
# Creates new node in repository according to provided parameters
|
10
11
|
# or raises CreateNode::Failure when
|
11
12
|
# * template not found
|
12
13
|
# * or repository contains a node with the same id
|
13
|
-
class CreateNode <
|
14
|
+
class CreateNode < Service
|
14
15
|
|
15
16
|
def call
|
16
17
|
@body = QueryTemplate.(@template) if @body.empty? && !@template.empty?
|
17
18
|
@node = Clerq::Entities::Node.new(
|
18
19
|
id: @id, title: @title, body: @body, meta: @meta)
|
19
20
|
Clerq.node_repository.save(@node)
|
20
|
-
rescue StandardError => e
|
21
|
-
raise Failure, e.message
|
22
21
|
end
|
23
22
|
|
24
23
|
protected
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require_relative 'service'
|
3
|
+
|
4
|
+
module Clerq
|
5
|
+
module Services
|
6
|
+
|
7
|
+
# The service returns assembly and prints progress in console
|
8
|
+
class LoadAssembly < Service
|
9
|
+
def call
|
10
|
+
memo = {}
|
11
|
+
on_parse_callback = lambda do |src|
|
12
|
+
puts "Reading '#{src}'..."
|
13
|
+
memo[src] = 0
|
14
|
+
end
|
15
|
+
on_error_callback = lambda do |err|
|
16
|
+
puts "\t#{err}"
|
17
|
+
memo[memo.keys.last] += 1
|
18
|
+
end
|
19
|
+
|
20
|
+
puts "Loading repository..."
|
21
|
+
assemble = Clerq.assemble(
|
22
|
+
on_parse: on_parse_callback,
|
23
|
+
on_error: on_error_callback)
|
24
|
+
|
25
|
+
if memo.empty?
|
26
|
+
puts "This repository is empty"
|
27
|
+
else
|
28
|
+
errors_count = memo.values.inject(0, &:+)
|
29
|
+
message = [].tap do |m|
|
30
|
+
m << "#{nos(memo.size, 'file')} loaded"
|
31
|
+
m << "#{nos(errors_count, 'error')} detected"
|
32
|
+
end.join(', ')
|
33
|
+
puts message
|
34
|
+
end
|
35
|
+
|
36
|
+
assemble
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
# TODO 0 zero, no
|
42
|
+
def nos(number, subject)
|
43
|
+
case number
|
44
|
+
when 0 then "no #{subject}s"
|
45
|
+
when 1 then "one #{subject}"
|
46
|
+
when 2 then "two #{subject}s"
|
47
|
+
else "#{number} #{subject}s"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -1,16 +1,16 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
require_relative "
|
3
|
+
require_relative "service"
|
4
4
|
|
5
5
|
module Clerq
|
6
|
-
module
|
6
|
+
module Services
|
7
7
|
|
8
8
|
# Provides repository nodes assembly
|
9
9
|
# @param [Sring] optional query string
|
10
10
|
# @return [Node] assembly of repository nodes
|
11
11
|
#
|
12
12
|
# Usage
|
13
|
-
# QueryAssembly.()
|
13
|
+
# QueryAssembly.(asse:, query:)
|
14
14
|
# QueryAssembly.(query_string)
|
15
15
|
#
|
16
16
|
# Rules for root node:
|
@@ -20,14 +20,12 @@ module Clerq
|
|
20
20
|
# when query returns mare than one nodes, then
|
21
21
|
# * it creates empty root node with meta[:query]
|
22
22
|
# * and places found nodes to the root
|
23
|
-
class
|
23
|
+
class QueryNode < Service
|
24
24
|
|
25
25
|
def call
|
26
26
|
proc = prepare_query unless @query.empty?
|
27
|
-
join = Clerq.node_repository.assemble
|
28
|
-
return join if @query.empty?
|
29
27
|
|
30
|
-
arry =
|
28
|
+
arry = @assembly.select{|node| proc.call(node) }
|
31
29
|
return arry.first.orphan! if arry.size == 1
|
32
30
|
|
33
31
|
node = Node.new(title: Clerq.title, meta: {query: @query})
|
@@ -37,12 +35,17 @@ module Clerq
|
|
37
35
|
|
38
36
|
protected
|
39
37
|
|
40
|
-
def initialize(query
|
38
|
+
def initialize(assembly: , query: )
|
39
|
+
unless assembly.is_a? Node
|
40
|
+
msg = "Invailid argument 'assembly'"
|
41
|
+
raise ArgumentError, msg
|
42
|
+
end
|
41
43
|
unless query.is_a? String
|
42
44
|
msg = "Invailid argument 'query'"
|
43
45
|
raise ArgumentError, msg
|
44
46
|
end
|
45
47
|
@query = query
|
48
|
+
@assembly = assembly
|
46
49
|
end
|
47
50
|
|
48
51
|
def prepare_query
|
@@ -50,13 +53,13 @@ module Clerq
|
|
50
53
|
Node.new.select{|node| proc.call(node)}
|
51
54
|
proc
|
52
55
|
rescue Exception => e
|
53
|
-
msg = "Invalid query #{@query} (#{e.message})\n#{USAGE}"
|
54
|
-
raise
|
56
|
+
msg = "Invalid query format #{@query} (#{e.message})\n#{USAGE}"
|
57
|
+
raise ArgumentError, msg
|
55
58
|
end
|
56
59
|
|
57
60
|
USAGE = <<~EOF
|
58
|
-
|
59
|
-
by eval() as 'Proc.new { |node| eval(
|
61
|
+
QueryAssembly.call(query: ) evaluates query paramater
|
62
|
+
by eval() as 'Proc.new { |node| eval(query) }'
|
60
63
|
e.g. the followed queries are valid:
|
61
64
|
node.id == 'uc'
|
62
65
|
node.title == 'Introduction'
|
@@ -1,19 +1,14 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
require_relative "
|
3
|
+
require_relative "service"
|
4
4
|
|
5
5
|
module Clerq
|
6
|
-
module
|
6
|
+
module Services
|
7
7
|
|
8
|
-
class QueryTemplate <
|
9
|
-
|
10
|
-
private_class_method :new
|
8
|
+
class QueryTemplate < Service
|
11
9
|
|
12
10
|
def call
|
13
11
|
Clerq.text_repository.text(@template)
|
14
|
-
rescue StandardError
|
15
|
-
err = "'#{@template}' template not found!"
|
16
|
-
raise Failure, err
|
17
12
|
end
|
18
13
|
|
19
14
|
# @param template [String] required name of template
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require_relative 'service'
|
3
|
+
require_relative '../entities/node'
|
4
|
+
include Clerq::Entities
|
5
|
+
|
6
|
+
module Clerq
|
7
|
+
module Services
|
8
|
+
|
9
|
+
# The service reads nodes from file and returns array of nodes
|
10
|
+
# It returns array because the file can have a few root nodes
|
11
|
+
class ReadNode < Service
|
12
|
+
|
13
|
+
def call
|
14
|
+
text = File.foreach(@filename)
|
15
|
+
read_nodes(text) do |node_text|
|
16
|
+
level, node = parse_node(node_text)
|
17
|
+
next unless node
|
18
|
+
node[:filename] = @filename
|
19
|
+
insert_node(node, level)
|
20
|
+
end
|
21
|
+
|
22
|
+
ary = Array.new(@node.items)
|
23
|
+
ary.each(&:orphan!)
|
24
|
+
ary
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param filename [String] input file for reading
|
28
|
+
# @param on_error [Proc] proc object {|error| ...}
|
29
|
+
def initialize(filename, on_error = nil)
|
30
|
+
@filename = filename
|
31
|
+
@on_error = on_error
|
32
|
+
@node = Node.new(id: filename)
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
# @param [Enumerator<String>] text
|
38
|
+
# @return [Array<String>] where each item represents one node
|
39
|
+
def read_nodes(text, &block)
|
40
|
+
quote, node = false, []
|
41
|
+
text.each do |line|
|
42
|
+
if line.start_with?('#') && !quote && !node.empty?
|
43
|
+
block.call(node.join("\n"))
|
44
|
+
node = []
|
45
|
+
end
|
46
|
+
node << line
|
47
|
+
quote = !quote if line.start_with?('```')
|
48
|
+
end
|
49
|
+
block.call(node.join("\n")) unless node.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
def insert_node(node, level)
|
53
|
+
parent = @node
|
54
|
+
while parent.last_item && (parent.nesting_level + 1) < level
|
55
|
+
parent = parent.last_item
|
56
|
+
end
|
57
|
+
unless (parent.nesting_level + 1) == level
|
58
|
+
msg = "invalid header level: #{'#' * level} #{node.title}"
|
59
|
+
@on_error.call(msg) if @on_error
|
60
|
+
parent = @node
|
61
|
+
end
|
62
|
+
parent << node
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_node(text)
|
66
|
+
text += "\n" unless text.end_with?("\n")
|
67
|
+
parts = NODE_REGX.match(text)
|
68
|
+
lv = parts[1] || ''
|
69
|
+
id = parts[3] || ''
|
70
|
+
title = parts[4] || ''
|
71
|
+
body = parts[7] || ''
|
72
|
+
meta = {}
|
73
|
+
meta.merge!(parse_meta(parts[6])) if parts[6]
|
74
|
+
[lv.size, Node.new(id: id, title: title, body: body.strip, meta: meta)]
|
75
|
+
rescue StandardError
|
76
|
+
msg = "invalid node format: #{text}"
|
77
|
+
@on_error.call(msg) if @on_error
|
78
|
+
[nil, nil]
|
79
|
+
end
|
80
|
+
|
81
|
+
def parse_meta(text)
|
82
|
+
text.strip.split(/[;,\n]/).inject({}) do |h, i|
|
83
|
+
pair = /\s?(\w*):\s*(.*)/.match(i)
|
84
|
+
h.merge(pair[1].to_sym => pair[2])
|
85
|
+
end || {}
|
86
|
+
rescue StandardError
|
87
|
+
msg = "invalid meta format:\n{{#{text}}}"
|
88
|
+
@on_error.call(msg) if @on_error
|
89
|
+
{}
|
90
|
+
end
|
91
|
+
|
92
|
+
NODE_REGX =
|
93
|
+
/^(\#+)[ ]*(\[([^\[\]\s]*)\][ ]*)?([\s\S]*?)\n({{([\s\S]*?)}})?(.*)$/m
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
require_relative 'service'
|
5
|
+
|
6
|
+
module Clerq
|
7
|
+
module Services
|
8
|
+
|
9
|
+
# Render @object trough ERB temlate
|
10
|
+
#
|
11
|
+
# Usage
|
12
|
+
# erb = "id: <%= @object.id %>\ntitle: <%= @object.title %>\n"
|
13
|
+
# obj = Node.new(id: 'uc', title: 'Use Cases', meta: {skip_meta: true})
|
14
|
+
# txt = RenderErb.call(erb, obj) # or RenderErb.(erb, obj)
|
15
|
+
class RenderErb < Service
|
16
|
+
|
17
|
+
def call
|
18
|
+
tt = ERB.new(@erb, nil, "-")
|
19
|
+
tt.result(binding)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(erb: , object: )
|
23
|
+
@erb = erb
|
24
|
+
@object = object
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -1,26 +1,22 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
require_relative '
|
3
|
-
require_relative '
|
2
|
+
require_relative 'service'
|
3
|
+
require_relative 'query_template'
|
4
|
+
require_relative 'render_erb'
|
4
5
|
|
5
6
|
module Clerq
|
6
|
-
module
|
7
|
+
module Services
|
7
8
|
|
8
|
-
class
|
9
|
+
class RenderNode < Service
|
9
10
|
|
10
11
|
def call
|
11
12
|
@erb = QueryTemplate.(@ett)
|
12
|
-
|
13
|
-
RenderErb.(erb: @erb, object: asmb)
|
14
|
-
# TODO Clerq.binaries.save(@out, text)
|
15
|
-
rescue StandardError => e
|
16
|
-
raise Failure, e.message
|
13
|
+
RenderErb.(erb: @erb, object: @node)
|
17
14
|
end
|
18
15
|
|
19
|
-
def initialize(template
|
16
|
+
def initialize(node: , template:)
|
20
17
|
check_string_argument!(template, 'template')
|
21
18
|
check_string_empty!(template, 'template')
|
22
|
-
|
23
|
-
@qry = query
|
19
|
+
@node = node
|
24
20
|
@ett = template
|
25
21
|
end
|
26
22
|
|