clerq 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|