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