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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/CHANGELOG.md +14 -2
  4. data/Gemfile.lock +24 -0
  5. data/README.md +107 -30
  6. data/clerq.thor +28 -0
  7. data/docs/README.md +408 -0
  8. data/lib/assets/knb/business-case.md +135 -0
  9. data/lib/assets/knb/requirement-life-cycle.md +47 -0
  10. data/lib/assets/knb/vision-document.md +191 -0
  11. data/lib/assets/lib/clerq_doc.thor +119 -0
  12. data/lib/assets/lib/colonize_repo.rb +82 -0
  13. data/lib/assets/lib/spec/colonize_repo_spec.rb +85 -0
  14. data/lib/assets/new/content.md.tt +1 -0
  15. data/lib/assets/tt/default.md.erb +1 -1
  16. data/lib/assets/tt/pandoc.md.erb +6 -6
  17. data/lib/clerq.rb +7 -2
  18. data/lib/clerq/cli.rb +33 -47
  19. data/lib/clerq/entities/node.rb +11 -5
  20. data/lib/clerq/repositories.rb +0 -1
  21. data/lib/clerq/repositories/file_repository.rb +1 -0
  22. data/lib/clerq/repositories/node_repository.rb +7 -6
  23. data/lib/clerq/services.rb +8 -0
  24. data/lib/clerq/services/check_assembly.rb +108 -0
  25. data/lib/clerq/{interactors → services}/create_node.rb +4 -5
  26. data/lib/clerq/services/load_assembly.rb +54 -0
  27. data/lib/clerq/{interactors/query_assembly.rb → services/query_node.rb} +15 -12
  28. data/lib/clerq/{interactors → services}/query_template.rb +3 -8
  29. data/lib/clerq/services/read_node.rb +98 -0
  30. data/lib/clerq/services/render_erb.rb +29 -0
  31. data/lib/clerq/{interactors/render_assembly.rb → services/render_node.rb} +8 -12
  32. data/lib/clerq/services/service.rb +19 -0
  33. data/lib/clerq/version.rb +1 -1
  34. metadata +21 -12
  35. data/TODO.md +0 -7
  36. data/lib/clerq/interactors.rb +0 -5
  37. data/lib/clerq/interactors/check_assembly.rb +0 -77
  38. data/lib/clerq/interactors/interactor.rb +0 -26
  39. data/lib/clerq/render_erb.rb +0 -33
  40. 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.
@@ -12,7 +12,7 @@ class MarkupNode < SimpleDelegator
12
12
  hsh = {}.merge(super)
13
13
  hsh.delete(:parent)
14
14
  hsh.delete(:order_index)
15
- hsh.delete(:file_name)
15
+ hsh.delete(:filename)
16
16
  return '' if hsh.empty?
17
17
 
18
18
  hsh.map{|k, v| "#{k}: #{v}"}
@@ -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(:file_name)
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
- id = 'p' + id if id[0] == '0'
41
- id.downcase
42
- .gsub(/[^A-Za-z0-9]{1,}/, '-')
43
- .gsub(/^-/, '')
44
- .gsub(/-$/, '')
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
@@ -1,10 +1,9 @@
1
1
  require_relative 'clerq/version'
2
2
  require_relative 'clerq/entities'
3
- require_relative 'clerq/interactors'
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
@@ -1,8 +1,8 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  require 'thor'
4
- require_relative 'interactors'
5
- include Clerq::Interactors
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 thor_file_name(str);
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: thor_file_name(project)},
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
- content = RenderAssembly.(template: template, query: query)
93
- File.write(build, content)
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 RenderAssembly::Failure => e
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
- errors = CheckAssembly.()
103
- if errors.empty?
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 = QueryAssembly.(options[:query] || '')
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 QueryAssembly::Failure => e
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
 
@@ -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
- order.split(/ /).each do |o|
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
@@ -1,4 +1,3 @@
1
- require_relative 'repositories/node_reader'
2
1
  require_relative 'repositories/file_repository'
3
2
  require_relative 'repositories/node_repository'
4
3
  require_relative 'repositories/text_repository'
@@ -50,6 +50,7 @@ module Clerq
50
50
  raise StandardError, errmsg
51
51
  end
52
52
  File.write(join, content)
53
+ join
53
54
  end
54
55
 
55
56
  end
@@ -2,8 +2,9 @@
2
2
 
3
3
  require_relative '../entities'
4
4
  require_relative 'file_repository'
5
- require_relative 'node_reader'
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
- # TODO: what to do with errors?
44
- tmp = NodeReader.(file)
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