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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +24 -0
- data/README.md +269 -0
- data/Rakefile +10 -0
- data/TODO.md +3 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/clerq.gemspec +44 -0
- data/exe/clerq +8 -0
- data/lib/assets/new/README.md.tt +75 -0
- data/lib/assets/new/clerq.thor.tt +15 -0
- data/lib/assets/new/clerq.yml.tt +4 -0
- data/lib/assets/new/content.md.tt +41 -0
- data/lib/assets/promo/README.md +40 -0
- data/lib/assets/promo/bin/Clerq SRS.docx +0 -0
- data/lib/assets/promo/bin/Clerq SRS.md +579 -0
- data/lib/assets/promo/bin/assets/promo_dark.png +0 -0
- data/lib/assets/promo/bin/assets/promo_light.png +0 -0
- data/lib/assets/promo/clerq.yml +3 -0
- data/lib/assets/promo/promo.thor +58 -0
- data/lib/assets/promo/src/clerq.md +82 -0
- data/lib/assets/promo/src/fr/cmp.node.md +14 -0
- data/lib/assets/promo/src/fr/cmp.repo.md +10 -0
- data/lib/assets/promo/src/fr/cmp.tt.md +20 -0
- data/lib/assets/promo/src/fr/cmp.writer.md +19 -0
- data/lib/assets/promo/src/fr/ent.md +32 -0
- data/lib/assets/promo/src/ui/cli/cli.bld.md +32 -0
- data/lib/assets/promo/src/ui/cli/cli.chk.md +17 -0
- data/lib/assets/promo/src/ui/cli/cli.hlp.md +14 -0
- data/lib/assets/promo/src/ui/cli/cli.new.md +20 -0
- data/lib/assets/promo/src/ui/cli/cli.opt.md +11 -0
- data/lib/assets/promo/src/ui/cli/cli.ver.md +4 -0
- data/lib/assets/promo/src/ui/ui.cli.md +8 -0
- data/lib/assets/promo/src/us/us.reader.md +8 -0
- data/lib/assets/promo/src/us/us.writer.md +79 -0
- data/lib/assets/tt/default.md.erb +64 -0
- data/lib/assets/tt/gitlab.md.erb +93 -0
- data/lib/assets/tt/pandoc.md.erb +88 -0
- data/lib/assets/tt/raw.md.erb +23 -0
- data/lib/clerq.rb +41 -0
- data/lib/clerq/cli.rb +129 -0
- data/lib/clerq/entities.rb +2 -0
- data/lib/clerq/entities/node.rb +135 -0
- data/lib/clerq/entities/template.rb +19 -0
- data/lib/clerq/gateways.rb +3 -0
- data/lib/clerq/gateways/gateway.rb +17 -0
- data/lib/clerq/gateways/in_files.rb +36 -0
- data/lib/clerq/gateways/in_memory.rb +35 -0
- data/lib/clerq/interactors.rb +5 -0
- data/lib/clerq/interactors/check_nodes.rb +81 -0
- data/lib/clerq/interactors/compile_nodes.rb +31 -0
- data/lib/clerq/interactors/create_node.rb +40 -0
- data/lib/clerq/interactors/interactor.rb +28 -0
- data/lib/clerq/interactors/join_nodes.rb +59 -0
- data/lib/clerq/interactors/query_nodes.rb +62 -0
- data/lib/clerq/properties.rb +21 -0
- data/lib/clerq/repositories.rb +5 -0
- data/lib/clerq/repositories/in_memory.rb +45 -0
- data/lib/clerq/repositories/node_reader.rb +107 -0
- data/lib/clerq/repositories/node_repository.rb +56 -0
- data/lib/clerq/repositories/repository.rb +11 -0
- data/lib/clerq/repositories/template_repository.rb +53 -0
- data/lib/clerq/settings.rb +56 -0
- data/lib/clerq/templater.rb +32 -0
- data/lib/clerq/version.rb +3 -0
- 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,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,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
|