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,62 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require_relative "interactor"
|
4
|
+
require_relative "join_nodes"
|
5
|
+
|
6
|
+
module Clerq
|
7
|
+
module Interactors
|
8
|
+
|
9
|
+
class QueryNodes < Interactor
|
10
|
+
|
11
|
+
def call
|
12
|
+
proc = prepare_query
|
13
|
+
join = JoinNodes.()
|
14
|
+
list = join.select{|node| proc.call(node)}
|
15
|
+
return Node.new(title: "#{Clerq.settings.title}. Query: #{@query}") if list.empty?
|
16
|
+
make_response(list)
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def initialize(query:)
|
22
|
+
raise ArgumentError, "Invalid argument :query" if !(query.is_a?(String) && !query.empty?)
|
23
|
+
@query = query
|
24
|
+
end
|
25
|
+
|
26
|
+
def prepare_query
|
27
|
+
proc = Proc.new {|node| eval(@query)}
|
28
|
+
test = Node.new
|
29
|
+
test.select{|node| proc.call(node)}
|
30
|
+
proc
|
31
|
+
rescue Exception => e
|
32
|
+
msg = <<~EOF
|
33
|
+
Invalid query #{@query} (#{e.message})
|
34
|
+
It must eval as 'Proc.new{|node| eval(<query>)}'
|
35
|
+
Valid query examples are following:
|
36
|
+
node.id == 'uc'
|
37
|
+
node.belong?('uc')
|
38
|
+
node.belong?('ud') && node[:source] == 'sales'
|
39
|
+
EOF
|
40
|
+
raise self.class::Failure, msg
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param nodes [Array<Node>] result of node.select
|
44
|
+
# @return [Node] node that includes all selected nodes
|
45
|
+
def make_response(nodes)
|
46
|
+
if nodes.size == 1
|
47
|
+
node = nodes.first
|
48
|
+
node.orphan!
|
49
|
+
return node
|
50
|
+
end
|
51
|
+
Node.new(id: @query).tap{|node|
|
52
|
+
nodes.each{|n|
|
53
|
+
n.orphan!
|
54
|
+
node << n
|
55
|
+
}
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Clerq
|
2
|
+
|
3
|
+
module Properties
|
4
|
+
def property(name, options = {}, &validation)
|
5
|
+
default_value = options[:default]
|
6
|
+
define_method name do
|
7
|
+
v = instance_variable_get("@#{name}")
|
8
|
+
v = default_value unless v
|
9
|
+
v
|
10
|
+
end
|
11
|
+
|
12
|
+
define_method "#{name}=" do |val|
|
13
|
+
if validation && !validation.call(val)
|
14
|
+
raise ArgumentError, "Invalid property value #{name}: #{val}"
|
15
|
+
end
|
16
|
+
instance_variable_set("@#{name}", val)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require_relative 'repository'
|
2
|
+
|
3
|
+
module Clerq
|
4
|
+
module Repositories
|
5
|
+
|
6
|
+
class InMemory < Repository
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@counter = 0
|
10
|
+
@items = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def items(id = nil)
|
14
|
+
return @items[id] if id
|
15
|
+
@items.values
|
16
|
+
end
|
17
|
+
|
18
|
+
def find(id)
|
19
|
+
@items[id]
|
20
|
+
end
|
21
|
+
|
22
|
+
def save(e)
|
23
|
+
raise ArgumentError, "Invalid argument" unless entity?(e)
|
24
|
+
id = has_id?(e) ? e.id : counter
|
25
|
+
@items[id] = e
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def counter
|
31
|
+
@counter += 1
|
32
|
+
end
|
33
|
+
|
34
|
+
def has_id?(e)
|
35
|
+
e.id && !e.id.empty?
|
36
|
+
end
|
37
|
+
|
38
|
+
def entity?(e)
|
39
|
+
e.is_a?(Object) && e.respond_to?(:id)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require_relative '../entities'
|
4
|
+
include Clerq::Entities
|
5
|
+
|
6
|
+
module Clerq
|
7
|
+
module Repositories
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Clerq::Repositories::NodeReader
|
12
|
+
|
13
|
+
# @param [String] file_name with node
|
14
|
+
# @return [Array<Node>] from file
|
15
|
+
def self.call(file_name)
|
16
|
+
new(file_name).call
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param [Enumerator] lines; for testing purpose
|
20
|
+
# @return [Array<Node>] from file
|
21
|
+
def call(text = File.foreach(@file_name))
|
22
|
+
STDOUT.print "Reading #{@file_name} ..."
|
23
|
+
|
24
|
+
read_nodes(text).each do |node_text|
|
25
|
+
level, node = parse_node(node_text)
|
26
|
+
next unless node
|
27
|
+
node[:file_name] = @file_name
|
28
|
+
insert_node(node, level)
|
29
|
+
end
|
30
|
+
|
31
|
+
STDOUT.puts @errors.empty? ? "OK" : "Errors found"
|
32
|
+
unless @errors.empty?
|
33
|
+
STDERR.puts "Errors reading #{@file_name}"
|
34
|
+
STDERR.puts @errors.map{|e| "\t#{e}"}.join("\n")
|
35
|
+
end
|
36
|
+
|
37
|
+
nodes = Array.new(@node.items)
|
38
|
+
nodes.each(&:orphan!)
|
39
|
+
nodes
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def initialize(file_name)
|
45
|
+
@file_name = file_name
|
46
|
+
@node = Node.new(id: file_name)
|
47
|
+
@errors = []
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param [Enumerator<String>] text
|
51
|
+
# @return [Array<String>] where each item represents one node
|
52
|
+
def read_nodes(text)
|
53
|
+
[].tap do |nodes|
|
54
|
+
quote, body = false, ''
|
55
|
+
text.each do |line|
|
56
|
+
if line.start_with?('#') && !quote && !body.empty?
|
57
|
+
nodes << body
|
58
|
+
body = ''
|
59
|
+
end
|
60
|
+
body << line
|
61
|
+
quote = !quote if line.start_with?('```')
|
62
|
+
end
|
63
|
+
nodes << body unless body.empty?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def insert_node(node, level)
|
68
|
+
parent = @node
|
69
|
+
while parent.last_item && (parent.nesting_level + 1) < level
|
70
|
+
parent = parent.last_item
|
71
|
+
end
|
72
|
+
unless (parent.nesting_level + 1) == level
|
73
|
+
@errors << "invalid header level: #{'#' * level} [#{node.id}]"
|
74
|
+
parent = @node
|
75
|
+
end
|
76
|
+
parent << node
|
77
|
+
end
|
78
|
+
|
79
|
+
def parse_node(text)
|
80
|
+
text += "\n" unless text.end_with?("\n")
|
81
|
+
parts = NODE_REGX.match(text)
|
82
|
+
lv = parts[1] || ''
|
83
|
+
id = parts[3] || ''
|
84
|
+
title = parts[4] || ''
|
85
|
+
body = parts[7] || ''
|
86
|
+
meta = {}
|
87
|
+
meta.merge!(parse_meta(parts[6])) if parts[6]
|
88
|
+
[lv.size, Node.new(id: id, title: title, body: body.strip, meta: meta)]
|
89
|
+
rescue StandardError
|
90
|
+
@errors << "invalid node format:\n#{text}"
|
91
|
+
[nil, nil]
|
92
|
+
end
|
93
|
+
|
94
|
+
def parse_meta(text)
|
95
|
+
text.strip.split(/[;\n]/).inject({}) do |h, i|
|
96
|
+
pair = /\s?(\w*):\s*(.*)/.match(i)
|
97
|
+
h.merge(pair[1].to_sym => pair[2])
|
98
|
+
end || {}
|
99
|
+
rescue StandardError
|
100
|
+
@errors << "invalid meta format:\n{{#{text}}}"
|
101
|
+
{}
|
102
|
+
end
|
103
|
+
|
104
|
+
NODE_REGX =
|
105
|
+
/^(\#+)[ ]*(\[([^\[\]\s]*)\][ ]*)?([\s\S]*?)\n({{([\s\S]*?)}})?(.*)$/m
|
106
|
+
|
107
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require_relative '../entities'
|
4
|
+
require_relative 'repository'
|
5
|
+
include Clerq::Entities
|
6
|
+
|
7
|
+
module Clerq
|
8
|
+
module Repositories
|
9
|
+
|
10
|
+
class NodeRepository < Repository
|
11
|
+
def initialize(path = Dir.pwd)
|
12
|
+
@path = path
|
13
|
+
@items = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def items(id = nil)
|
17
|
+
load_nodes if @items.empty?
|
18
|
+
return @items.find {|i| i.id == id} if id
|
19
|
+
@items
|
20
|
+
end
|
21
|
+
|
22
|
+
def save(node)
|
23
|
+
raise ArgumentError, "Invalid argument" unless node.is_a? Node
|
24
|
+
Dir.chdir(@path){ File.write(node.id + '.md', markdown(node))}
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
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}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
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")
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require_relative '../entities'
|
4
|
+
require_relative 'repository'
|
5
|
+
include Clerq::Entities
|
6
|
+
|
7
|
+
module Clerq
|
8
|
+
module Repositories
|
9
|
+
|
10
|
+
class TemplateRepository < Repository
|
11
|
+
attr_reader :path
|
12
|
+
def initialize(path = Dir.pwd)
|
13
|
+
@items = {}
|
14
|
+
@path = path
|
15
|
+
end
|
16
|
+
|
17
|
+
# TODO move id parameter to #find
|
18
|
+
def items(id = nil)
|
19
|
+
load if @items.empty?
|
20
|
+
return find(id) if id
|
21
|
+
@items.values
|
22
|
+
end
|
23
|
+
|
24
|
+
def find(id)
|
25
|
+
return @items[id] if @items[id]
|
26
|
+
PATTERNS.each do |p|
|
27
|
+
i0 = "#{id}#{p[1..-1]}"
|
28
|
+
return @items[i0] if @items[i0]
|
29
|
+
end
|
30
|
+
return nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def save(tt)
|
34
|
+
raise ArgumentError, "Invalid argument" unless tt.is_a? Template
|
35
|
+
Dir.chdir(@path){ File.write(tt.id, tt.body) }
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def load
|
41
|
+
Dir.chdir(@path) do
|
42
|
+
Dir.glob(PATTERNS)
|
43
|
+
.map {|f| Template.new(id: f, body: File.read(f))}
|
44
|
+
.each{|t| @items[t.id] = t}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
PATTERNS = ['*.tt', '*.erb']
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'pp'
|
4
|
+
require 'yaml'
|
5
|
+
require_relative 'properties'
|
6
|
+
|
7
|
+
module Clerq
|
8
|
+
|
9
|
+
class Settings
|
10
|
+
extend Properties
|
11
|
+
|
12
|
+
STORAGE = 'clerq.yml'.freeze
|
13
|
+
|
14
|
+
# binary document settings that can be changed through 'clerq.yml'
|
15
|
+
property :document, default: 'Clеrk SRS'
|
16
|
+
property :template, default: 'default.md.erb'
|
17
|
+
property :title, default: 'Clеrk SRS'
|
18
|
+
|
19
|
+
# folders structure
|
20
|
+
property :bin, default: 'bin'
|
21
|
+
property :src, default: 'src'
|
22
|
+
property :knb, default: 'knb'
|
23
|
+
property :lib, default: 'lib'
|
24
|
+
property :tt, default: 'tt'
|
25
|
+
property :assets, default: 'bin/assets'
|
26
|
+
|
27
|
+
def folders
|
28
|
+
[bin, src, knb, lib, tt, assets]
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize
|
32
|
+
load
|
33
|
+
end
|
34
|
+
|
35
|
+
# TODO: load settings than can be changed by user
|
36
|
+
def load
|
37
|
+
return unless File.exist?(STORAGE)
|
38
|
+
|
39
|
+
props = YAML.load(File.read(STORAGE))
|
40
|
+
props.each{|k, v| instance_variable_set("@#{k}", v) }
|
41
|
+
end
|
42
|
+
|
43
|
+
# TODO: it saves only changed properties
|
44
|
+
# properties with default values won't be saved
|
45
|
+
def save
|
46
|
+
props = {}
|
47
|
+
instance_variables.each{|v|
|
48
|
+
# props[v.to_s[1..-1]] = instance_variable_get("#{v}")
|
49
|
+
p = v.to_s[1..-1]
|
50
|
+
props[p] = self.send(p)
|
51
|
+
}
|
52
|
+
File.write(STORAGE, YAML.dump(props))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
module Clerq
|
6
|
+
|
7
|
+
# Compile @object to text in accordance with @template
|
8
|
+
# Usage
|
9
|
+
# tt = "id: <%= @object.id %>\ntitle: <%= @object.title %>\n"
|
10
|
+
# n = Node.new(id: 'uc', title: 'Use Cases', meta: {skip_meta: true})
|
11
|
+
# text = Templater.call(tt, n) # or Templater.(tt, n)
|
12
|
+
class Templater
|
13
|
+
|
14
|
+
# @param tt [Template]
|
15
|
+
# @param object [Object]
|
16
|
+
# @result [String?]
|
17
|
+
def self.call(template, object)
|
18
|
+
new(template, object).call
|
19
|
+
end
|
20
|
+
|
21
|
+
def call
|
22
|
+
tt = ERB.new(@template, nil, "-")
|
23
|
+
tt.result(binding)
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(template, object)
|
27
|
+
@template = template
|
28
|
+
@object = object
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|