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,64 @@
1
+ <%
2
+ # Template for drafts
3
+ require 'delegate'
4
+ class MarkupNode < SimpleDelegator
5
+ def title
6
+ '#' * nesting_level + ' ' + super
7
+ end
8
+
9
+ def meta
10
+ return '' if nesting_level == 0
11
+ return '' if super[:skip_meta]
12
+
13
+ hsh = {id: id}.merge(super)
14
+ hsh.delete(:order_index)
15
+ hsh.delete(:parent)
16
+ hsh.delete(:file_name)
17
+ [].tap{|ary|
18
+ ary << "Attribute | Value"
19
+ ary << "--------- | -----"
20
+ hsh.each{|k,v| ary << "#{k} | #{v}"}
21
+ }.join("\n")
22
+ end
23
+
24
+ def body
25
+ return '' if super.empty?
26
+ # replace links
27
+ txt = String.new(super)
28
+ links.each{|l| txt.gsub!("[[#{l}]]", link(l))}
29
+ txt
30
+ end
31
+
32
+ def link(id)
33
+ r = find_node(id)
34
+ return "[#{id}](#unknown)" unless r
35
+ "[#{r.title}](##{url(r.id)})"
36
+ end
37
+
38
+ # @param [String] id Node id, when it starts with
39
+ # '.' it finds for first descendant by #end_with?
40
+ # '*' it finds first note by #end_with? from root
41
+ # @return [Node] or nil when node not found
42
+ def find_node(id)
43
+ return find{|n| n.id.end_with? id[1..-1]} if id.start_with? '.'
44
+ return root.find{|n| n.id.end_with? id[1..-1]} if id.start_with? '*'
45
+ root.find{|n| n.id.eql? id}
46
+ end
47
+
48
+ def url(id)
49
+ id.downcase
50
+ .gsub(/[^A-Za-z0-9]{1,}/, '-')
51
+ .gsub(/^-/, '')
52
+ .gsub(/-$/, '')
53
+ end
54
+
55
+ end
56
+ -%>
57
+ % <%= @object.title %>
58
+ % generated by Clerq on <%= Time.now.strftime('%B %e, %Y at %H:%M') %>
59
+
60
+ <% for @node in @object.to_a.drop(1) -%>
61
+ <% n = MarkupNode.new(@node) -%>
62
+ <%= [n.title, n.meta, n.body].select{|t| !t.empty?}.join("\n\n") %>
63
+
64
+ <% end %>
@@ -0,0 +1,93 @@
1
+ <%
2
+ require 'delegate'
3
+ # Template for generate documents by Pandoc
4
+ class MarkupNode < SimpleDelegator
5
+ def title
6
+ [].tap do |ary|
7
+ ary << '#' * (nesting_level + 1)
8
+ ary << '[' + id + ']' unless __getobj__.meta[:skip_meta]
9
+ ary << super
10
+ end.join(' ')
11
+ end
12
+
13
+ def meta
14
+ return '' if nesting_level == 0
15
+ return '' if super[:skip_meta]
16
+
17
+ hsh = {}.merge(super)
18
+ hsh.delete(:order_index)
19
+ hsh.delete(:file_name)
20
+ hsh.delete(:parent)
21
+ return '' if hsh.empty?
22
+
23
+ [].tap{|ary|
24
+ ary << "Attribute | Value"
25
+ ary << "--------- | -----"
26
+ hsh.each{|k,v| ary << "#{k} | #{v}"}
27
+ }.join("\n")
28
+ end
29
+
30
+ def body
31
+ # links replacement
32
+ txt = String.new(super)
33
+ links.each{|l| txt.gsub! "[[#{l}]]", link(l)}
34
+ # macro substitution
35
+ macro.each{|m| txt.gsub! m.regex, m.substitution}
36
+ txt
37
+ end
38
+
39
+ def link(ref)
40
+ node = root.find{|n| n.id == ref}
41
+ return "[#{ref}](#unknown)" unless node
42
+ "[#{node.title}](##{url(node.title)})"
43
+ end
44
+
45
+ def url(id)
46
+ id.downcase
47
+ .gsub(/[^A-Za-z0-9]{1,}/, '-')
48
+ .gsub(/^-/, '')
49
+ .gsub(/-$/, '')
50
+ end
51
+
52
+ class Macro
53
+ attr_reader :regex
54
+ def initialize(regex, &substitution)
55
+ @regex, @substitution = regex, substitution
56
+ end
57
+
58
+ def substitution
59
+ @substitution.call()
60
+ end
61
+ end
62
+
63
+ def macro
64
+ [].tap do |ary|
65
+ ary << Macro.new(/{{@@skip[\s\S]*?}}/, &Proc.new{ '' })
66
+ ary << Macro.new("{{@@list}}", &Proc.new{list})
67
+ ary << Macro.new("{{@@tree}}", &Proc.new{tree})
68
+ end
69
+ end
70
+
71
+ def tree
72
+ this_level = nesting_level + 1
73
+ to_a.drop(1).inject([]) do |ary, n|
74
+ lead_spaces = ' ' * (n.nesting_level - this_level)
75
+ ary << "#{lead_spaces}* [#{n.title}](##{url(n.id)})"
76
+ end.join("\n")
77
+ end
78
+
79
+ def list
80
+ items.inject([]) do |ary, n|
81
+ ary << "* [#{n.title}](##{url(n.id)})"
82
+ end.join("\n")
83
+ end
84
+ end
85
+ -%>
86
+ % <%= @object.title %>
87
+ % generated by Clerq on <%= Time.now.strftime('%B %e, %Y at %H:%M') %>
88
+
89
+ <% for @node in @object.to_a.drop(1) -%>
90
+ <% n = MarkupNode.new(@node) -%>
91
+ <%= [n.title, n.meta, n.body].select{|t| !t.empty?}.join("\n\n") %>
92
+
93
+ <% end %>
@@ -0,0 +1,88 @@
1
+ <%
2
+ require 'delegate'
3
+ # Template for generate documents by Pandoc
4
+ class MarkupNode < SimpleDelegator
5
+ def title
6
+ "#{'#' * nesting_level} #{super} {##{url(id)}}"
7
+ end
8
+
9
+ def meta
10
+ return '' if nesting_level == 0
11
+ return '' if super[:skip_meta]
12
+
13
+ hsh = {id: id}.merge(super)
14
+ hsh.delete(:order_index)
15
+ hsh.delete(:file_name)
16
+ hsh.delete(:parent)
17
+ [].tap{|ary|
18
+ ary << "Attribute | Value"
19
+ ary << "--------- | -----"
20
+ hsh.each{|k,v| ary << "#{k} | #{v}"}
21
+ }.join("\n")
22
+ end
23
+
24
+ def body
25
+ # links replacement
26
+ txt = String.new(super)
27
+ links.each{|l| txt.gsub! "[[#{l}]]", link(l)}
28
+ # macro substitution
29
+ macro.each{|m| txt.gsub! m.regex, m.substitution}
30
+ txt
31
+ end
32
+
33
+ def link(ref)
34
+ node = root.find{|n| n.id == ref}
35
+ return "[#{ref}](#unknown)" unless node
36
+ "[#{node.title}](##{url(ref)})"
37
+ end
38
+
39
+ def url(id)
40
+ id = 'p' + id if id[0] == '0'
41
+ id.downcase
42
+ .gsub(/[^A-Za-z0-9]{1,}/, '-')
43
+ .gsub(/^-/, '')
44
+ .gsub(/-$/, '')
45
+ end
46
+
47
+ class Macro
48
+ attr_reader :regex
49
+ def initialize(regex, &substitution)
50
+ @regex, @substitution = regex, substitution
51
+ end
52
+
53
+ def substitution
54
+ @substitution.call()
55
+ end
56
+ end
57
+
58
+ def macro
59
+ [].tap do |ary|
60
+ ary << Macro.new(/{{@@skip[\s\S]*?}}/, &Proc.new{ '' })
61
+ ary << Macro.new("{{@@list}}", &Proc.new{list})
62
+ ary << Macro.new("{{@@tree}}", &Proc.new{tree})
63
+ end
64
+ end
65
+
66
+ def tree
67
+ this_level = nesting_level + 1
68
+ to_a.drop(1).inject([]) do |ary, n|
69
+ lead_spaces = ' ' * (n.nesting_level - this_level)
70
+ ary << "#{lead_spaces}* [#{n.title}](##{url(n.id)})"
71
+ end.join("\n")
72
+ end
73
+
74
+ def list
75
+ items.inject([]) do |ary, n|
76
+ ary << "* [#{n.title}](##{url(n.id)})"
77
+ end.join("\n")
78
+ end
79
+ end
80
+ -%>
81
+ % <%= @object.title %>
82
+ % generated by Clerq on <%= Time.now.strftime('%B %e, %Y at %H:%M') %> using 'pandoc.md.erb' template
83
+
84
+ <% for @node in @object.to_a.drop(1) -%>
85
+ <% n = MarkupNode.new(@node) -%>
86
+ <%= [n.title, n.meta, n.body].select{|t| !t.empty?}.join("\n\n") %>
87
+
88
+ <% end %>
@@ -0,0 +1,23 @@
1
+ <%
2
+ # template for saving created nodes
3
+ require 'delegate'
4
+ class MarkupNode < SimpleDelegator
5
+ def title
6
+ '#' * (nesting_level + 1) + ' [' + id + '] ' + super
7
+ end
8
+
9
+ def meta
10
+ return '' if super.empty?
11
+ [].tap{|ary|
12
+ ary << '{{'
13
+ hsh.each{|k,v| ary << "#{k}: #{v}"}
14
+ ary << '}}'
15
+ }.join("\n")
16
+ end
17
+ end
18
+ -%>
19
+ <% for @node in @object.to_a.drop(1) -%>
20
+ <% n = MarkupNode.new(@node) -%>
21
+ <%= [n.title, n.meta, n.body].select{|t| !t.empty?}.join("\n\n") %>
22
+
23
+ <% end %>
@@ -0,0 +1,41 @@
1
+ require_relative 'clerq/version'
2
+ require_relative 'clerq/entities'
3
+ require_relative 'clerq/gateways'
4
+ require_relative 'clerq/interactors'
5
+ require_relative 'clerq/repositories'
6
+ require_relative 'clerq/properties'
7
+ require_relative 'clerq/settings'
8
+ require_relative 'clerq/templater'
9
+ require_relative 'clerq/cli'
10
+
11
+ module Clerq
12
+ class Error < StandardError; end
13
+
14
+ class << self
15
+
16
+ def root
17
+ File.dirname __dir__
18
+ end
19
+
20
+ def gateway
21
+ @gateway ||= Clerq::Gateways::InFiles.new
22
+ end
23
+
24
+ def gateway=(gateway)
25
+ errmsg = "Invalid argument! Clark::Gateway required"
26
+ raise ArgumentError, errmsg unless gateway.is_a? Clerq::Gateways::Gateway
27
+ @gateway = gateway
28
+ end
29
+
30
+ def settings
31
+ @settings ||= Settings.new
32
+ end
33
+
34
+ def reset
35
+ @gateway = nil
36
+ @settings = nil
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,129 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'thor'
4
+ require_relative 'interactors'
5
+ include Clerq::Interactors
6
+
7
+ module Clerq
8
+
9
+ class Cli < Thor
10
+ include Thor::Actions
11
+ namespace :clerq
12
+
13
+ def self.source_root
14
+ File.join Clerq.root, "lib/assets"
15
+ end
16
+
17
+ desc "--version, -v", "Print the version"
18
+ def version
19
+ puts "Clerq v#{Clerq::VERSION}"
20
+ end
21
+ map %w[--version -v] => :version
22
+
23
+ desc "new PROJECT", "Create a new Clerq project"
24
+ def new(project)
25
+ say "Creating project '#{project}'..."
26
+
27
+ if Dir.exist?(project)
28
+ error "Directory '#{project}' already exists!"
29
+ return
30
+ end
31
+
32
+ settings = Clerq.settings
33
+ tts = [
34
+ {tt: 'new/README.md.tt', target: 'README.md'},
35
+ {tt: 'new/clerq.yml.tt', target: 'clerq.yml'},
36
+ {tt: 'new/clerq.thor.tt', target: "#{project}.thor"},
37
+ {tt: 'new/content.md.tt', target: File.join(settings.src, "#{project}.md")}
38
+ ]
39
+
40
+ Dir.mkdir(project)
41
+ Dir.chdir(project) do
42
+ settings.folders.each{|f| Dir.mkdir(f)}
43
+ tts.each do |tt|
44
+ template(tt[:tt], File.join(Dir.pwd, tt[:target]), {project: project})
45
+ end
46
+ directory('tt', File.join(Dir.pwd, 'tt'))
47
+ say "Project created!"
48
+ end
49
+ end
50
+
51
+ desc "promo", "Copy content of promo project"
52
+ def promo
53
+ say "Copying promo content ..."
54
+ directory('promo', Dir.pwd)
55
+ end
56
+
57
+ desc "build [OPTIONS]", "Build the clerq project"
58
+ method_option :query, aliases: "-q", type: :string, desc: "query"
59
+ method_option :tt, aliases: "-t", type: :string, desc: "template"
60
+ method_option :output, aliases: "-o", type: :string, desc: "output file"
61
+ def build
62
+ settings = Clerq.settings
63
+ document = options[:output] || settings.document + '.md'
64
+ template = options[:tt] || settings.template
65
+ query = options[:query] || ''
66
+ build = File.join(settings.bin, document)
67
+ content = CompileNodes.(template: template, query: query)
68
+ File.write(build, content)
69
+ say "'#{build}' created!"
70
+ rescue CompileNodes::Failure => e
71
+ error e.message
72
+ end
73
+
74
+ desc "check", "Check the project for errors"
75
+ def check
76
+ errors = CheckNodes.()
77
+ if errors.empty?
78
+ say "No errors found"
79
+ return
80
+ end
81
+
82
+ CHECK_MESSAGES.each do |key, msg|
83
+ if errors.key?(key)
84
+ say "The following #{msg[0]}:"
85
+ errors[key].each{|k,v| say "\t#{k}\t#{msg[1]} #{v.join(', ')}"}
86
+ end
87
+ end
88
+ rescue CheckNodes::Failure => e
89
+ error e.message
90
+ end
91
+
92
+ CHECK_MESSAGES = {
93
+ nonuniq_ids: ['node identifiers are non-uniqe', 'in'],
94
+ unknown_parents: ['meta[:parent] not found', 'in'],
95
+ unknown_references: ['links are unknown', 'in'],
96
+ unknown_order_index: ['node meta[:order_index] unknown', ':']
97
+ }.freeze
98
+
99
+ desc "node ID [TITLE]", "Create a new node"
100
+ method_option :template, aliases: "-t", type: :string, desc: "template"
101
+ def node(id, title = '')
102
+ settings = Clerq.settings
103
+ file = File.join(settings.src, "#{id}.md")
104
+ if File.exist?(file)
105
+ error "File already exists #{fn}"
106
+ return
107
+ end
108
+ template = options[:template] || ''
109
+ CreateNode.(id: id, title: title, template: template)
110
+ say "'#{file}' created"
111
+ rescue CreateNode::Failure => e
112
+ error e.message
113
+ end
114
+
115
+ desc "toc [OPTIONS]", "Print the project TOC"
116
+ method_option :query, aliases: "-q", type: :string, desc: "Query"
117
+ def toc
118
+ query = options[:query]
119
+ node = query ? QueryNodes.(query: query) : JoinNodes.()
120
+ puts "% #{node.title}"
121
+ node.to_a.drop(1).each{|n|
122
+ puts "#{' ' * (n.nesting_level - 1)}[#{n.id}] #{n.title}"
123
+ }
124
+ rescue QueryNodes::Failure, JoinNodes::Failure => e
125
+ error e.message
126
+ end
127
+
128
+ end
129
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'entities/node'
2
+ require_relative 'entities/template'