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,82 @@
|
|
1
|
+
require 'clerq'
|
2
|
+
require 'erb'
|
3
|
+
include Clerq::Entities
|
4
|
+
include Clerq::Services
|
5
|
+
|
6
|
+
# Creates clerq repository sources in current work directory
|
7
|
+
# Warning! Change work dierectory before calling for the serice
|
8
|
+
# Usage
|
9
|
+
# node = Clerq.node_repository.assembly
|
10
|
+
# Dir.chdir(Clerq.src) { ColonizeRepo.(node) }
|
11
|
+
class ColonizeRepo < Service
|
12
|
+
private_class_method :new
|
13
|
+
|
14
|
+
def call
|
15
|
+
write(@node)
|
16
|
+
end
|
17
|
+
|
18
|
+
# TODO callback? and calback for ReadNode.()?
|
19
|
+
def write(node)
|
20
|
+
dir = folder(node)
|
21
|
+
src = source(node)
|
22
|
+
txt = text(node)
|
23
|
+
unless dir.empty? || Dir.exist?(dir)
|
24
|
+
Dir.mkdir(dir)
|
25
|
+
@on_create_dir.call(dir) if @on_create_dir
|
26
|
+
end
|
27
|
+
File.write(src, txt)
|
28
|
+
@on_create_file.call(src) if @on_create_file
|
29
|
+
node.items.reject{|n| n.items.empty?}.each{|n| write(n)}
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param node [Node] the node for colonization
|
33
|
+
# @param on_create_file [Block(arg)] on create new file callback
|
34
|
+
# @param on_create_dir [Block(arg)] on create new directory callback
|
35
|
+
def initialize(node, on_create_dir = nil, on_create_file = nil)
|
36
|
+
@node = node
|
37
|
+
@on_create_dir = on_create_dir
|
38
|
+
@on_create_file = on_create_file
|
39
|
+
end
|
40
|
+
|
41
|
+
def source(node)
|
42
|
+
fld = folder(node)
|
43
|
+
src = filename(node)
|
44
|
+
fld.empty? ? src : File.join(fld, src)
|
45
|
+
end
|
46
|
+
|
47
|
+
def folder(node)
|
48
|
+
dir = ''
|
49
|
+
n = node
|
50
|
+
while n != @node.root && n.parent != @node.root
|
51
|
+
dir = File.join("#{n.parent.id} #{n.parent.title}", dir)
|
52
|
+
n = n.parent
|
53
|
+
end
|
54
|
+
dir
|
55
|
+
end
|
56
|
+
|
57
|
+
def filename(node)
|
58
|
+
"#{node.id} #{node.title}.md"
|
59
|
+
end
|
60
|
+
|
61
|
+
# TODO replace to services
|
62
|
+
def text(node)
|
63
|
+
RenderErb.(erb: TEMPLATE, object: node)
|
64
|
+
end
|
65
|
+
|
66
|
+
TEMPLATE = <<~EOF
|
67
|
+
# <%= @object.title %>
|
68
|
+
{{id: <%= @object.id %><%= ", parent: " + @object.parent.id if @object.parent %>, order_index: <%= @object.items.map(&:id).join(' ') %>}}
|
69
|
+
|
70
|
+
<%= @object.body %>
|
71
|
+
|
72
|
+
<% for n in @object.items -%>
|
73
|
+
<% next unless n.items.empty? -%>
|
74
|
+
## <%= n.title %>
|
75
|
+
{{id: <%= n.id %>}}
|
76
|
+
|
77
|
+
<%= n.body %>
|
78
|
+
|
79
|
+
<% end %>
|
80
|
+
EOF
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'clerq'
|
2
|
+
require 'erb'
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require_relative 'colonize_repo'
|
5
|
+
include Clerq::Entities
|
6
|
+
include Clerq::Services
|
7
|
+
|
8
|
+
describe ColonizeRepo do
|
9
|
+
class FakeWriter < ColonizeRepo
|
10
|
+
public_class_method :new
|
11
|
+
attr_reader :node
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:node) {
|
15
|
+
Node.new(id: '0', title: 'Import').tap{|n|
|
16
|
+
n << Node.new(id: '01', title: 'User')
|
17
|
+
n << Node.new(id: '02', title: 'Func')
|
18
|
+
n.item('01') << Node.new(id: '01.01', title: 'Story 1')
|
19
|
+
n.item('01') << Node.new(id: '01.02', title: 'Story 2')
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
let(:writer) { FakeWriter.new(node) }
|
24
|
+
|
25
|
+
describe '#folder' do
|
26
|
+
it 'must return parent.id + parent.name' do
|
27
|
+
spec = writer.node.map{|n| writer.folder(n)}
|
28
|
+
_(spec).must_equal([
|
29
|
+
'', '', '01 User/', '01 User/', ''
|
30
|
+
])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#filename' do
|
35
|
+
it 'must return node.id + node.title' do
|
36
|
+
spec = writer.node.map{|n| writer.filename(n)}
|
37
|
+
_(spec).must_equal([
|
38
|
+
'0 Import.md', '01 User.md', '01.01 Story 1.md',
|
39
|
+
'01.02 Story 2.md', '02 Func.md'
|
40
|
+
])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#source' do
|
45
|
+
# title and first level shall be paced in root folder
|
46
|
+
it 'must return node.id + node.title' do
|
47
|
+
spec = writer.node.map{|n| writer.source(n)}
|
48
|
+
_(spec).must_equal([
|
49
|
+
'0 Import.md',
|
50
|
+
'01 User.md',
|
51
|
+
'01 User/01.01 Story 1.md',
|
52
|
+
'01 User/01.02 Story 2.md',
|
53
|
+
'02 Func.md'
|
54
|
+
])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#write(node)' do
|
59
|
+
let(:node) {
|
60
|
+
Node.new(id: '0', title: 'Import').tap{|n|
|
61
|
+
n << Node.new(id: '01', title: 'User')
|
62
|
+
n << Node.new(id: '02', title: 'Func')
|
63
|
+
n.item('01') << Node.new(id: '01.01', title: 'Story 1')
|
64
|
+
n.item('01') << Node.new(id: '01.02', title: 'Story 2')
|
65
|
+
n.node('01.01') << Node.new(id: '01.01.01', title: 'Story 1.1')
|
66
|
+
n.node('01.01') << Node.new(id: '01.01.02', title: 'Story 1.2')
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
it 'must build repo' do
|
71
|
+
files = ['0 Import.md', '01 User.md', '01 User/01.01 Story 1.md']
|
72
|
+
Dir.mktmpdir(['clerq']) do |dir|
|
73
|
+
Dir.chdir(dir) do
|
74
|
+
ColonizeRepo.(node)
|
75
|
+
files.each{|fn| _(File.exist?(fn)).must_equal true }
|
76
|
+
_(File.read('0 Import.md')).wont_match "parent:"
|
77
|
+
_(File.read('01 User.md')).must_match "parent: 0"
|
78
|
+
_(File.read('01 User/01.01 Story 1.md')).must_match "parent: 01"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
1
|
# <%=config[:project]%>
|
2
|
+
{{id: 0}}
|
2
3
|
|
3
4
|
This file was created automatically by the `clerq new` command as the root node of your project. To familiarize yourself with how to start writing with the Clerq - please read the [Writing](https://github.com/nvoynov/clerq/blob/master/README.md#writing) section and then just delete the content of the paragraph.
|
data/lib/assets/tt/pandoc.md.erb
CHANGED
@@ -12,7 +12,7 @@ class MarkupNode < SimpleDelegator
|
|
12
12
|
|
13
13
|
hsh = {id: id}.merge(super)
|
14
14
|
hsh.delete(:order_index)
|
15
|
-
hsh.delete(:
|
15
|
+
hsh.delete(:filename)
|
16
16
|
hsh.delete(:parent)
|
17
17
|
[].tap{|ary|
|
18
18
|
ary << "Attribute | Value"
|
@@ -37,11 +37,11 @@ class MarkupNode < SimpleDelegator
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def url(id)
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
40
|
+
r = id.start_with?(/[[:digit:]]/) ? "p#{id}" : id
|
41
|
+
r.downcase
|
42
|
+
.gsub(/[^A-Za-z0-9]{1,}/, '-')
|
43
|
+
.gsub(/^-/, '')
|
44
|
+
.gsub(/-$/, '')
|
45
45
|
end
|
46
46
|
|
47
47
|
class Macro
|
data/lib/clerq.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
require_relative 'clerq/version'
|
2
2
|
require_relative 'clerq/entities'
|
3
|
-
require_relative 'clerq/
|
3
|
+
require_relative 'clerq/services'
|
4
4
|
require_relative 'clerq/repositories'
|
5
5
|
require_relative 'clerq/properties'
|
6
6
|
require_relative 'clerq/settings'
|
7
|
-
require_relative 'clerq/render_erb'
|
8
7
|
require_relative 'clerq/cli'
|
9
8
|
include Clerq::Repositories
|
10
9
|
|
@@ -76,6 +75,12 @@ module Clerq
|
|
76
75
|
@node_repository = repository
|
77
76
|
end
|
78
77
|
|
78
|
+
def assemble(on_parse: nil, on_error: nil)
|
79
|
+
node_repository.assemble(
|
80
|
+
on_parse: on_parse,
|
81
|
+
on_error: on_error)
|
82
|
+
end
|
83
|
+
|
79
84
|
end
|
80
85
|
|
81
86
|
end
|
data/lib/clerq/cli.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
3
|
require 'thor'
|
4
|
-
require_relative '
|
5
|
-
include Clerq::
|
4
|
+
require_relative 'services'
|
5
|
+
include Clerq::Services
|
6
6
|
|
7
7
|
module Clerq
|
8
8
|
|
@@ -23,7 +23,7 @@ module Clerq
|
|
23
23
|
no_commands {
|
24
24
|
# @param [String]
|
25
25
|
# @returns [String] usual name for ruby file
|
26
|
-
def
|
26
|
+
def thor_filename(str);
|
27
27
|
str.split(/[\W+_]/).map(&:downcase).join('_') + '.thor'
|
28
28
|
end
|
29
29
|
|
@@ -44,6 +44,13 @@ module Clerq
|
|
44
44
|
def stop!(msg)
|
45
45
|
raise Thor::Error, msg
|
46
46
|
end
|
47
|
+
|
48
|
+
def query_assembly(query)
|
49
|
+
# TODO pretty errors ...OK, ... 1 error found, ... 2 errors found
|
50
|
+
on_parse = lambda {|src| puts "Reading '#{src}'... "}
|
51
|
+
on_error = lambda {|err| puts "\terror: #{err} "}
|
52
|
+
QueryAssembly.(query: query, on_parse: on_parse, on_error: on_error)
|
53
|
+
end
|
47
54
|
}
|
48
55
|
|
49
56
|
desc "new PROJECT", "Create a new Clerq project"
|
@@ -55,7 +62,7 @@ module Clerq
|
|
55
62
|
tts = [
|
56
63
|
{tt: 'new/README.md.tt', target: 'README.md'},
|
57
64
|
{tt: 'new/clerq.yml.tt', target: Clerq::Settings::STORAGE},
|
58
|
-
{tt: 'new/clerq.thor.tt', target:
|
65
|
+
{tt: 'new/clerq.thor.tt', target: thor_filename(project)},
|
59
66
|
{tt: 'new/content.md.tt', target: File.join(settings.src, "#{project}.md")}
|
60
67
|
]
|
61
68
|
|
@@ -68,6 +75,7 @@ module Clerq
|
|
68
75
|
template(tt[:tt], File.join(Dir.pwd, tt[:target]), config)
|
69
76
|
end
|
70
77
|
directory('tt', File.join(Dir.pwd, 'tt'))
|
78
|
+
directory('lib', File.join(Dir.pwd, 'lib'))
|
71
79
|
say "Project created!"
|
72
80
|
end
|
73
81
|
end
|
@@ -87,68 +95,46 @@ module Clerq
|
|
87
95
|
stop_unless_clerq!
|
88
96
|
document = options[:output] || Clerq.document + '.md'
|
89
97
|
template = options[:tt] || Clerq.template
|
90
|
-
query = options[:query] || ''
|
91
98
|
build = File.join(Clerq.bin, document)
|
92
|
-
|
93
|
-
|
99
|
+
|
100
|
+
node = LoadAssembly.()
|
101
|
+
node = QueryNode.(assembly: node, query: options[:query]) if options[:query]
|
102
|
+
text = RenderNode.(node: node, template: template)
|
103
|
+
File.write(build, text)
|
94
104
|
say "'#{build}' created!"
|
95
|
-
rescue
|
105
|
+
rescue StandardError => e
|
96
106
|
stop!(e.message)
|
97
107
|
end
|
98
108
|
|
99
109
|
desc "check", "Check the project for errors"
|
100
110
|
def check
|
101
111
|
stop_unless_clerq!
|
102
|
-
|
103
|
-
|
104
|
-
say "No errors found"
|
105
|
-
return
|
106
|
-
end
|
107
|
-
|
108
|
-
CHECK_MESSAGES.each do |key, msg|
|
109
|
-
if errors.key?(key)
|
110
|
-
say "The following #{msg[0]}:"
|
111
|
-
errors[key].each{|k,v| say "\t#{k}\t#{msg[1]} #{v.join(', ')}"}
|
112
|
-
end
|
113
|
-
end
|
114
|
-
rescue CheckAssembly::Failure => e
|
115
|
-
stop!(e.message)
|
116
|
-
end
|
117
|
-
|
118
|
-
CHECK_MESSAGES = {
|
119
|
-
nonuniq_ids: ['node identifiers are non-uniqe', 'in'],
|
120
|
-
unknown_parents: ['meta[:parent] not found', 'in'],
|
121
|
-
unknown_references: ['links are unknown', 'in'],
|
122
|
-
unknown_order_index: ['node meta[:order_index] unknown', ':']
|
123
|
-
}.freeze
|
124
|
-
|
125
|
-
desc "node ID [TITLE]", "Create a new node"
|
126
|
-
method_option :template, aliases: "-t", type: :string, desc: "template"
|
127
|
-
def node(id, title = '')
|
128
|
-
stop_unless_clerq!
|
129
|
-
# smells! smells! smells!
|
130
|
-
# TODO interactor must return file name
|
131
|
-
# TODO must not check if file exists - it task of repository
|
132
|
-
file = File.join(Clerq.src, "#{id}.md")
|
133
|
-
stop!("File already exists #{fn}") if File.exist?(file)
|
134
|
-
template = options[:template] || ''
|
135
|
-
CreateNode.(id: id, title: title, template: template)
|
136
|
-
say "'#{file}' created"
|
137
|
-
rescue CreateNode::Failure => e
|
138
|
-
stop!(e.message)
|
112
|
+
puts "Checking assembly for writing errors..."
|
113
|
+
CheckAssembly.(LoadAssembly.())
|
139
114
|
end
|
140
115
|
|
141
116
|
desc "toc [OPTIONS]", "Print the project TOC"
|
142
117
|
method_option :query, aliases: "-q", type: :string, desc: "Query"
|
143
118
|
def toc
|
144
119
|
stop_unless_clerq!
|
145
|
-
node =
|
120
|
+
node = LoadAssembly.()
|
121
|
+
node = QueryNode.(assembly: node, query: options[:query]) if options[:query]
|
146
122
|
puts "% #{node.title}"
|
147
123
|
puts "% #{node[:query]}" if node[:query]
|
148
124
|
node.to_a.drop(1).each{|n|
|
149
125
|
puts "#{' ' * (n.nesting_level - 1)}[#{n.id}] #{n.title}"
|
150
126
|
}
|
151
|
-
rescue
|
127
|
+
rescue StandardError => e
|
128
|
+
stop!(e.message)
|
129
|
+
end
|
130
|
+
|
131
|
+
desc "node ID [TITLE]", "Create a new node"
|
132
|
+
method_option :template, aliases: "-t", type: :string, desc: "template"
|
133
|
+
def node(id, title = '')
|
134
|
+
stop_unless_clerq!
|
135
|
+
fn = CreateNode.(id: id, title: title, template: options[:template] || '')
|
136
|
+
say "'#{fn}' created"
|
137
|
+
rescue StandardError => e
|
152
138
|
stop!(e.message)
|
153
139
|
end
|
154
140
|
|
data/lib/clerq/entities/node.rb
CHANGED
@@ -44,6 +44,8 @@ module Clerq
|
|
44
44
|
raise ArgumentError, "Invalid argument :title" unless title.is_a? String
|
45
45
|
raise ArgumentError, "Invalid argument :body" unless body.is_a? String
|
46
46
|
raise ArgumentError, "Invalid argument :meta" unless meta.is_a? Hash
|
47
|
+
id = meta.delete(:id) if id.empty? && meta[:id]
|
48
|
+
meta.delete(:id) unless id.empty?
|
47
49
|
@parent = nil
|
48
50
|
@items = []
|
49
51
|
@id = id
|
@@ -68,16 +70,20 @@ module Clerq
|
|
68
70
|
@items.find{|r| r.id.eql? id}
|
69
71
|
end
|
70
72
|
|
73
|
+
# @return [Array<String>] of ids from meta[:order_index]
|
74
|
+
def order_index
|
75
|
+
return [] unless @meta[:order_index]
|
76
|
+
@meta[:order_index].strip.gsub(/[\s]{2,}/, ' ').split(/\s/)
|
77
|
+
end
|
78
|
+
|
71
79
|
# @return [Array<Node>] list of child nodes; when the node
|
72
80
|
# metadate has :order_index arrtibute, the list will be
|
73
81
|
# ordered according the attribute value
|
74
82
|
def items
|
75
|
-
return @items if @items.empty?
|
76
|
-
return @items if @meta[:order_index].nil?
|
77
|
-
source = Array.new(@items)
|
78
|
-
order = @meta[:order_index]
|
83
|
+
return @items if @items.empty? || order_index.empty?
|
79
84
|
[].tap do |ordered|
|
80
|
-
|
85
|
+
source = Array.new(@items)
|
86
|
+
order_index.each do |o|
|
81
87
|
e = source.delete(item(o))
|
82
88
|
ordered << e if e
|
83
89
|
end
|
data/lib/clerq/repositories.rb
CHANGED
@@ -2,8 +2,9 @@
|
|
2
2
|
|
3
3
|
require_relative '../entities'
|
4
4
|
require_relative 'file_repository'
|
5
|
-
require_relative '
|
5
|
+
require_relative '../services/read_node'
|
6
6
|
include Clerq::Entities
|
7
|
+
include Clerq::Services
|
7
8
|
|
8
9
|
module Clerq
|
9
10
|
module Repositories
|
@@ -21,9 +22,9 @@ module Clerq
|
|
21
22
|
|
22
23
|
# asseble repository nodes hierarchy
|
23
24
|
# @return [Node]
|
24
|
-
def assemble
|
25
|
+
def assemble(on_parse: nil, on_error: nil)
|
25
26
|
@node = Node.new(id: 'join', title: Clerq.title)
|
26
|
-
loadn = load
|
27
|
+
loadn = load(on_parse: on_parse, on_error: on_error)
|
27
28
|
loadn.each{|n| @node << n}
|
28
29
|
subo!
|
29
30
|
eqid!
|
@@ -36,12 +37,12 @@ module Clerq
|
|
36
37
|
|
37
38
|
protected
|
38
39
|
|
39
|
-
def load
|
40
|
+
def load(on_parse: nil, on_error: nil)
|
40
41
|
inside do
|
41
42
|
[].tap do |ary|
|
42
43
|
glob.each do |file|
|
43
|
-
|
44
|
-
tmp =
|
44
|
+
on_parse.call(file) if on_parse
|
45
|
+
tmp = ReadNode.(file, on_error)
|
45
46
|
tmp.each{|node| ary << node }
|
46
47
|
end
|
47
48
|
end
|