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
@@ -3,7 +3,9 @@ require 'delegate'
3
3
  # Template for generate documents by Pandoc
4
4
  class MarkupNode < SimpleDelegator
5
5
  def title
6
- "#{'#' * nesting_level} #{super} {##{url(id)}}"
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(:file_name)
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
- id = 'p' + id if id[0] == '0'
41
- id.downcase
42
- .gsub(/[^A-Za-z0-9]{1,}/, '-')
43
- .gsub(/^-/, '')
44
- .gsub(/-$/, '')
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') %> using 'pandoc.md.erb' template
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/gateways'
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 gateway
21
- @gateway ||= Clerq::Gateways::InFiles.new
20
+ def settings
21
+ @settings ||= Settings.new
22
22
  end
23
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
24
+ def title
25
+ settings.title
28
26
  end
29
27
 
30
- def settings
31
- @settings ||= Settings.new
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 'interactors'
5
- include Clerq::Interactors
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: 'clerq.yml'},
36
- {tt: 'new/clerq.thor.tt', target: "#{project}.thor"},
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]), {project: project})
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
- 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)
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 CompileNodes::Failure => e
71
- error e.message
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
- 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
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
- query = options[:query]
119
- node = query ? QueryNodes.(query: query) : JoinNodes.()
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 QueryNodes::Failure, JoinNodes::Failure => e
125
- error e.message
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
@@ -1,2 +1 @@
1
1
  require_relative 'entities/node'
2
- require_relative 'entities/template'
@@ -3,133 +3,153 @@ require "forwardable"
3
3
 
4
4
  module Clerq
5
5
  module Entities
6
- end
7
- end
8
6
 
9
- class Clerq::Entities::Node
10
- extend Forwardable
11
- include Enumerable
12
-
13
- attr_reader :parent
14
- attr_reader :title
15
- attr_reader :body
16
- attr_reader :meta
17
- attr_writer :id
18
-
19
- def_delegators :@meta, :[], :[]=
20
- def_delegator :@items, :delete, :delete_item
21
- def_delegator :@items, :last, :last_item
22
- protected :delete_item
23
-
24
- def initialize(id: '', title: '', body: '', meta: {})
25
- raise ArgumentError, "Invalid argument :id" unless id.is_a? String
26
- raise ArgumentError, "Invalid argument :title" unless title.is_a? String
27
- raise ArgumentError, "Invalid argument :body" unless body.is_a? String
28
- raise ArgumentError, "Invalid argument :meta" unless meta.is_a? Hash
29
- @parent = nil
30
- @items = []
31
- @id = id
32
- @title = title
33
- @body = body
34
- @meta = meta
35
- end
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
- def <<(node)
38
- raise ArgumentError, "Invalid argument :node" unless node.is_a? Node
39
- node.parent = self
40
- @items << node
41
- node
42
- end
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
- # Find descendant node by id (for sort_order purpose)
45
- # @param [String] node id
46
- # @return [Node] when id found or nil if did not
47
- def item(id)
48
- return @items.find{|r| r.id.end_with? id[1..-1]} if id.start_with? '.'
49
- @items.find{|r| r.id.eql? id}
50
- end
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
- # @return [Array<Node>] array of items ordered by order_index meta
53
- def items
54
- return @items if @items.empty?
55
- return @items if @meta[:order_index].nil?
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
- # TODO: place valid Yard pointer to Enumerable#each
68
- def each(&block)
69
- return to_enum(__callee__) unless block_given?
70
- yield(self)
71
- items.each{|n| n.each(&block) }
72
- end
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
- # TODO: it needs for writing, maybe shold be moved to decorator?
75
- def root
76
- n = self
77
- n = n.parent while n.parent
78
- n
79
- end
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
- # TODO: it needs for writing, maybe shold be moved to decorator?
82
- def nesting_level
83
- @parent.nil? ? 0 : @parent.nesting_level + 1
84
- end
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
- # TODO: it needs for checking links and writing, maybe
87
- # shold be moved to decorator for checking also?
88
- # @return [Array<String>] links to other nodes inside @body
89
- def links
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
- def id
95
- @id.start_with?('.') && @parent ? @parent.id + @id : @id
96
- end
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
- # Find the first node in the node hierarchy by its id. It allows
99
- # finding a node by matching the end of id by providing '*' prefix,
100
- # @param [String] id Node id
101
- # @return [Node] or nil when node not found
102
- def node(id)
103
- return find{|n| n.id.end_with? id[1..-1]} if id.start_with? '*'
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
- # TODO: extract to NodeFinder class and do think about helpful prefixes
108
- # '.' for first descendant
109
- # '.*' for first descendant by #end_with?
110
- # '..' for first node at the level of node parent
111
- # '..' for first node at the level of node parent by #end_with?
112
- # def find_node(id)
113
- # end
114
-
115
- # TODO: Node#belong_to?(node[String|Node])
116
- # that returns true if node belongs to hierarhy of self included
117
- # @param [String|Node] node id or node
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
- protected
130
- def parent=(node)
131
- raise ArgumentError, "Invalid parameter :node" unless node.is_a? Node
132
- @parent = node
133
- end
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