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,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,5 @@
1
+ require_relative 'repositories/repository'
2
+ require_relative 'repositories/in_memory'
3
+ require_relative 'repositories/node_reader'
4
+ require_relative 'repositories/node_repository'
5
+ require_relative 'repositories/template_repository'
@@ -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,11 @@
1
+ module Clerq
2
+ module Repositories
3
+
4
+ class Repository
5
+ def items(id = nil); end
6
+ def find(id); end
7
+ def save(e); end
8
+ end
9
+
10
+ end
11
+ 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