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,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,24 +1,23 @@
1
1
  # encoding: UTF-8
2
2
 
3
- require_relative "interactor"
3
+ require_relative "service"
4
4
  require_relative "query_template"
5
+ require_relative "../entities/node"
5
6
 
6
7
  module Clerq
7
- module Interactors
8
+ module Services
8
9
 
9
10
  # Creates new node in repository according to provided parameters
10
11
  # or raises CreateNode::Failure when
11
12
  # * template not found
12
13
  # * or repository contains a node with the same id
13
- class CreateNode < Interactor
14
+ class CreateNode < Service
14
15
 
15
16
  def call
16
17
  @body = QueryTemplate.(@template) if @body.empty? && !@template.empty?
17
18
  @node = Clerq::Entities::Node.new(
18
19
  id: @id, title: @title, body: @body, meta: @meta)
19
20
  Clerq.node_repository.save(@node)
20
- rescue StandardError => e
21
- raise Failure, e.message
22
21
  end
23
22
 
24
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
@@ -1,16 +1,16 @@
1
1
  # encoding: UTF-8
2
2
 
3
- require_relative "interactor"
3
+ require_relative "service"
4
4
 
5
5
  module Clerq
6
- module Interactors
6
+ module Services
7
7
 
8
8
  # Provides repository nodes assembly
9
9
  # @param [Sring] optional query string
10
10
  # @return [Node] assembly of repository nodes
11
11
  #
12
12
  # Usage
13
- # QueryAssembly.()
13
+ # QueryAssembly.(asse:, query:)
14
14
  # QueryAssembly.(query_string)
15
15
  #
16
16
  # Rules for root node:
@@ -20,14 +20,12 @@ module Clerq
20
20
  # when query returns mare than one nodes, then
21
21
  # * it creates empty root node with meta[:query]
22
22
  # * and places found nodes to the root
23
- class QueryAssembly < Interactor
23
+ class QueryNode < Service
24
24
 
25
25
  def call
26
26
  proc = prepare_query unless @query.empty?
27
- join = Clerq.node_repository.assemble
28
- return join if @query.empty?
29
27
 
30
- arry = join.select{|node| proc.call(node) }
28
+ arry = @assembly.select{|node| proc.call(node) }
31
29
  return arry.first.orphan! if arry.size == 1
32
30
 
33
31
  node = Node.new(title: Clerq.title, meta: {query: @query})
@@ -37,12 +35,17 @@ module Clerq
37
35
 
38
36
  protected
39
37
 
40
- def initialize(query = '')
38
+ def initialize(assembly: , query: )
39
+ unless assembly.is_a? Node
40
+ msg = "Invailid argument 'assembly'"
41
+ raise ArgumentError, msg
42
+ end
41
43
  unless query.is_a? String
42
44
  msg = "Invailid argument 'query'"
43
45
  raise ArgumentError, msg
44
46
  end
45
47
  @query = query
48
+ @assembly = assembly
46
49
  end
47
50
 
48
51
  def prepare_query
@@ -50,13 +53,13 @@ module Clerq
50
53
  Node.new.select{|node| proc.call(node)}
51
54
  proc
52
55
  rescue Exception => e
53
- msg = "Invalid query #{@query} (#{e.message})\n#{USAGE}"
54
- raise self.class::Failure, msg
56
+ msg = "Invalid query format #{@query} (#{e.message})\n#{USAGE}"
57
+ raise ArgumentError, msg
55
58
  end
56
59
 
57
60
  USAGE = <<~EOF
58
- QueryNode.call(query) evaluates query paramater
59
- by eval() as 'Proc.new { |node| eval(@query) }'
61
+ QueryAssembly.call(query: ) evaluates query paramater
62
+ by eval() as 'Proc.new { |node| eval(query) }'
60
63
  e.g. the followed queries are valid:
61
64
  node.id == 'uc'
62
65
  node.title == 'Introduction'
@@ -1,19 +1,14 @@
1
1
  # encoding: UTF-8
2
2
 
3
- require_relative "interactor"
3
+ require_relative "service"
4
4
 
5
5
  module Clerq
6
- module Interactors
6
+ module Services
7
7
 
8
- class QueryTemplate < Interactor
9
-
10
- private_class_method :new
8
+ class QueryTemplate < Service
11
9
 
12
10
  def call
13
11
  Clerq.text_repository.text(@template)
14
- rescue StandardError
15
- err = "'#{@template}' template not found!"
16
- raise Failure, err
17
12
  end
18
13
 
19
14
  # @param template [String] required name of template
@@ -0,0 +1,98 @@
1
+ # encoding: UTF-8
2
+ require_relative 'service'
3
+ require_relative '../entities/node'
4
+ include Clerq::Entities
5
+
6
+ module Clerq
7
+ module Services
8
+
9
+ # The service reads nodes from file and returns array of nodes
10
+ # It returns array because the file can have a few root nodes
11
+ class ReadNode < Service
12
+
13
+ def call
14
+ text = File.foreach(@filename)
15
+ read_nodes(text) do |node_text|
16
+ level, node = parse_node(node_text)
17
+ next unless node
18
+ node[:filename] = @filename
19
+ insert_node(node, level)
20
+ end
21
+
22
+ ary = Array.new(@node.items)
23
+ ary.each(&:orphan!)
24
+ ary
25
+ end
26
+
27
+ # @param filename [String] input file for reading
28
+ # @param on_error [Proc] proc object {|error| ...}
29
+ def initialize(filename, on_error = nil)
30
+ @filename = filename
31
+ @on_error = on_error
32
+ @node = Node.new(id: filename)
33
+ end
34
+
35
+ protected
36
+
37
+ # @param [Enumerator<String>] text
38
+ # @return [Array<String>] where each item represents one node
39
+ def read_nodes(text, &block)
40
+ quote, node = false, []
41
+ text.each do |line|
42
+ if line.start_with?('#') && !quote && !node.empty?
43
+ block.call(node.join("\n"))
44
+ node = []
45
+ end
46
+ node << line
47
+ quote = !quote if line.start_with?('```')
48
+ end
49
+ block.call(node.join("\n")) unless node.empty?
50
+ end
51
+
52
+ def insert_node(node, level)
53
+ parent = @node
54
+ while parent.last_item && (parent.nesting_level + 1) < level
55
+ parent = parent.last_item
56
+ end
57
+ unless (parent.nesting_level + 1) == level
58
+ msg = "invalid header level: #{'#' * level} #{node.title}"
59
+ @on_error.call(msg) if @on_error
60
+ parent = @node
61
+ end
62
+ parent << node
63
+ end
64
+
65
+ def parse_node(text)
66
+ text += "\n" unless text.end_with?("\n")
67
+ parts = NODE_REGX.match(text)
68
+ lv = parts[1] || ''
69
+ id = parts[3] || ''
70
+ title = parts[4] || ''
71
+ body = parts[7] || ''
72
+ meta = {}
73
+ meta.merge!(parse_meta(parts[6])) if parts[6]
74
+ [lv.size, Node.new(id: id, title: title, body: body.strip, meta: meta)]
75
+ rescue StandardError
76
+ msg = "invalid node format: #{text}"
77
+ @on_error.call(msg) if @on_error
78
+ [nil, nil]
79
+ end
80
+
81
+ def parse_meta(text)
82
+ text.strip.split(/[;,\n]/).inject({}) do |h, i|
83
+ pair = /\s?(\w*):\s*(.*)/.match(i)
84
+ h.merge(pair[1].to_sym => pair[2])
85
+ end || {}
86
+ rescue StandardError
87
+ msg = "invalid meta format:\n{{#{text}}}"
88
+ @on_error.call(msg) if @on_error
89
+ {}
90
+ end
91
+
92
+ NODE_REGX =
93
+ /^(\#+)[ ]*(\[([^\[\]\s]*)\][ ]*)?([\s\S]*?)\n({{([\s\S]*?)}})?(.*)$/m
94
+
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'erb'
4
+ require_relative 'service'
5
+
6
+ module Clerq
7
+ module Services
8
+
9
+ # Render @object trough ERB temlate
10
+ #
11
+ # Usage
12
+ # erb = "id: <%= @object.id %>\ntitle: <%= @object.title %>\n"
13
+ # obj = Node.new(id: 'uc', title: 'Use Cases', meta: {skip_meta: true})
14
+ # txt = RenderErb.call(erb, obj) # or RenderErb.(erb, obj)
15
+ class RenderErb < Service
16
+
17
+ def call
18
+ tt = ERB.new(@erb, nil, "-")
19
+ tt.result(binding)
20
+ end
21
+
22
+ def initialize(erb: , object: )
23
+ @erb = erb
24
+ @object = object
25
+ end
26
+ end
27
+
28
+ end
29
+ end
@@ -1,26 +1,22 @@
1
1
  # encoding: UTF-8
2
- require_relative 'interactor'
3
- require_relative '../render_erb'
2
+ require_relative 'service'
3
+ require_relative 'query_template'
4
+ require_relative 'render_erb'
4
5
 
5
6
  module Clerq
6
- module Interactors
7
+ module Services
7
8
 
8
- class RenderAssembly < Interactor
9
+ class RenderNode < Service
9
10
 
10
11
  def call
11
12
  @erb = QueryTemplate.(@ett)
12
- asmb = QueryAssembly.(@qry)
13
- RenderErb.(erb: @erb, object: asmb)
14
- # TODO Clerq.binaries.save(@out, text)
15
- rescue StandardError => e
16
- raise Failure, e.message
13
+ RenderErb.(erb: @erb, object: @node)
17
14
  end
18
15
 
19
- def initialize(template:, query: '')
16
+ def initialize(node: , template:)
20
17
  check_string_argument!(template, 'template')
21
18
  check_string_empty!(template, 'template')
22
- check_string_argument!(query, 'query')
23
- @qry = query
19
+ @node = node
24
20
  @ett = template
25
21
  end
26
22