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
@@ -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