clerq 0.1.0 → 0.3.3

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -3
  3. data/CHANGELOG.md +49 -0
  4. data/Gemfile.lock +9 -9
  5. data/README.md +260 -133
  6. data/Rakefile +1 -0
  7. data/clerq.gemspec +6 -6
  8. data/clerq.thor +28 -0
  9. data/docs/README.md +408 -0
  10. data/lib/assets/knb/SRS-IEEE-830-1998.md +293 -0
  11. data/lib/assets/knb/SRS-RUP.md +283 -0
  12. data/lib/assets/knb/business-case.md +135 -0
  13. data/lib/assets/knb/ears-with-examples.md +101 -0
  14. data/lib/assets/knb/problem-statement.md +8 -0
  15. data/lib/assets/knb/product-statement.md +8 -0
  16. data/lib/assets/knb/requirement-attributes.md +26 -0
  17. data/lib/assets/knb/requirement-classification.md +27 -0
  18. data/lib/assets/knb/requirement-life-cycle.md +47 -0
  19. data/lib/assets/knb/requirement-quality.md +13 -0
  20. data/lib/assets/knb/use-case.md +39 -0
  21. data/lib/assets/knb/vision-document.md +191 -0
  22. data/lib/assets/lib/clerq_doc.thor +119 -0
  23. data/lib/assets/lib/colonize_repo.rb +82 -0
  24. data/lib/assets/lib/spec/colonize_repo_spec.rb +85 -0
  25. data/lib/assets/new/clerq.thor.tt +32 -5
  26. data/lib/assets/new/content.md.tt +3 -40
  27. data/lib/assets/tt/default.md.erb +23 -42
  28. data/lib/assets/tt/pandoc.md.erb +11 -8
  29. data/lib/clerq.rb +57 -12
  30. data/lib/clerq/cli.rb +77 -60
  31. data/lib/clerq/entities.rb +0 -1
  32. data/lib/clerq/entities/node.rb +135 -115
  33. data/lib/clerq/properties.rb +1 -3
  34. data/lib/clerq/repositories.rb +2 -4
  35. data/lib/clerq/repositories/file_repository.rb +59 -0
  36. data/lib/clerq/repositories/node_repository.rb +72 -30
  37. data/lib/clerq/repositories/text_repository.rb +47 -0
  38. data/lib/clerq/services.rb +8 -0
  39. data/lib/clerq/services/check_assembly.rb +108 -0
  40. data/lib/clerq/{interactors → services}/create_node.rb +12 -11
  41. data/lib/clerq/services/load_assembly.rb +54 -0
  42. data/lib/clerq/services/query_node.rb +72 -0
  43. data/lib/clerq/services/query_template.rb +26 -0
  44. data/lib/clerq/services/read_node.rb +101 -0
  45. data/lib/clerq/services/render_erb.rb +29 -0
  46. data/lib/clerq/services/render_node.rb +37 -0
  47. data/lib/clerq/services/service.rb +19 -0
  48. data/lib/clerq/settings.rb +2 -2
  49. data/lib/clerq/version.rb +1 -1
  50. metadata +49 -37
  51. data/TODO.md +0 -3
  52. data/lib/assets/tt/gitlab.md.erb +0 -93
  53. data/lib/assets/tt/raw.md.erb +0 -23
  54. data/lib/clerq/entities/template.rb +0 -19
  55. data/lib/clerq/gateways.rb +0 -3
  56. data/lib/clerq/gateways/gateway.rb +0 -17
  57. data/lib/clerq/gateways/in_files.rb +0 -36
  58. data/lib/clerq/gateways/in_memory.rb +0 -35
  59. data/lib/clerq/interactors.rb +0 -5
  60. data/lib/clerq/interactors/check_nodes.rb +0 -81
  61. data/lib/clerq/interactors/compile_nodes.rb +0 -31
  62. data/lib/clerq/interactors/interactor.rb +0 -28
  63. data/lib/clerq/interactors/join_nodes.rb +0 -59
  64. data/lib/clerq/interactors/query_nodes.rb +0 -62
  65. data/lib/clerq/repositories/in_memory.rb +0 -45
  66. data/lib/clerq/repositories/node_reader.rb +0 -107
  67. data/lib/clerq/repositories/repository.rb +0 -11
  68. data/lib/clerq/repositories/template_repository.rb +0 -53
  69. data/lib/clerq/templater.rb +0 -32
@@ -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
- v = instance_variable_get("@#{name}")
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|
@@ -1,5 +1,3 @@
1
- require_relative 'repositories/repository'
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/template_repository'
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 'repository'
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 < Repository
11
- def initialize(path = Dir.pwd)
12
- @path = path
13
- @items = []
14
- end
12
+ class NodeRepository < FileRepository
15
13
 
16
- def items(id = nil)
17
- load_nodes if @items.empty?
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
- raise ArgumentError, "Invalid argument" unless node.is_a? Node
24
- Dir.chdir(@path){ File.write(node.id + '.md', markdown(node))}
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
- def load_nodes
30
- @items = []
31
- Dir.chdir(@path) do
32
- Dir.glob('**/*.md').each do |f|
33
- # TODO: what to do with errors?
34
- nodes = NodeReader.(f)
35
- nodes.each{|n| @items << n}
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
- def markdown(n)
41
- [].tap do |out|
42
- out << "#{'#' * (n.nesting_level + 1)} [#{n.id}] #{n.title}"
43
- unless n.meta.empty?
44
- out << '{{'
45
- n.meta.each{|k,v| out << "#{k}: #{v}"}
46
- out << '}}'
47
- out << "\n"
48
- end
49
- out << n.body
50
- end.join("\n")
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 "interactor"
3
+ require_relative "service"
4
+ require_relative "query_template"
5
+ require_relative "../entities/node"
4
6
 
5
7
  module Clerq
6
- module Interactors
8
+ module Services
7
9
 
8
- class CreateNode < Interactor
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
- unless @template.empty?
12
- tt = gateway.templates(@template)
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
- gateway.save(node)
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