clerq 0.1.0 → 0.3.3
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 -3
- data/CHANGELOG.md +49 -0
- data/Gemfile.lock +9 -9
- data/README.md +260 -133
- data/Rakefile +1 -0
- data/clerq.gemspec +6 -6
- data/clerq.thor +28 -0
- data/docs/README.md +408 -0
- data/lib/assets/knb/SRS-IEEE-830-1998.md +293 -0
- data/lib/assets/knb/SRS-RUP.md +283 -0
- data/lib/assets/knb/business-case.md +135 -0
- data/lib/assets/knb/ears-with-examples.md +101 -0
- data/lib/assets/knb/problem-statement.md +8 -0
- data/lib/assets/knb/product-statement.md +8 -0
- data/lib/assets/knb/requirement-attributes.md +26 -0
- data/lib/assets/knb/requirement-classification.md +27 -0
- data/lib/assets/knb/requirement-life-cycle.md +47 -0
- data/lib/assets/knb/requirement-quality.md +13 -0
- data/lib/assets/knb/use-case.md +39 -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/clerq.thor.tt +32 -5
- data/lib/assets/new/content.md.tt +3 -40
- data/lib/assets/tt/default.md.erb +23 -42
- data/lib/assets/tt/pandoc.md.erb +11 -8
- data/lib/clerq.rb +57 -12
- data/lib/clerq/cli.rb +77 -60
- data/lib/clerq/entities.rb +0 -1
- data/lib/clerq/entities/node.rb +135 -115
- data/lib/clerq/properties.rb +1 -3
- data/lib/clerq/repositories.rb +2 -4
- data/lib/clerq/repositories/file_repository.rb +59 -0
- data/lib/clerq/repositories/node_repository.rb +72 -30
- data/lib/clerq/repositories/text_repository.rb +47 -0
- data/lib/clerq/services.rb +8 -0
- data/lib/clerq/services/check_assembly.rb +108 -0
- data/lib/clerq/{interactors → services}/create_node.rb +12 -11
- data/lib/clerq/services/load_assembly.rb +54 -0
- data/lib/clerq/services/query_node.rb +72 -0
- data/lib/clerq/services/query_template.rb +26 -0
- data/lib/clerq/services/read_node.rb +101 -0
- data/lib/clerq/services/render_erb.rb +29 -0
- data/lib/clerq/services/render_node.rb +37 -0
- data/lib/clerq/services/service.rb +19 -0
- data/lib/clerq/settings.rb +2 -2
- data/lib/clerq/version.rb +1 -1
- metadata +49 -37
- data/TODO.md +0 -3
- data/lib/assets/tt/gitlab.md.erb +0 -93
- data/lib/assets/tt/raw.md.erb +0 -23
- data/lib/clerq/entities/template.rb +0 -19
- data/lib/clerq/gateways.rb +0 -3
- data/lib/clerq/gateways/gateway.rb +0 -17
- data/lib/clerq/gateways/in_files.rb +0 -36
- data/lib/clerq/gateways/in_memory.rb +0 -35
- data/lib/clerq/interactors.rb +0 -5
- data/lib/clerq/interactors/check_nodes.rb +0 -81
- data/lib/clerq/interactors/compile_nodes.rb +0 -31
- data/lib/clerq/interactors/interactor.rb +0 -28
- data/lib/clerq/interactors/join_nodes.rb +0 -59
- data/lib/clerq/interactors/query_nodes.rb +0 -62
- data/lib/clerq/repositories/in_memory.rb +0 -45
- data/lib/clerq/repositories/node_reader.rb +0 -107
- data/lib/clerq/repositories/repository.rb +0 -11
- data/lib/clerq/repositories/template_repository.rb +0 -53
- data/lib/clerq/templater.rb +0 -32
data/lib/clerq/properties.rb
CHANGED
@@ -4,9 +4,7 @@ module Clerq
|
|
4
4
|
def property(name, options = {}, &validation)
|
5
5
|
default_value = options[:default]
|
6
6
|
define_method name do
|
7
|
-
|
8
|
-
v = default_value unless v
|
9
|
-
v
|
7
|
+
instance_variable_get("@#{name}") || default_value
|
10
8
|
end
|
11
9
|
|
12
10
|
define_method "#{name}=" do |val|
|
data/lib/clerq/repositories.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require_relative 'repositories/
|
2
|
-
require_relative 'repositories/in_memory'
|
3
|
-
require_relative 'repositories/node_reader'
|
1
|
+
require_relative 'repositories/file_repository'
|
4
2
|
require_relative 'repositories/node_repository'
|
5
|
-
require_relative 'repositories/
|
3
|
+
require_relative 'repositories/text_repository'
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Clerq
|
4
|
+
module Repositories
|
5
|
+
|
6
|
+
# The class provides File and Dir functions that executed relativly path
|
7
|
+
# provided in constructor.
|
8
|
+
#
|
9
|
+
# Usage:
|
10
|
+
# FileRepository.new(path: Dir.pwd, pattern: '*.*')
|
11
|
+
# FileRepository.new(path: Dir.pwd, pattern: ['*.rb', '*.md'])
|
12
|
+
class FileRepository
|
13
|
+
attr_reader :path
|
14
|
+
attr_reader :patt
|
15
|
+
|
16
|
+
# @param path [String]
|
17
|
+
# @param pattern [String, Array<String>]
|
18
|
+
def initialize(path: Dir.pwd, pattern: '*.*')
|
19
|
+
# TODO check that path exists and save it in full form
|
20
|
+
unless Dir.exist?(path)
|
21
|
+
msg = "'#{path}' directory does not exist!"
|
22
|
+
raise ArgumentError, msg
|
23
|
+
end
|
24
|
+
@path = path
|
25
|
+
@patt = pattern
|
26
|
+
end
|
27
|
+
|
28
|
+
def inside
|
29
|
+
Dir.chdir(@path) { yield }
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
# @param pattern [String, Array<String>]
|
35
|
+
def glob(pattern = '')
|
36
|
+
pt = pattern.empty? ? @patt : pattern
|
37
|
+
pt = [pt] if pt.is_a?(String)
|
38
|
+
pt = pt.map{|p| p = File.join('**', p)}
|
39
|
+
Dir.chdir(@path) { Dir.glob pt }
|
40
|
+
end
|
41
|
+
|
42
|
+
def read(filename)
|
43
|
+
File.read(File.join @path, filename)
|
44
|
+
end
|
45
|
+
|
46
|
+
def write(filename, content)
|
47
|
+
join = File.join(@path, filename)
|
48
|
+
if File.exist?(join)
|
49
|
+
errmsg = "File '#{join}' alredy exists!"
|
50
|
+
raise StandardError, errmsg
|
51
|
+
end
|
52
|
+
File.write(join, content)
|
53
|
+
join
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -1,55 +1,97 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
3
|
require_relative '../entities'
|
4
|
-
require_relative '
|
4
|
+
require_relative 'file_repository'
|
5
|
+
require_relative '../services/read_node'
|
5
6
|
include Clerq::Entities
|
7
|
+
include Clerq::Services
|
6
8
|
|
7
9
|
module Clerq
|
8
10
|
module Repositories
|
9
11
|
|
10
|
-
class NodeRepository <
|
11
|
-
def initialize(path = Dir.pwd)
|
12
|
-
@path = path
|
13
|
-
@items = []
|
14
|
-
end
|
12
|
+
class NodeRepository < FileRepository
|
15
13
|
|
16
|
-
def
|
17
|
-
|
18
|
-
return @items.find {|i| i.id == id} if id
|
19
|
-
@items
|
14
|
+
def initialize(path: Dir.pwd, pattern: '*.md')
|
15
|
+
super(path: path, pattern: pattern)
|
20
16
|
end
|
21
17
|
|
22
18
|
def save(node)
|
23
|
-
|
24
|
-
|
19
|
+
check! node
|
20
|
+
write("#{node.id}.md", markup(node))
|
21
|
+
end
|
22
|
+
|
23
|
+
# asseble repository nodes hierarchy
|
24
|
+
# @return [Node]
|
25
|
+
def assemble(on_parse: nil, on_error: nil)
|
26
|
+
@node = Node.new(id: 'join', title: Clerq.title)
|
27
|
+
loadn = load(on_parse: on_parse, on_error: on_error)
|
28
|
+
loadn.each{|n| @node << n}
|
29
|
+
subo!
|
30
|
+
eqid!
|
31
|
+
if @node.items.size == 1
|
32
|
+
@node = @node.items.first
|
33
|
+
@node.orphan!
|
34
|
+
end
|
35
|
+
@node
|
25
36
|
end
|
26
37
|
|
27
38
|
protected
|
28
39
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
40
|
+
def load(on_parse: nil, on_error: nil)
|
41
|
+
inside do
|
42
|
+
[].tap do |ary|
|
43
|
+
glob.each do |file|
|
44
|
+
on_parse.call(file) if on_parse
|
45
|
+
tmp = ReadNode.(file, on_error)
|
46
|
+
tmp.each{|node| ary << node }
|
36
47
|
end
|
37
48
|
end
|
38
49
|
end
|
50
|
+
end
|
39
51
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
52
|
+
def subo!
|
53
|
+
@node.items
|
54
|
+
.select{|n| n[:parent] && n[:parent] != n.parent.id}
|
55
|
+
.each{|n|
|
56
|
+
parent = @node.node(n[:parent])
|
57
|
+
next unless parent
|
58
|
+
parent << n
|
59
|
+
@node.items.delete(n)
|
60
|
+
n.meta.delete(:parent)
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def eqid!
|
65
|
+
counter = {}
|
66
|
+
@node.select{|n| n.id.empty?}.each do |n|
|
67
|
+
# TODO maybe just .to_a.drop(1).select ?
|
68
|
+
next if n == @node
|
69
|
+
index = counter[n.parent] || 1
|
70
|
+
counter[n.parent] = index + 1
|
71
|
+
id = index.to_s.rjust(2, '0')
|
72
|
+
id = '.' + id unless n.parent == @node
|
73
|
+
n.id = id
|
51
74
|
end
|
75
|
+
end
|
52
76
|
|
77
|
+
def check!(node)
|
78
|
+
return if node.is_a? Node
|
79
|
+
errmsg = "Invalid argument"
|
80
|
+
raise ArgumentError, errmsg, caller #caller[1..-1]
|
81
|
+
end
|
82
|
+
|
83
|
+
def markup(n)
|
84
|
+
head = "# [#{n.id}] #{n.title}"
|
85
|
+
meta = n.meta.empty? ? '' : n.meta
|
86
|
+
.map{|k,v| "#{k}: #{v}"}
|
87
|
+
.unshift("{{")
|
88
|
+
.push("}}")
|
89
|
+
.join("\n")
|
90
|
+
[head].tap do |txt|
|
91
|
+
txt << "\n#{meta}" unless meta.empty?
|
92
|
+
txt << "\n\n#{n.body}" unless n.body.empty?
|
93
|
+
end.join + "\n"
|
94
|
+
end
|
53
95
|
end
|
54
96
|
|
55
97
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require_relative 'file_repository'
|
3
|
+
|
4
|
+
module Clerq
|
5
|
+
module Repositories
|
6
|
+
|
7
|
+
class TextRepository < FileRepository
|
8
|
+
|
9
|
+
def initialize(path: Dir.pwd, pattern: ['*.md.erb', '*.md.tt'])
|
10
|
+
super(path: path, pattern: pattern)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Return template body @param name [String]
|
14
|
+
def text(name)
|
15
|
+
filename = find(name)
|
16
|
+
if filename.empty?
|
17
|
+
err = "File '#{name}' not found"
|
18
|
+
raise StandardError, err
|
19
|
+
end
|
20
|
+
read(filename)
|
21
|
+
end
|
22
|
+
|
23
|
+
# def find(filename)
|
24
|
+
# inside do
|
25
|
+
# return filename if File.exist?(filename)
|
26
|
+
# @patt.each do |p|
|
27
|
+
# fn = "#{filename}#{p[1..-1]}"
|
28
|
+
# return fn if File.exist?(fn)
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
# ''
|
32
|
+
# end
|
33
|
+
|
34
|
+
def find(name)
|
35
|
+
inside {
|
36
|
+
return name if File.exist?(name) and !File.directory?(name)}
|
37
|
+
all = glob
|
38
|
+
pos = @patt.map{|p| "#{name}#{p[1..-1]}"}.unshift(name)
|
39
|
+
all.find(lambda {''}){|n|
|
40
|
+
pos.include?(n) || n.start_with?(*pos) || n.end_with?(*pos)
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -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,22 +1,23 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
require_relative "
|
3
|
+
require_relative "service"
|
4
|
+
require_relative "query_template"
|
5
|
+
require_relative "../entities/node"
|
4
6
|
|
5
7
|
module Clerq
|
6
|
-
module
|
8
|
+
module Services
|
7
9
|
|
8
|
-
|
10
|
+
# Creates new node in repository according to provided parameters
|
11
|
+
# or raises CreateNode::Failure when
|
12
|
+
# * template not found
|
13
|
+
# * or repository contains a node with the same id
|
14
|
+
class CreateNode < Service
|
9
15
|
|
10
16
|
def call
|
11
|
-
|
12
|
-
|
13
|
-
raise Failure, "Template not found" unless tt
|
14
|
-
# TODO not just `@body = tt.body` but build `@body` in accordance with tt.body
|
15
|
-
@body = tt.body
|
16
|
-
end
|
17
|
-
node = Clerq::Entities::Node.new(
|
17
|
+
@body = QueryTemplate.(@template) if @body.empty? && !@template.empty?
|
18
|
+
@node = Clerq::Entities::Node.new(
|
18
19
|
id: @id, title: @title, body: @body, meta: @meta)
|
19
|
-
|
20
|
+
Clerq.node_repository.save(@node)
|
20
21
|
end
|
21
22
|
|
22
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
|