clerq 0.1.0 → 0.3.3

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 +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 %>