clerq 0.1.0 → 0.3.3

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 +4 -4
  2. data/.gitignore +4 -3
  3. data/CHANGELOG.md +49 -0
  4. data/Gemfile.lock +9 -9
  5. data/README.md +260 -133
  6. data/Rakefile +1 -0
  7. data/clerq.gemspec +6 -6
  8. data/clerq.thor +28 -0
  9. data/docs/README.md +408 -0
  10. data/lib/assets/knb/SRS-IEEE-830-1998.md +293 -0
  11. data/lib/assets/knb/SRS-RUP.md +283 -0
  12. data/lib/assets/knb/business-case.md +135 -0
  13. data/lib/assets/knb/ears-with-examples.md +101 -0
  14. data/lib/assets/knb/problem-statement.md +8 -0
  15. data/lib/assets/knb/product-statement.md +8 -0
  16. data/lib/assets/knb/requirement-attributes.md +26 -0
  17. data/lib/assets/knb/requirement-classification.md +27 -0
  18. data/lib/assets/knb/requirement-life-cycle.md +47 -0
  19. data/lib/assets/knb/requirement-quality.md +13 -0
  20. data/lib/assets/knb/use-case.md +39 -0
  21. data/lib/assets/knb/vision-document.md +191 -0
  22. data/lib/assets/lib/clerq_doc.thor +119 -0
  23. data/lib/assets/lib/colonize_repo.rb +82 -0
  24. data/lib/assets/lib/spec/colonize_repo_spec.rb +85 -0
  25. data/lib/assets/new/clerq.thor.tt +32 -5
  26. data/lib/assets/new/content.md.tt +3 -40
  27. data/lib/assets/tt/default.md.erb +23 -42
  28. data/lib/assets/tt/pandoc.md.erb +11 -8
  29. data/lib/clerq.rb +57 -12
  30. data/lib/clerq/cli.rb +77 -60
  31. data/lib/clerq/entities.rb +0 -1
  32. data/lib/clerq/entities/node.rb +135 -115
  33. data/lib/clerq/properties.rb +1 -3
  34. data/lib/clerq/repositories.rb +2 -4
  35. data/lib/clerq/repositories/file_repository.rb +59 -0
  36. data/lib/clerq/repositories/node_repository.rb +72 -30
  37. data/lib/clerq/repositories/text_repository.rb +47 -0
  38. data/lib/clerq/services.rb +8 -0
  39. data/lib/clerq/services/check_assembly.rb +108 -0
  40. data/lib/clerq/{interactors → services}/create_node.rb +12 -11
  41. data/lib/clerq/services/load_assembly.rb +54 -0
  42. data/lib/clerq/services/query_node.rb +72 -0
  43. data/lib/clerq/services/query_template.rb +26 -0
  44. data/lib/clerq/services/read_node.rb +101 -0
  45. data/lib/clerq/services/render_erb.rb +29 -0
  46. data/lib/clerq/services/render_node.rb +37 -0
  47. data/lib/clerq/services/service.rb +19 -0
  48. data/lib/clerq/settings.rb +2 -2
  49. data/lib/clerq/version.rb +1 -1
  50. metadata +49 -37
  51. data/TODO.md +0 -3
  52. data/lib/assets/tt/gitlab.md.erb +0 -93
  53. data/lib/assets/tt/raw.md.erb +0 -23
  54. data/lib/clerq/entities/template.rb +0 -19
  55. data/lib/clerq/gateways.rb +0 -3
  56. data/lib/clerq/gateways/gateway.rb +0 -17
  57. data/lib/clerq/gateways/in_files.rb +0 -36
  58. data/lib/clerq/gateways/in_memory.rb +0 -35
  59. data/lib/clerq/interactors.rb +0 -5
  60. data/lib/clerq/interactors/check_nodes.rb +0 -81
  61. data/lib/clerq/interactors/compile_nodes.rb +0 -31
  62. data/lib/clerq/interactors/interactor.rb +0 -28
  63. data/lib/clerq/interactors/join_nodes.rb +0 -59
  64. data/lib/clerq/interactors/query_nodes.rb +0 -62
  65. data/lib/clerq/repositories/in_memory.rb +0 -45
  66. data/lib/clerq/repositories/node_reader.rb +0 -107
  67. data/lib/clerq/repositories/repository.rb +0 -11
  68. data/lib/clerq/repositories/template_repository.rb +0 -53
  69. data/lib/clerq/templater.rb +0 -32
@@ -0,0 +1,119 @@
1
+ require 'clerq'
2
+ require 'thor'
3
+ require 'tmpdir'
4
+ require_relative 'lib/colonize_repo'
5
+ include Clerq::Repositories
6
+ include Clerq::Entities
7
+ include Clerq::Services
8
+
9
+ # This thor file sample was created with purpose to show some examples
10
+ # of using Pandoc for clerq reposiory publishing and even import souce
11
+ # documents into a clerq repository ... `thor clerq:doc:grab`, `thor clerq:doc:publish`
12
+ class ClerqDoc < Thor
13
+ include Thor::Actions
14
+ namespace 'clerq:doc'.to_sym
15
+
16
+ no_commands {
17
+ def eqid!(node)
18
+ counter = {}
19
+ node.to_a.drop(1).each do |n|
20
+ index = counter[n.parent] || 1
21
+ counter[n.parent] = index + 1
22
+ id = index.to_s.rjust(2, '0')
23
+ id = '.' + id unless n.parent == node
24
+ n.id = id
25
+ end
26
+ end
27
+ }
28
+
29
+ # Why does one need to grab the document?
30
+ # it started in MS Word but the author decieded to pкoceed in Clerq
31
+ # it is the source for the clerq project, SRS based on Vision?
32
+ # something else?
33
+ # TODO provide -q/--query QUERY_STRING parameter
34
+ # TODO how about importing images?
35
+ desc 'grab FILENAME', 'Grab document and populate the clerq repository'
36
+ def grab(filename)
37
+ mdown = File.join(Dir.tmpdir, 'clerq.grab.md')
38
+ optns = [].tap{|o|
39
+ o << '--wrap=none'
40
+ o << '--atx-headers'
41
+ o << "#{filename}"
42
+ o << "-o #{mdown}"
43
+ }.join(' ')
44
+
45
+ `pandoc #{optns}`
46
+
47
+ on_error_callback = lambda {|err| puts err}
48
+ puts "Reading '#{mdown}'..."
49
+
50
+ # TODO handle query parameter
51
+ arry = ReadNode.(mdown, on_error_callback)
52
+ node = Node.new(id: '00', title: File.basename(mdown, '.md'))
53
+ arry.each{|n| node << n}
54
+ eqid!(node)
55
+
56
+ puts "Colonizing this repository..."
57
+ dir_counter = 0
58
+ src_counter = 0
59
+ on_create_dir = lambda {|dir| puts "Created '#{dir}' directory"; dir_counter += 1 }
60
+ on_create_src = lambda {|src| puts "Created '#{src}' file"; src_counter += 1 }
61
+
62
+ Dir.chdir(Clerq.src) { ColonizeRepo.(node, on_create_dir, on_create_src) }
63
+
64
+ msg = [].tap do |memo|
65
+ memo << "#{dir_counter} #{dir_counter == 1 ? 'directory' : 'directories'}" if dir_counter > 0
66
+ memo << "#{src_counter} #{src_counter == 1 ? 'file' : 'files'}" if src_counter > 0
67
+ end.join(' and ')
68
+
69
+ say "#{node.to_a.size} nodes from '#{filename}' imported to the repositroy. #{msg} created in the repository."
70
+ end
71
+
72
+ desc 'publish', 'Publishing final deliverables'
73
+ def publish
74
+ invoke :docx
75
+ invoke :html
76
+ end
77
+
78
+ desc 'html', 'Publish final deliverable in Html'
79
+ def html
80
+ source = File.join(Clerq.bin, Clerq.document + ".md")
81
+ target = File.join(Clerq.bin, Clerq.document + ".html")
82
+ optns = [].tap{|o|
83
+ o << '-s --toc --toc-depth 3'
84
+ o << "--resource-path #{Clerq.bin}"
85
+ o << "\"#{source}\""
86
+ o << "-o \"#{target}\""
87
+ }.join(" ")
88
+
89
+ `clerq build -t pandoc.md.erb`
90
+ `pandoc #{optns}`
91
+ say "\"#{target}\" created!"
92
+ end
93
+
94
+ desc 'docx', 'Publish final deliverable in Docx'
95
+ def docx
96
+ source = File.join(Clerq.bin, Clerq.document + ".md")
97
+ target = File.join(Clerq.bin, Clerq.document + ".docx")
98
+ sample = File.join(Clerq.bin, 'custom-reference.docx')
99
+
100
+ unless File.exist?(sample)
101
+ # produce a custom reference.docx
102
+ `pandoc -o #{sample} --print-default-data-file reference.docx`
103
+ end
104
+
105
+ optns = [].tap{|o|
106
+ o << '-s --toc --toc-depth 3'
107
+ o << '--from markdown+table_captions+implicit_figures'
108
+ o << "--reference-doc #{sample}"
109
+ o << "--resource-path #{Clerq.bin}"
110
+ o << "\"#{source}\""
111
+ o << "-o \"#{target}\""
112
+ }.join(" ")
113
+
114
+ `clerq build -t pandoc.md.erb`
115
+ `pandoc #{optns}`
116
+ say "\"#{target}\" created!"
117
+ end
118
+
119
+ end
@@ -0,0 +1,82 @@
1
+ require 'clerq'
2
+ require 'erb'
3
+ include Clerq::Entities
4
+ include Clerq::Services
5
+
6
+ # Creates clerq repository sources in current work directory
7
+ # Warning! Change work dierectory before calling for the serice
8
+ # Usage
9
+ # node = Clerq.node_repository.assembly
10
+ # Dir.chdir(Clerq.src) { ColonizeRepo.(node) }
11
+ class ColonizeRepo < Service
12
+ private_class_method :new
13
+
14
+ def call
15
+ write(@node)
16
+ end
17
+
18
+ # TODO callback? and calback for ReadNode.()?
19
+ def write(node)
20
+ dir = folder(node)
21
+ src = source(node)
22
+ txt = text(node)
23
+ unless dir.empty? || Dir.exist?(dir)
24
+ Dir.mkdir(dir)
25
+ @on_create_dir.call(dir) if @on_create_dir
26
+ end
27
+ File.write(src, txt)
28
+ @on_create_file.call(src) if @on_create_file
29
+ node.items.reject{|n| n.items.empty?}.each{|n| write(n)}
30
+ end
31
+
32
+ # @param node [Node] the node for colonization
33
+ # @param on_create_file [Block(arg)] on create new file callback
34
+ # @param on_create_dir [Block(arg)] on create new directory callback
35
+ def initialize(node, on_create_dir = nil, on_create_file = nil)
36
+ @node = node
37
+ @on_create_dir = on_create_dir
38
+ @on_create_file = on_create_file
39
+ end
40
+
41
+ def source(node)
42
+ fld = folder(node)
43
+ src = filename(node)
44
+ fld.empty? ? src : File.join(fld, src)
45
+ end
46
+
47
+ def folder(node)
48
+ dir = ''
49
+ n = node
50
+ while n != @node.root && n.parent != @node.root
51
+ dir = File.join("#{n.parent.id} #{n.parent.title}", dir)
52
+ n = n.parent
53
+ end
54
+ dir
55
+ end
56
+
57
+ def filename(node)
58
+ "#{node.id} #{node.title}.md"
59
+ end
60
+
61
+ # TODO replace to services
62
+ def text(node)
63
+ RenderErb.(erb: TEMPLATE, object: node)
64
+ end
65
+
66
+ TEMPLATE = <<~EOF
67
+ # <%= @object.title %>
68
+ {{id: <%= @object.id %><%= ", parent: " + @object.parent.id if @object.parent %>, order_index: <%= @object.items.map(&:id).join(' ') %>}}
69
+
70
+ <%= @object.body %>
71
+
72
+ <% for n in @object.items -%>
73
+ <% next unless n.items.empty? -%>
74
+ ## <%= n.title %>
75
+ {{id: <%= n.id %>}}
76
+
77
+ <%= n.body %>
78
+
79
+ <% end %>
80
+ EOF
81
+
82
+ end
@@ -0,0 +1,85 @@
1
+ require 'clerq'
2
+ require 'erb'
3
+ require 'minitest/autorun'
4
+ require_relative 'colonize_repo'
5
+ include Clerq::Entities
6
+ include Clerq::Services
7
+
8
+ describe ColonizeRepo do
9
+ class FakeWriter < ColonizeRepo
10
+ public_class_method :new
11
+ attr_reader :node
12
+ end
13
+
14
+ let(:node) {
15
+ Node.new(id: '0', title: 'Import').tap{|n|
16
+ n << Node.new(id: '01', title: 'User')
17
+ n << Node.new(id: '02', title: 'Func')
18
+ n.item('01') << Node.new(id: '01.01', title: 'Story 1')
19
+ n.item('01') << Node.new(id: '01.02', title: 'Story 2')
20
+ }
21
+ }
22
+
23
+ let(:writer) { FakeWriter.new(node) }
24
+
25
+ describe '#folder' do
26
+ it 'must return parent.id + parent.name' do
27
+ spec = writer.node.map{|n| writer.folder(n)}
28
+ _(spec).must_equal([
29
+ '', '', '01 User/', '01 User/', ''
30
+ ])
31
+ end
32
+ end
33
+
34
+ describe '#filename' do
35
+ it 'must return node.id + node.title' do
36
+ spec = writer.node.map{|n| writer.filename(n)}
37
+ _(spec).must_equal([
38
+ '0 Import.md', '01 User.md', '01.01 Story 1.md',
39
+ '01.02 Story 2.md', '02 Func.md'
40
+ ])
41
+ end
42
+ end
43
+
44
+ describe '#source' do
45
+ # title and first level shall be paced in root folder
46
+ it 'must return node.id + node.title' do
47
+ spec = writer.node.map{|n| writer.source(n)}
48
+ _(spec).must_equal([
49
+ '0 Import.md',
50
+ '01 User.md',
51
+ '01 User/01.01 Story 1.md',
52
+ '01 User/01.02 Story 2.md',
53
+ '02 Func.md'
54
+ ])
55
+ end
56
+ end
57
+
58
+ describe '#write(node)' do
59
+ let(:node) {
60
+ Node.new(id: '0', title: 'Import').tap{|n|
61
+ n << Node.new(id: '01', title: 'User')
62
+ n << Node.new(id: '02', title: 'Func')
63
+ n.item('01') << Node.new(id: '01.01', title: 'Story 1')
64
+ n.item('01') << Node.new(id: '01.02', title: 'Story 2')
65
+ n.node('01.01') << Node.new(id: '01.01.01', title: 'Story 1.1')
66
+ n.node('01.01') << Node.new(id: '01.01.02', title: 'Story 1.2')
67
+ }
68
+ }
69
+
70
+ it 'must build repo' do
71
+ files = ['0 Import.md', '01 User.md', '01 User/01.01 Story 1.md']
72
+ Dir.mktmpdir(['clerq']) do |dir|
73
+ Dir.chdir(dir) do
74
+ ColonizeRepo.(node)
75
+ files.each{|fn| _(File.exist?(fn)).must_equal true }
76
+ _(File.read('0 Import.md')).wont_match "parent:"
77
+ _(File.read('01 User.md')).must_match "parent: 0"
78
+ _(File.read('01 User/01.01 Story 1.md')).must_match "parent: 01"
79
+ end
80
+ end
81
+
82
+ end
83
+ end
84
+
85
+ end
@@ -1,15 +1,42 @@
1
- # TODO how about to include commands automatically from 'lib' folder
2
-
3
- require 'thor'
4
1
  require 'clerq'
2
+ require 'thor'
5
3
 
6
- class <%=config[:project].capitalize%> < Thor
4
+ class <%=config[:klass]%> < Thor
7
5
  include Thor::Actions
8
6
 
9
7
  desc "stat", "Print statistic"
10
8
  def stat
11
- nodes = Clerq::Interactors::JoinNodes.()
9
+ nodes = Clerq.assemble
12
10
  say "#{nodes.to_a.drop(1).size} nodes total"
13
11
  end
14
12
 
13
+ desc "mm", "Create meeting minutes"
14
+ def mm
15
+ minutes = "meeting minutes #{Time.new.strftime('%Y-%m-%d')}.md"
16
+ content = "% #{minutes.capitalize}\n\n" + MINUTES_TEMPLATE
17
+ Dir.mkdir('mm') unless Dir.exist?('mm')
18
+ File.write("mm/#{minutes}", content)
19
+ say "'mm/#{minutes}' created!"
20
+ end
21
+
22
+ MINUTES_TEMPLATE = <<~EOF
23
+ # Attendants
24
+
25
+ 1.
26
+ 2.
27
+ 3.
28
+
29
+ # Questions
30
+
31
+ 1.
32
+ 2.
33
+ 3.
34
+
35
+ # Resolutions
36
+
37
+ 1.
38
+ 2.
39
+ 3.
40
+ EOF
41
+
15
42
  end
@@ -1,41 +1,4 @@
1
- # Introduction
2
- {{skip_meta: true}}
1
+ # <%=config[:project]%>
2
+ {{id: 0}}
3
3
 
4
- {{@@skip
5
-
6
- This file was created automatically from the premise that you could want to have the high-level structure of the project (document) in a single place.
7
-
8
- For the usual descriptive parts (nodes) that do not need unique identifiers - use `skip_meta: true` attribute. To output child parts in appropriate order - use the `order_index: nx ny nz` attribute.
9
-
10
- Pay attention that this content placed under `skip` macro that means this content won't be included in the output document.}}
11
-
12
- ## Purpose
13
- {{skip_meta: true}}
14
-
15
- The purpose of this document is to provide a detailed product description of the <%= config[:project] %>. The document describes the product from user and software requirements perspectives.
16
-
17
- All parties who are responsible for the definition and implementation of the project should read and understand this document.
18
-
19
- ## Scope
20
- {{skip_meta: true}}
21
-
22
- ## Definitions, acronyms, and abbreviations
23
- {{skip_meta: true}}
24
-
25
- ## References
26
- {{skip_meta: true}}
27
-
28
- ## Overview
29
- {{skip_meta: true}}
30
-
31
- # Users and Stakeholders
32
- {{skip_meta: true}}
33
-
34
- # User requirements
35
- {{skip_meta: true}}
36
-
37
- # Functional requirements
38
- {{skip_meta: true}}
39
-
40
- # Nonfunctional requirements
41
- {{skip_meta: true}}
4
+ This file was created automatically by the `clerq new` command as the root node of your project. To familiarize yourself with how to start writing with the Clerq - please read the [Writing](https://github.com/nvoynov/clerq/blob/master/README.md#writing) section and then just delete the content of the paragraph.
@@ -1,64 +1,45 @@
1
1
  <%
2
- # Template for drafts
2
+
3
3
  require 'delegate'
4
+
4
5
  class MarkupNode < SimpleDelegator
5
6
  def title
6
- '#' * nesting_level + ' ' + super
7
+ s = super
8
+ s = ".#{id.split(/\./).last}" if s.empty?
9
+ '#' * nesting_level + ' ' + s
7
10
  end
8
11
 
9
12
  def meta
10
13
  return '' if nesting_level == 0
11
- return '' if super[:skip_meta]
12
-
13
- hsh = {id: id}.merge(super)
14
- hsh.delete(:order_index)
14
+ hsh = {}.merge(super)
15
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
16
+ hsh.delete(:order_index)
17
+ hsh.delete(:filename)
18
+ return '' if hsh.empty?
37
19
 
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}
20
+ hsh.map{|k, v| "#{k}: #{v}"}
21
+ .unshift("{{")
22
+ .push("}}")
23
+ .join("\n")
46
24
  end
47
25
 
48
- def url(id)
49
- id.downcase
50
- .gsub(/[^A-Za-z0-9]{1,}/, '-')
51
- .gsub(/^-/, '')
52
- .gsub(/-$/, '')
26
+ def markup
27
+ [].tap do |o|
28
+ o << title
29
+ o << meta unless meta.empty?
30
+ unless body.empty?
31
+ o << "\n#{body}"
32
+ end
33
+ end.join("\n")
53
34
  end
54
-
55
35
  end
56
36
  -%>
57
37
  % <%= @object.title %>
58
38
  % generated by Clerq on <%= Time.now.strftime('%B %e, %Y at %H:%M') %>
39
+ % default template
59
40
 
60
41
  <% for @node in @object.to_a.drop(1) -%>
61
42
  <% n = MarkupNode.new(@node) -%>
62
- <%= [n.title, n.meta, n.body].select{|t| !t.empty?}.join("\n\n") %>
43
+ <%= n.markup %>
63
44
 
64
45
  <% end %>