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