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,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