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.
- checksums.yaml +4 -4
- data/.gitignore +4 -3
- data/CHANGELOG.md +49 -0
- data/Gemfile.lock +9 -9
- data/README.md +260 -133
- data/Rakefile +1 -0
- data/clerq.gemspec +6 -6
- data/clerq.thor +28 -0
- data/docs/README.md +408 -0
- data/lib/assets/knb/SRS-IEEE-830-1998.md +293 -0
- data/lib/assets/knb/SRS-RUP.md +283 -0
- data/lib/assets/knb/business-case.md +135 -0
- data/lib/assets/knb/ears-with-examples.md +101 -0
- data/lib/assets/knb/problem-statement.md +8 -0
- data/lib/assets/knb/product-statement.md +8 -0
- data/lib/assets/knb/requirement-attributes.md +26 -0
- data/lib/assets/knb/requirement-classification.md +27 -0
- data/lib/assets/knb/requirement-life-cycle.md +47 -0
- data/lib/assets/knb/requirement-quality.md +13 -0
- data/lib/assets/knb/use-case.md +39 -0
- data/lib/assets/knb/vision-document.md +191 -0
- data/lib/assets/lib/clerq_doc.thor +119 -0
- data/lib/assets/lib/colonize_repo.rb +82 -0
- data/lib/assets/lib/spec/colonize_repo_spec.rb +85 -0
- data/lib/assets/new/clerq.thor.tt +32 -5
- data/lib/assets/new/content.md.tt +3 -40
- data/lib/assets/tt/default.md.erb +23 -42
- data/lib/assets/tt/pandoc.md.erb +11 -8
- data/lib/clerq.rb +57 -12
- data/lib/clerq/cli.rb +77 -60
- data/lib/clerq/entities.rb +0 -1
- data/lib/clerq/entities/node.rb +135 -115
- data/lib/clerq/properties.rb +1 -3
- data/lib/clerq/repositories.rb +2 -4
- data/lib/clerq/repositories/file_repository.rb +59 -0
- data/lib/clerq/repositories/node_repository.rb +72 -30
- data/lib/clerq/repositories/text_repository.rb +47 -0
- data/lib/clerq/services.rb +8 -0
- data/lib/clerq/services/check_assembly.rb +108 -0
- data/lib/clerq/{interactors → services}/create_node.rb +12 -11
- data/lib/clerq/services/load_assembly.rb +54 -0
- data/lib/clerq/services/query_node.rb +72 -0
- data/lib/clerq/services/query_template.rb +26 -0
- data/lib/clerq/services/read_node.rb +101 -0
- data/lib/clerq/services/render_erb.rb +29 -0
- data/lib/clerq/services/render_node.rb +37 -0
- data/lib/clerq/services/service.rb +19 -0
- data/lib/clerq/settings.rb +2 -2
- data/lib/clerq/version.rb +1 -1
- metadata +49 -37
- data/TODO.md +0 -3
- data/lib/assets/tt/gitlab.md.erb +0 -93
- data/lib/assets/tt/raw.md.erb +0 -23
- data/lib/clerq/entities/template.rb +0 -19
- data/lib/clerq/gateways.rb +0 -3
- data/lib/clerq/gateways/gateway.rb +0 -17
- data/lib/clerq/gateways/in_files.rb +0 -36
- data/lib/clerq/gateways/in_memory.rb +0 -35
- data/lib/clerq/interactors.rb +0 -5
- data/lib/clerq/interactors/check_nodes.rb +0 -81
- data/lib/clerq/interactors/compile_nodes.rb +0 -31
- data/lib/clerq/interactors/interactor.rb +0 -28
- data/lib/clerq/interactors/join_nodes.rb +0 -59
- data/lib/clerq/interactors/query_nodes.rb +0 -62
- data/lib/clerq/repositories/in_memory.rb +0 -45
- data/lib/clerq/repositories/node_reader.rb +0 -107
- data/lib/clerq/repositories/repository.rb +0 -11
- data/lib/clerq/repositories/template_repository.rb +0 -53
- data/lib/clerq/templater.rb +0 -32
data/lib/assets/tt/pandoc.md.erb
CHANGED
@@ -3,7 +3,9 @@ require 'delegate'
|
|
3
3
|
# Template for generate documents by Pandoc
|
4
4
|
class MarkupNode < SimpleDelegator
|
5
5
|
def title
|
6
|
-
|
6
|
+
s = super
|
7
|
+
s = ".#{id.split(/\./).last}" if s.empty?
|
8
|
+
"#{'#' * nesting_level} #{s} {##{url(id)}}"
|
7
9
|
end
|
8
10
|
|
9
11
|
def meta
|
@@ -12,7 +14,7 @@ class MarkupNode < SimpleDelegator
|
|
12
14
|
|
13
15
|
hsh = {id: id}.merge(super)
|
14
16
|
hsh.delete(:order_index)
|
15
|
-
hsh.delete(:
|
17
|
+
hsh.delete(:filename)
|
16
18
|
hsh.delete(:parent)
|
17
19
|
[].tap{|ary|
|
18
20
|
ary << "Attribute | Value"
|
@@ -37,11 +39,11 @@ class MarkupNode < SimpleDelegator
|
|
37
39
|
end
|
38
40
|
|
39
41
|
def url(id)
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
r = id.start_with?(/[[:digit:]]/) ? "p#{id}" : id
|
43
|
+
r.downcase
|
44
|
+
.gsub(/[^A-Za-z0-9]{1,}/, '-')
|
45
|
+
.gsub(/^-/, '')
|
46
|
+
.gsub(/-$/, '')
|
45
47
|
end
|
46
48
|
|
47
49
|
class Macro
|
@@ -79,7 +81,8 @@ class MarkupNode < SimpleDelegator
|
|
79
81
|
end
|
80
82
|
-%>
|
81
83
|
% <%= @object.title %>
|
82
|
-
% generated by Clerq on <%= Time.now.strftime('%B %e, %Y at %H:%M') %>
|
84
|
+
% generated by Clerq on <%= Time.now.strftime('%B %e, %Y at %H:%M') %>
|
85
|
+
% pandoc template
|
83
86
|
|
84
87
|
<% for @node in @object.to_a.drop(1) -%>
|
85
88
|
<% n = MarkupNode.new(@node) -%>
|
data/lib/clerq.rb
CHANGED
@@ -1,39 +1,84 @@
|
|
1
1
|
require_relative 'clerq/version'
|
2
2
|
require_relative 'clerq/entities'
|
3
|
-
require_relative 'clerq/
|
4
|
-
require_relative 'clerq/interactors'
|
3
|
+
require_relative 'clerq/services'
|
5
4
|
require_relative 'clerq/repositories'
|
6
5
|
require_relative 'clerq/properties'
|
7
6
|
require_relative 'clerq/settings'
|
8
|
-
require_relative 'clerq/templater'
|
9
7
|
require_relative 'clerq/cli'
|
8
|
+
include Clerq::Repositories
|
10
9
|
|
11
10
|
module Clerq
|
12
11
|
class Error < StandardError; end
|
13
12
|
|
14
13
|
class << self
|
14
|
+
# TODO try forwardable there for bin, tt, title, output, etc.
|
15
15
|
|
16
16
|
def root
|
17
17
|
File.dirname __dir__
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
21
|
-
@
|
20
|
+
def settings
|
21
|
+
@settings ||= Settings.new
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
25
|
-
|
26
|
-
raise ArgumentError, errmsg unless gateway.is_a? Clerq::Gateways::Gateway
|
27
|
-
@gateway = gateway
|
24
|
+
def title
|
25
|
+
settings.title
|
28
26
|
end
|
29
27
|
|
30
|
-
def
|
31
|
-
|
28
|
+
def document
|
29
|
+
settings.document
|
30
|
+
end
|
31
|
+
|
32
|
+
def template
|
33
|
+
settings.template
|
34
|
+
end
|
35
|
+
|
36
|
+
def bin
|
37
|
+
settings.bin
|
38
|
+
end
|
39
|
+
|
40
|
+
def src
|
41
|
+
settings.src
|
42
|
+
end
|
43
|
+
|
44
|
+
def tt
|
45
|
+
settings.tt
|
32
46
|
end
|
33
47
|
|
34
48
|
def reset
|
35
|
-
@gateway = nil
|
36
49
|
@settings = nil
|
50
|
+
@node_repository = nil
|
51
|
+
@text_repository = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def text_repository
|
55
|
+
@text_repository ||= TextRepository.new(path: File.join(Dir.pwd, tt))
|
56
|
+
end
|
57
|
+
|
58
|
+
def text_repository=(repository)
|
59
|
+
unless repository.is_a? TextRepository
|
60
|
+
err = "Invalid argument. Clerq::Repositories::TextRepository required"
|
61
|
+
raise ArgumentError, err
|
62
|
+
end
|
63
|
+
@text_repository = repository
|
64
|
+
end
|
65
|
+
|
66
|
+
def node_repository
|
67
|
+
@node_repository ||= NodeRepository.new(path: File.join(Dir.pwd, src))
|
68
|
+
end
|
69
|
+
|
70
|
+
def node_repository=(repository)
|
71
|
+
unless repository.is_a? NodeRepository
|
72
|
+
err = "Invalid argument. Clerq::Repositories::NodeRepository required"
|
73
|
+
raise ArgumentError, err
|
74
|
+
end
|
75
|
+
@node_repository = repository
|
76
|
+
end
|
77
|
+
|
78
|
+
def assemble(on_parse: nil, on_error: nil)
|
79
|
+
node_repository.assemble(
|
80
|
+
on_parse: on_parse,
|
81
|
+
on_error: on_error)
|
37
82
|
end
|
38
83
|
|
39
84
|
end
|
data/lib/clerq/cli.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
3
|
require 'thor'
|
4
|
-
require_relative '
|
5
|
-
include Clerq::
|
4
|
+
require_relative 'services'
|
5
|
+
include Clerq::Services
|
6
6
|
|
7
7
|
module Clerq
|
8
8
|
|
@@ -14,36 +14,72 @@ module Clerq
|
|
14
14
|
File.join Clerq.root, "lib/assets"
|
15
15
|
end
|
16
16
|
|
17
|
+
def self.exit_on_failure?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
17
21
|
desc "--version, -v", "Print the version"
|
18
22
|
def version
|
19
23
|
puts "Clerq v#{Clerq::VERSION}"
|
20
24
|
end
|
21
25
|
map %w[--version -v] => :version
|
22
26
|
|
27
|
+
no_commands {
|
28
|
+
# @param [String]
|
29
|
+
# @returns [String] usual name for ruby file
|
30
|
+
def thor_filename(str);
|
31
|
+
str.split(/[\W+_]/).map(&:downcase).join('_') + '.thor'
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param [String]
|
35
|
+
# @returns [String] usual name for ruby class
|
36
|
+
def ruby_class_name(str);
|
37
|
+
str.split(/[\W+_]/).map(&:capitalize).join
|
38
|
+
end
|
39
|
+
|
40
|
+
def clerq_project?
|
41
|
+
File.exist?(Clerq::Settings::STORAGE) || Dir.exist?(Clerq.settings.src)
|
42
|
+
end
|
43
|
+
|
44
|
+
def stop_unless_clerq!
|
45
|
+
stop! "Clerq project required!" unless clerq_project?
|
46
|
+
end
|
47
|
+
|
48
|
+
def stop!(msg)
|
49
|
+
raise Thor::Error, msg
|
50
|
+
end
|
51
|
+
|
52
|
+
def query_assembly(query)
|
53
|
+
# TODO pretty errors ...OK, ... 1 error found, ... 2 errors found
|
54
|
+
on_parse = lambda {|src| puts "Reading '#{src}'... "}
|
55
|
+
on_error = lambda {|err| puts "\terror: #{err} "}
|
56
|
+
QueryAssembly.(query: query, on_parse: on_parse, on_error: on_error)
|
57
|
+
end
|
58
|
+
}
|
59
|
+
|
23
60
|
desc "new PROJECT", "Create a new Clerq project"
|
24
61
|
def new(project)
|
62
|
+
stop! "'#{project}' folder already exists!" if Dir.exist?(project)
|
25
63
|
say "Creating project '#{project}'..."
|
26
64
|
|
27
|
-
if Dir.exist?(project)
|
28
|
-
error "Directory '#{project}' already exists!"
|
29
|
-
return
|
30
|
-
end
|
31
|
-
|
32
65
|
settings = Clerq.settings
|
33
66
|
tts = [
|
34
67
|
{tt: 'new/README.md.tt', target: 'README.md'},
|
35
|
-
{tt: 'new/clerq.yml.tt', target:
|
36
|
-
{tt: 'new/clerq.thor.tt', target:
|
68
|
+
{tt: 'new/clerq.yml.tt', target: Clerq::Settings::STORAGE},
|
69
|
+
{tt: 'new/clerq.thor.tt', target: thor_filename(project)},
|
37
70
|
{tt: 'new/content.md.tt', target: File.join(settings.src, "#{project}.md")}
|
38
71
|
]
|
39
72
|
|
73
|
+
config = {project: project, klass: ruby_class_name(project)}
|
74
|
+
|
40
75
|
Dir.mkdir(project)
|
41
76
|
Dir.chdir(project) do
|
42
77
|
settings.folders.each{|f| Dir.mkdir(f)}
|
43
78
|
tts.each do |tt|
|
44
|
-
template(tt[:tt], File.join(Dir.pwd, tt[:target]),
|
79
|
+
template(tt[:tt], File.join(Dir.pwd, tt[:target]), config)
|
45
80
|
end
|
46
81
|
directory('tt', File.join(Dir.pwd, 'tt'))
|
82
|
+
directory('lib', File.join(Dir.pwd, 'lib'))
|
47
83
|
say "Project created!"
|
48
84
|
end
|
49
85
|
end
|
@@ -52,6 +88,7 @@ module Clerq
|
|
52
88
|
def promo
|
53
89
|
say "Copying promo content ..."
|
54
90
|
directory('promo', Dir.pwd)
|
91
|
+
say "Copied!"
|
55
92
|
end
|
56
93
|
|
57
94
|
desc "build [OPTIONS]", "Build the clerq project"
|
@@ -59,70 +96,50 @@ module Clerq
|
|
59
96
|
method_option :tt, aliases: "-t", type: :string, desc: "template"
|
60
97
|
method_option :output, aliases: "-o", type: :string, desc: "output file"
|
61
98
|
def build
|
62
|
-
|
63
|
-
document = options[:output] ||
|
64
|
-
template = options[:tt] ||
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
99
|
+
stop_unless_clerq!
|
100
|
+
document = options[:output] || Clerq.document + '.md'
|
101
|
+
template = options[:tt] || Clerq.template
|
102
|
+
build = File.join(Clerq.bin, document)
|
103
|
+
|
104
|
+
node = LoadAssembly.()
|
105
|
+
node = QueryNode.(assembly: node, query: options[:query]) if options[:query]
|
106
|
+
text = RenderNode.(node: node, template: template)
|
107
|
+
File.write(build, text)
|
69
108
|
say "'#{build}' created!"
|
70
|
-
rescue
|
71
|
-
|
109
|
+
rescue StandardError => e
|
110
|
+
stop!(e.message)
|
72
111
|
end
|
73
112
|
|
74
113
|
desc "check", "Check the project for errors"
|
75
114
|
def check
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
115
|
+
stop_unless_clerq!
|
116
|
+
puts "Checking assembly for writing errors..."
|
117
|
+
CheckAssembly.(LoadAssembly.())
|
113
118
|
end
|
114
119
|
|
115
120
|
desc "toc [OPTIONS]", "Print the project TOC"
|
116
121
|
method_option :query, aliases: "-q", type: :string, desc: "Query"
|
117
122
|
def toc
|
118
|
-
|
119
|
-
node =
|
123
|
+
stop_unless_clerq!
|
124
|
+
node = LoadAssembly.()
|
125
|
+
node = QueryNode.(assembly: node, query: options[:query]) if options[:query]
|
120
126
|
puts "% #{node.title}"
|
127
|
+
puts "% #{node[:query]}" if node[:query]
|
121
128
|
node.to_a.drop(1).each{|n|
|
122
129
|
puts "#{' ' * (n.nesting_level - 1)}[#{n.id}] #{n.title}"
|
123
130
|
}
|
124
|
-
rescue
|
125
|
-
|
131
|
+
rescue StandardError => e
|
132
|
+
stop!(e.message)
|
133
|
+
end
|
134
|
+
|
135
|
+
desc "node ID [TITLE]", "Create a new node"
|
136
|
+
method_option :template, aliases: "-t", type: :string, desc: "template"
|
137
|
+
def node(id, title = '')
|
138
|
+
stop_unless_clerq!
|
139
|
+
fn = CreateNode.(id: id, title: title, template: options[:template] || '')
|
140
|
+
say "'#{fn}' created"
|
141
|
+
rescue StandardError => e
|
142
|
+
stop!(e.message)
|
126
143
|
end
|
127
144
|
|
128
145
|
end
|
data/lib/clerq/entities.rb
CHANGED
data/lib/clerq/entities/node.rb
CHANGED
@@ -3,133 +3,153 @@ require "forwardable"
|
|
3
3
|
|
4
4
|
module Clerq
|
5
5
|
module Entities
|
6
|
-
end
|
7
|
-
end
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
7
|
+
# The basic block of hierarchy and the single Clerq entity
|
8
|
+
#
|
9
|
+
# Usage
|
10
|
+
# n = Node.new(title: 'Part I')
|
11
|
+
# n << Node.new(title: 'Item 1')
|
12
|
+
# n << Node.new(title: 'Item 2')
|
13
|
+
# n.each{|i| puts i.tile} # and all power of Enumerable
|
14
|
+
class Clerq::Entities::Node
|
15
|
+
extend Forwardable
|
16
|
+
include Enumerable
|
17
|
+
|
18
|
+
# @!attribute [r] parent
|
19
|
+
# @return [Node] the parent of the node
|
20
|
+
attr_reader :parent
|
21
|
+
# @!attribute [r] title
|
22
|
+
# @return [String] the title of the node
|
23
|
+
attr_reader :title
|
24
|
+
|
25
|
+
# @!attribute [r] body
|
26
|
+
# @return [String] the body of the node
|
27
|
+
attr_reader :body
|
28
|
+
|
29
|
+
# @!attribute [r] meta
|
30
|
+
# @return [Hash<Symbol, String>] the metadata of the node
|
31
|
+
attr_reader :meta
|
32
|
+
|
33
|
+
# @!attribute [w] id
|
34
|
+
# @return [Hash<Symbol, String>] the metadata of the node
|
35
|
+
attr_writer :id
|
36
|
+
|
37
|
+
def_delegators :@meta, :[], :[]=
|
38
|
+
def_delegator :@items, :delete, :delete_item
|
39
|
+
def_delegator :@items, :last, :last_item
|
40
|
+
protected :delete_item
|
41
|
+
|
42
|
+
def initialize(id: '', title: '', body: '', meta: {})
|
43
|
+
raise ArgumentError, "Invalid argument :id" unless id.is_a? String
|
44
|
+
raise ArgumentError, "Invalid argument :title" unless title.is_a? String
|
45
|
+
raise ArgumentError, "Invalid argument :body" unless body.is_a? String
|
46
|
+
raise ArgumentError, "Invalid argument :meta" unless meta.is_a? Hash
|
47
|
+
id = meta.delete(:id) if id.empty? && meta[:id]
|
48
|
+
meta.delete(:id) unless id.empty?
|
49
|
+
@parent = nil
|
50
|
+
@items = []
|
51
|
+
@id = id
|
52
|
+
@title = title
|
53
|
+
@body = body
|
54
|
+
@meta = meta
|
55
|
+
end
|
36
56
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
57
|
+
def <<(node)
|
58
|
+
raise ArgumentError, "Invalid argument :node" unless node.is_a? Node
|
59
|
+
node.parent = self
|
60
|
+
@items << node
|
61
|
+
node
|
62
|
+
end
|
43
63
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
64
|
+
# @param id [String] the id of child node; when it starts with '.',
|
65
|
+
# the method will find nodes that id ends with the param
|
66
|
+
# @return [Node, nil] child node by provided id; when id not found
|
67
|
+
# it will return ni
|
68
|
+
def item(id)
|
69
|
+
return @items.find{|r| r.id.end_with? id[1..-1]} if id.start_with? '.'
|
70
|
+
@items.find{|r| r.id.eql? id}
|
71
|
+
end
|
51
72
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
source = Array.new(@items)
|
57
|
-
order = @meta[:order_index]
|
58
|
-
[].tap do |ordered|
|
59
|
-
order.split(/ /).each do |o|
|
60
|
-
e = source.delete(item(o))
|
61
|
-
ordered << e if e
|
73
|
+
# @return [Array<String>] of ids from meta[:order_index]
|
74
|
+
def order_index
|
75
|
+
return [] unless @meta[:order_index]
|
76
|
+
@meta[:order_index].strip.gsub(/[\s]{2,}/, ' ').split(/\s/)
|
62
77
|
end
|
63
|
-
ordered.concat(source)
|
64
|
-
end
|
65
|
-
end
|
66
78
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
79
|
+
# @return [Array<Node>] list of child nodes; when the node
|
80
|
+
# metadate has :order_index arrtibute, the list will be
|
81
|
+
# ordered according the attribute value
|
82
|
+
def items
|
83
|
+
return @items if @items.empty? || order_index.empty?
|
84
|
+
[].tap do |ordered|
|
85
|
+
source = Array.new(@items)
|
86
|
+
order_index.each do |o|
|
87
|
+
e = source.delete(item(o))
|
88
|
+
ordered << e if e
|
89
|
+
end
|
90
|
+
ordered.concat(source)
|
91
|
+
end
|
92
|
+
end
|
73
93
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
94
|
+
# see Enumerable#each
|
95
|
+
def each(&block)
|
96
|
+
return to_enum(__callee__) unless block_given?
|
97
|
+
yield(self)
|
98
|
+
items.each{|n| n.each(&block) }
|
99
|
+
end
|
80
100
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
101
|
+
# @return [Node] the root node in the node hierarchy
|
102
|
+
def root
|
103
|
+
n = self
|
104
|
+
n = n.parent while n.parent
|
105
|
+
n
|
106
|
+
end
|
85
107
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
return [] if @body.empty?
|
91
|
-
@body.scan(/\[\[([\w\.]*)\]\]/).flatten.uniq
|
92
|
-
end
|
108
|
+
# @return [Integer] the node level in the node hierarchy
|
109
|
+
def nesting_level
|
110
|
+
@parent.nil? ? 0 : @parent.nesting_level + 1
|
111
|
+
end
|
93
112
|
|
94
|
-
|
95
|
-
|
96
|
-
|
113
|
+
# @return [Array<String>] macro links in the node #body
|
114
|
+
def links
|
115
|
+
return [] if @body.empty?
|
116
|
+
@body.scan(/\[\[([\w\.]*)\]\]/).flatten.uniq
|
117
|
+
end
|
97
118
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
find{|n| n.id.eql? id}
|
105
|
-
end
|
119
|
+
# When the node id starts with '.', the method will
|
120
|
+
# prefix the node id with parent id
|
121
|
+
# @return [String] the id of the node
|
122
|
+
def id
|
123
|
+
@id.start_with?('.') && @parent ? @parent.id + @id : @id
|
124
|
+
end
|
106
125
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
# @return [Boolean] true if node belongs to hierarchy of id or other node
|
119
|
-
# def belong?(node)
|
120
|
-
# end
|
121
|
-
|
122
|
-
# TODO: add tests
|
123
|
-
def orphan!
|
124
|
-
return unless @parent
|
125
|
-
@parent.delete_item(self)
|
126
|
-
@parent = nil
|
127
|
-
end
|
126
|
+
# Find the first node in the node hierarchy by its id.
|
127
|
+
# Add '*' prefix to find id by ends_with?
|
128
|
+
# @param [String] id Node id
|
129
|
+
# @return [Node] or nil when node not found
|
130
|
+
def node(id)
|
131
|
+
if id.start_with? '*'
|
132
|
+
ai = id[1..-1]
|
133
|
+
return find {|n| n.id.end_with? ai}
|
134
|
+
end
|
135
|
+
find{|n| n.id.eql? id}
|
136
|
+
end
|
128
137
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
138
|
+
# Break of the node from parent hierarhy
|
139
|
+
# @return self
|
140
|
+
def orphan!
|
141
|
+
return unless @parent
|
142
|
+
@parent.delete_item(self)
|
143
|
+
@parent = nil
|
144
|
+
self
|
145
|
+
end
|
134
146
|
|
147
|
+
protected
|
148
|
+
def parent=(node)
|
149
|
+
raise ArgumentError, "Invalid parameter :node" unless node.is_a? Node
|
150
|
+
@parent = node
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
135
155
|
end
|