clerq 0.2.0 → 0.3.0

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