clerq 0.1.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 (69) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.travis.yml +7 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +24 -0
  6. data/README.md +269 -0
  7. data/Rakefile +10 -0
  8. data/TODO.md +3 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/clerq.gemspec +44 -0
  12. data/exe/clerq +8 -0
  13. data/lib/assets/new/README.md.tt +75 -0
  14. data/lib/assets/new/clerq.thor.tt +15 -0
  15. data/lib/assets/new/clerq.yml.tt +4 -0
  16. data/lib/assets/new/content.md.tt +41 -0
  17. data/lib/assets/promo/README.md +40 -0
  18. data/lib/assets/promo/bin/Clerq SRS.docx +0 -0
  19. data/lib/assets/promo/bin/Clerq SRS.md +579 -0
  20. data/lib/assets/promo/bin/assets/promo_dark.png +0 -0
  21. data/lib/assets/promo/bin/assets/promo_light.png +0 -0
  22. data/lib/assets/promo/clerq.yml +3 -0
  23. data/lib/assets/promo/promo.thor +58 -0
  24. data/lib/assets/promo/src/clerq.md +82 -0
  25. data/lib/assets/promo/src/fr/cmp.node.md +14 -0
  26. data/lib/assets/promo/src/fr/cmp.repo.md +10 -0
  27. data/lib/assets/promo/src/fr/cmp.tt.md +20 -0
  28. data/lib/assets/promo/src/fr/cmp.writer.md +19 -0
  29. data/lib/assets/promo/src/fr/ent.md +32 -0
  30. data/lib/assets/promo/src/ui/cli/cli.bld.md +32 -0
  31. data/lib/assets/promo/src/ui/cli/cli.chk.md +17 -0
  32. data/lib/assets/promo/src/ui/cli/cli.hlp.md +14 -0
  33. data/lib/assets/promo/src/ui/cli/cli.new.md +20 -0
  34. data/lib/assets/promo/src/ui/cli/cli.opt.md +11 -0
  35. data/lib/assets/promo/src/ui/cli/cli.ver.md +4 -0
  36. data/lib/assets/promo/src/ui/ui.cli.md +8 -0
  37. data/lib/assets/promo/src/us/us.reader.md +8 -0
  38. data/lib/assets/promo/src/us/us.writer.md +79 -0
  39. data/lib/assets/tt/default.md.erb +64 -0
  40. data/lib/assets/tt/gitlab.md.erb +93 -0
  41. data/lib/assets/tt/pandoc.md.erb +88 -0
  42. data/lib/assets/tt/raw.md.erb +23 -0
  43. data/lib/clerq.rb +41 -0
  44. data/lib/clerq/cli.rb +129 -0
  45. data/lib/clerq/entities.rb +2 -0
  46. data/lib/clerq/entities/node.rb +135 -0
  47. data/lib/clerq/entities/template.rb +19 -0
  48. data/lib/clerq/gateways.rb +3 -0
  49. data/lib/clerq/gateways/gateway.rb +17 -0
  50. data/lib/clerq/gateways/in_files.rb +36 -0
  51. data/lib/clerq/gateways/in_memory.rb +35 -0
  52. data/lib/clerq/interactors.rb +5 -0
  53. data/lib/clerq/interactors/check_nodes.rb +81 -0
  54. data/lib/clerq/interactors/compile_nodes.rb +31 -0
  55. data/lib/clerq/interactors/create_node.rb +40 -0
  56. data/lib/clerq/interactors/interactor.rb +28 -0
  57. data/lib/clerq/interactors/join_nodes.rb +59 -0
  58. data/lib/clerq/interactors/query_nodes.rb +62 -0
  59. data/lib/clerq/properties.rb +21 -0
  60. data/lib/clerq/repositories.rb +5 -0
  61. data/lib/clerq/repositories/in_memory.rb +45 -0
  62. data/lib/clerq/repositories/node_reader.rb +107 -0
  63. data/lib/clerq/repositories/node_repository.rb +56 -0
  64. data/lib/clerq/repositories/repository.rb +11 -0
  65. data/lib/clerq/repositories/template_repository.rb +53 -0
  66. data/lib/clerq/settings.rb +56 -0
  67. data/lib/clerq/templater.rb +32 -0
  68. data/lib/clerq/version.rb +3 -0
  69. metadata +172 -0
@@ -0,0 +1,135 @@
1
+ # encoding: UTF-8
2
+ require "forwardable"
3
+
4
+ module Clerq
5
+ module Entities
6
+ end
7
+ end
8
+
9
+ class Clerq::Entities::Node
10
+ extend Forwardable
11
+ include Enumerable
12
+
13
+ attr_reader :parent
14
+ attr_reader :title
15
+ attr_reader :body
16
+ attr_reader :meta
17
+ attr_writer :id
18
+
19
+ def_delegators :@meta, :[], :[]=
20
+ def_delegator :@items, :delete, :delete_item
21
+ def_delegator :@items, :last, :last_item
22
+ protected :delete_item
23
+
24
+ def initialize(id: '', title: '', body: '', meta: {})
25
+ raise ArgumentError, "Invalid argument :id" unless id.is_a? String
26
+ raise ArgumentError, "Invalid argument :title" unless title.is_a? String
27
+ raise ArgumentError, "Invalid argument :body" unless body.is_a? String
28
+ raise ArgumentError, "Invalid argument :meta" unless meta.is_a? Hash
29
+ @parent = nil
30
+ @items = []
31
+ @id = id
32
+ @title = title
33
+ @body = body
34
+ @meta = meta
35
+ end
36
+
37
+ def <<(node)
38
+ raise ArgumentError, "Invalid argument :node" unless node.is_a? Node
39
+ node.parent = self
40
+ @items << node
41
+ node
42
+ end
43
+
44
+ # Find descendant node by id (for sort_order purpose)
45
+ # @param [String] node id
46
+ # @return [Node] when id found or nil if did not
47
+ def item(id)
48
+ return @items.find{|r| r.id.end_with? id[1..-1]} if id.start_with? '.'
49
+ @items.find{|r| r.id.eql? id}
50
+ end
51
+
52
+ # @return [Array<Node>] array of items ordered by order_index meta
53
+ def items
54
+ return @items if @items.empty?
55
+ return @items if @meta[:order_index].nil?
56
+ source = Array.new(@items)
57
+ order = @meta[:order_index]
58
+ [].tap do |ordered|
59
+ order.split(/ /).each do |o|
60
+ e = source.delete(item(o))
61
+ ordered << e if e
62
+ end
63
+ ordered.concat(source)
64
+ end
65
+ end
66
+
67
+ # TODO: place valid Yard pointer to Enumerable#each
68
+ def each(&block)
69
+ return to_enum(__callee__) unless block_given?
70
+ yield(self)
71
+ items.each{|n| n.each(&block) }
72
+ end
73
+
74
+ # TODO: it needs for writing, maybe shold be moved to decorator?
75
+ def root
76
+ n = self
77
+ n = n.parent while n.parent
78
+ n
79
+ end
80
+
81
+ # TODO: it needs for writing, maybe shold be moved to decorator?
82
+ def nesting_level
83
+ @parent.nil? ? 0 : @parent.nesting_level + 1
84
+ end
85
+
86
+ # TODO: it needs for checking links and writing, maybe
87
+ # shold be moved to decorator for checking also?
88
+ # @return [Array<String>] links to other nodes inside @body
89
+ def links
90
+ return [] if @body.empty?
91
+ @body.scan(/\[\[([\w\.]*)\]\]/).flatten.uniq
92
+ end
93
+
94
+ def id
95
+ @id.start_with?('.') && @parent ? @parent.id + @id : @id
96
+ end
97
+
98
+ # Find the first node in the node hierarchy by its id. It allows
99
+ # finding a node by matching the end of id by providing '*' prefix,
100
+ # @param [String] id Node id
101
+ # @return [Node] or nil when node not found
102
+ def node(id)
103
+ return find{|n| n.id.end_with? id[1..-1]} if id.start_with? '*'
104
+ find{|n| n.id.eql? id}
105
+ end
106
+
107
+ # TODO: extract to NodeFinder class and do think about helpful prefixes
108
+ # '.' for first descendant
109
+ # '.*' for first descendant by #end_with?
110
+ # '..' for first node at the level of node parent
111
+ # '..' for first node at the level of node parent by #end_with?
112
+ # def find_node(id)
113
+ # end
114
+
115
+ # TODO: Node#belong_to?(node[String|Node])
116
+ # that returns true if node belongs to hierarhy of self included
117
+ # @param [String|Node] node id or node
118
+ # @return [Boolean] true if node belongs to hierarchy of id or other node
119
+ # def belong?(node)
120
+ # end
121
+
122
+ # TODO: add tests
123
+ def orphan!
124
+ return unless @parent
125
+ @parent.delete_item(self)
126
+ @parent = nil
127
+ end
128
+
129
+ protected
130
+ def parent=(node)
131
+ raise ArgumentError, "Invalid parameter :node" unless node.is_a? Node
132
+ @parent = node
133
+ end
134
+
135
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: UTF-8
2
+
3
+ module Clerq
4
+ module Entities
5
+
6
+ class Template
7
+ attr_reader :id
8
+ attr_reader :body
9
+
10
+ def initialize(id:, body: '')
11
+ raise ArgumentError, "Invalid argument :id" if !(id.is_a?(String) && !id.empty?)
12
+ raise ArgumentError, "Invalid argument :body" unless body.is_a? String
13
+ @id = id
14
+ @body = body
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ require_relative 'gateways/gateway'
2
+ require_relative 'gateways/in_memory'
3
+ require_relative 'gateways/in_files'
@@ -0,0 +1,17 @@
1
+ module Clerq
2
+ module Gateways
3
+
4
+ class Gateway
5
+ # @return [Array<Node>]
6
+ def nodes; end
7
+
8
+ # @return [Array<Template>]
9
+ def templates; end
10
+
11
+ # @param obj [Node or Template]
12
+ def save(obj); end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: UTF-8
2
+
3
+ require "forwardable"
4
+ require_relative '../repositories'
5
+ require_relative 'gateway'
6
+
7
+ module Clerq
8
+ module Gateways
9
+
10
+ class InFiles < Gateway
11
+ extend Forwardable
12
+
13
+ attr_reader :node_repo, :tplt_repo
14
+
15
+ def_delegator :@node_repo, :items, :nodes
16
+ def_delegator :@tplt_repo, :items, :templates
17
+
18
+ def initialize
19
+ settings = Clerq.settings
20
+ @node_repo = Clerq::Repositories::NodeRepository.new(settings.src)
21
+ @tplt_repo = Clerq::Repositories::TemplateRepository.new(settings.tt)
22
+ @repositories = {}
23
+ @repositories[Clerq::Entities::Node] = @node_repo
24
+ @repositories[Clerq::Entities::Template] = @tplt_repo
25
+ end
26
+
27
+ def save(obj)
28
+ repo = @repositories[obj.class]
29
+ raise ArgumentError, "Repository for #{obj.class} not found" unless repo
30
+ repo.save(obj)
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: UTF-8
2
+
3
+ require "forwardable"
4
+ require_relative "gateway"
5
+ require_relative "../repositories/in_memory"
6
+
7
+ module Clerq
8
+ module Gateways
9
+
10
+ class InMemory < Gateway
11
+ extend Forwardable
12
+
13
+ attr_reader :node_repo, :template_repo
14
+
15
+ def_delegator :@node_repo, :items, :nodes
16
+ def_delegator :@template_repo, :items, :templates
17
+
18
+ def initialize
19
+ @node_repo = Clerq::Repositories::InMemory.new
20
+ @template_repo = Clerq::Repositories::InMemory.new
21
+ @repositories = {}
22
+ @repositories[Clerq::Entities::Node] = @node_repo
23
+ @repositories[Clerq::Entities::Template] = @template_repo
24
+ end
25
+
26
+ def save(obj)
27
+ repo = @repositories[obj.class]
28
+ raise ArgumentError, "Repository for #{obj.class} not found" unless repo
29
+ repo.save(obj)
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ require_relative "interactors/join_nodes"
2
+ require_relative "interactors/query_nodes"
3
+ require_relative "interactors/check_nodes"
4
+ require_relative "interactors/create_node"
5
+ require_relative "interactors/compile_nodes"
@@ -0,0 +1,81 @@
1
+ # encoding: UTF-8
2
+
3
+ require_relative "interactor"
4
+ require_relative "join_nodes"
5
+
6
+ module Clerq
7
+ module Interactors
8
+
9
+ # TODO: maybe it needs QueryNodes instead of JoinNodes?
10
+ # TODO: add check for empty node content - @items.empty && @body.empty?
11
+ class CheckNodes < Interactor
12
+
13
+ def call
14
+ # TODO: query parameter for each interactor
15
+ @node = JoinNodes.()
16
+ {}.tap do |errors|
17
+ nonuniq = nonuniq_ids
18
+ errors.merge!(nonuniq_ids: nonuniq) unless nonuniq.empty?
19
+ parents = unknown_parents
20
+ errors.merge!(unknown_parents: parents) unless parents.empty?
21
+ references = unknown_references
22
+ errors.merge!(unknown_references: references) unless references.empty?
23
+ order = unknown_order_index
24
+ errors.merge!(unknown_order_index: order) unless order.empty?
25
+ end
26
+ end
27
+
28
+ protected
29
+
30
+ # @return [Hash<node id, Array<file_name>>] node ids and array of file_names
31
+ def nonuniq_ids
32
+ @node.each_with_object({}){|node, hsh|
33
+ hsh[node.id] ||= []
34
+ # TODO that way CheckNodes depends on files!?
35
+ hsh[node.id] << node[:file_name]
36
+ }.select{|k, v| v.size > 1}
37
+ .each{|k, v| v.uniq!}
38
+ end
39
+
40
+ # @return [Hash<parent, Array<node id>>] unknown meta[:parent] and
41
+ # array of nodes those have this meta attribute
42
+ def unknown_parents
43
+ @node # .drop(1)?
44
+ .select{|node| node[:parent] && node.parent.id != node[:parent]}
45
+ .each_with_object({}){|node, hsh|
46
+ hsh[node[:parent]] ||= []
47
+ hsh[node[:parent]] << node.id
48
+ }
49
+ end
50
+
51
+ # @return [Hash<node_id, Array<node_id>>] node ids that noe found
52
+ # in hierarchy and array of node ids those have links to corresponding
53
+ # unknown node
54
+ def unknown_references
55
+ index = @node.map(&:id).drop(1)
56
+ @node.each_with_object({}) do |node, hsh|
57
+ node.links
58
+ .reject {|lnk| index.include?(lnk)}
59
+ .each do |lnk|
60
+ hsh[lnk] ||= []
61
+ hsh[lnk] << node.id
62
+ end
63
+ end
64
+ end
65
+
66
+ # @return [Hash<node_id, Array<node_id>>] node ids and array of
67
+ # unknown links in :order_index
68
+ def unknown_order_index
69
+ @node
70
+ .select{|node| node[:order_index]}
71
+ .each_with_object({}){|node, hsh|
72
+ order = node[:order_index].split(/ /)
73
+ wrong = order.select{|o| node.item(o).nil?}
74
+ hsh[node.id] = wrong unless wrong.empty?
75
+ }
76
+ end
77
+
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: UTF-8
2
+
3
+ require_relative "interactor"
4
+ require_relative "join_nodes"
5
+ require_relative "query_nodes"
6
+
7
+ module Clerq
8
+ module Interactors
9
+
10
+ class CompileNodes < Interactor
11
+
12
+ # @return [String] document body
13
+ def call
14
+ tt = gateway.templates(@template)
15
+ raise Failure, "Template '#{@template}' not found" unless tt
16
+ node = @query.empty? ? JoinNodes.() : QueryNodes.(query: @query)
17
+ Clerq::Templater.(tt.body, node)
18
+ end
19
+
20
+ protected
21
+
22
+ def initialize(template:, query: '')
23
+ raise ArgumentError, "Invalid argument :query" unless query.is_a? String
24
+ raise ArgumentError, "Invalid argument :template" if !(template.is_a?(String) && !template.empty?)
25
+ @query = query
26
+ @template = template
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,40 @@
1
+ # encoding: UTF-8
2
+
3
+ require_relative "interactor"
4
+
5
+ module Clerq
6
+ module Interactors
7
+
8
+ class CreateNode < Interactor
9
+
10
+ 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(
18
+ id: @id, title: @title, body: @body, meta: @meta)
19
+ gateway.save(node)
20
+ end
21
+
22
+ protected
23
+
24
+ def initialize(id: '', title: '', body: '', meta: {}, template: '')
25
+ raise ArgumentError, "Invalid argument :id" unless id.is_a? String
26
+ raise ArgumentError, "Invalid argument :title" unless title.is_a? String
27
+ raise ArgumentError, "Invalid argument :body" unless body.is_a? String
28
+ raise ArgumentError, "Invalid argument :meta" unless meta.is_a? Hash
29
+ raise ArgumentError, "Invalid argument :template" unless template.is_a? String
30
+ @id = id
31
+ @title = title
32
+ @body = body
33
+ @meta = meta
34
+ @template = template
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: UTF-8
2
+
3
+ module Clerq
4
+ module Interactors
5
+
6
+ class Interactor
7
+ Failure = Class.new(StandardError)
8
+
9
+ def self.inherited(klass)
10
+ klass.const_set(:Failure, Class.new(klass::Failure))
11
+ end
12
+
13
+ def self.call(*args)
14
+ new(*args).call
15
+ end
16
+
17
+ # Should be implemented in subclasses
18
+ def call; end
19
+
20
+ protected
21
+
22
+ def gateway
23
+ Clerq.gateway
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,59 @@
1
+ # encoding: UTF-8
2
+
3
+ require_relative "interactor"
4
+
5
+ module Clerq
6
+ module Interactors
7
+
8
+ # Combine all nodes in one root node (document)
9
+ class JoinNodes < Interactor
10
+
11
+ # @return [Node]
12
+ def call
13
+ @node = join(gateway.nodes)
14
+ subordinate!
15
+ equip_ident!
16
+ @node
17
+ end
18
+
19
+ protected
20
+
21
+ def join(nodes)
22
+ return nodes.first if nodes.size == 1
23
+ node = Clerq::Entities::Node.new(id: 'join', title: Clerq.settings.title)
24
+ node.tap{|node| nodes.each{|n| node << n}}
25
+ end
26
+
27
+ def subordinate!
28
+ @node.items
29
+ .select{|n| n[:parent] && n[:parent] != n.parent.id}
30
+ .each{|n|
31
+ parent = @node.node(n[:parent])
32
+ next unless parent
33
+ parent << n
34
+ @node.items.delete(n)
35
+ n.meta.delete(:parent)
36
+ }
37
+ # if @node.id == 'join' && @node.items.size == 1
38
+ # @node = @node.items.first
39
+ # @node.orphan!
40
+ # end
41
+ end
42
+
43
+ # equips nodes with autogenerated id
44
+ # @param [Node] node
45
+ def equip_ident!
46
+ counter = {}
47
+ @node.select{|n| n.id.empty?}.each do |n|
48
+ next if n == @node
49
+ index = counter[n.parent] || 1
50
+ counter[n.parent] = index + 1
51
+ id = index.to_s.rjust(2, '0')
52
+ id = '.' + id unless n.parent == @node
53
+ n.id = id
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ end