clerq 0.1.0

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