boost_info 0.1.3 → 0.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 73d4ca44feede1ed7606e7a82341982cedefa81d
4
- data.tar.gz: 97e80dbb95417f24197e775b097e2819e6dbec48
3
+ metadata.gz: 0114ee49982c750a6dd040d3ad3cbe442016bc04
4
+ data.tar.gz: 9294cc5f854ce61386ca2fd54291ebb72fa5c0ae
5
5
  SHA512:
6
- metadata.gz: ae9647cbe4d07c4ffd648fc657035cbd6db29e2b28690f38756f4bd98b1030b4ea792e7fc6623744d8d7073befc5c788f300629edd173514313421651d05a22f
7
- data.tar.gz: 3d175935226d8bf31b7f62d27f9ac2d39b6b07f2a5e17b52250f690956c4c47a5e41ad1d1ace9c7775e110c189f820f2c49a69ed527500e4eeae9f0dd57667fd
6
+ metadata.gz: f59e7d7de56fbd7ca43ddd23e0b854bd243c27a39069ced005d7bb2565d66f050a18fbe83e297631805670c6e7a577788c87f37d2f12e1c8932b2ecfe70e4699
7
+ data.tar.gz: f87f5e05fe8e597d1fd23c26a0c5c8c5b6eec943891593fc8c097d92ba984950886ab7159da53cf15b2157da207198196e58587c8e0abddd67adee7932ec100c
data/README.md CHANGED
@@ -27,7 +27,7 @@ BoostInfo.parse(file, options={})
27
27
 
28
28
  Available options:
29
29
 
30
- - **symbolize_names**: convert all keys to symbols (default: false)
30
+ - **symbolize_keys**: convert all keys to symbols (default: false)
31
31
 
32
32
  ### Generating INFO
33
33
 
data/boost_info.gemspec CHANGED
@@ -5,6 +5,7 @@ require 'boost_info/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "boost_info"
8
+ spec.description = "Simple parser for Boost INFO format."
8
9
  spec.version = BoostInfo::VERSION
9
10
  spec.authors = ["Igor Vetrov", "Alexander Tsygankov"]
10
11
  spec.email = ["capybarov@gmail.com"]
@@ -1,43 +1,63 @@
1
- require 'boost_info/node'
2
-
3
1
  module BoostInfo
4
2
  class Generator
5
- def initialize(hash, opts={})
6
- raise TypeError unless hash.is_a?(Hash)
7
- @hash = hash
3
+ def initialize(root_node, opts={})
4
+ @root_node = root_node
8
5
  @opts = opts
9
6
  @opts[:indent] ||= 4
10
7
  end
11
8
 
12
- def generate
13
- @result = ''
14
- level = 0
15
- iterate_hash(@hash, level)
16
- @result
9
+ def to_hash
10
+ build_hash(@root_node)
11
+ end
12
+
13
+ def to_info
14
+ build_info(@root_node, 0).join("\n") + "\n"
17
15
  end
18
16
 
19
17
  private
20
18
 
21
- def iterate_hash(hash, level)
22
- hash.each do |k,v|
23
- k = k.to_s
24
- if v.is_a?(Hash)
25
- # Wrap hash in curly braces
26
- @result << add_indent("#{ k } {\n", level)
27
- iterate_hash(v, level + 1)
28
- @result << add_indent("}\n", level)
29
- else
30
- v = v.to_s
31
- # Wrap spaces and special symbols in double quotes
32
- v = %["#{ v }"] if v[/[\s\/:;.,]/]
33
- @result << add_indent("#{ k } #{ v }\n", level)
34
- end
19
+ def build_hash(parent_node)
20
+ hash = {}
21
+ parent_node.childrens.each do |node|
22
+ key = @opts[:symbolize_keys] ? node.key.to_sym : node.key
23
+ if node.childrens
24
+ hash[key] = build_hash(node)
25
+ else
26
+ hash[key] = node.value
35
27
  end
36
28
  end
29
+ hash
30
+ end
37
31
 
38
- def add_indent(string, level)
39
- indent = @opts[:indent] * level
40
- "#{ ' ' * indent if indent > 0}#{string}"
32
+ def build_info(parent_node, level)
33
+ lines = []
34
+ last_node_index = parent_node.childrens.size - 1
35
+ parent_node.childrens.each_with_index do |node,index|
36
+ if node.childrens.nil?
37
+ value = wrap_in_quotes(node.value)
38
+ lines << add_indent("#{node.key} #{value}", level)
39
+ elsif node.childrens.any?
40
+ lines << add_indent("#{node.key} {", level)
41
+ nested_lines = build_info(node, level + 1)
42
+ lines.concat(nested_lines)
43
+ elsif node.childrens.empty?
44
+ lines << add_indent("#{node.key} { }", level)
45
+ end
46
+ if level > 0 && index == last_node_index
47
+ lines << add_indent("}", level - 1)
48
+ end
41
49
  end
50
+ lines
51
+ end
52
+
53
+ def add_indent(string, level)
54
+ indent = @opts[:indent] * level
55
+ "#{' ' * indent if indent > 0}#{string}"
56
+ end
57
+
58
+ def wrap_in_quotes(value)
59
+ string = value.to_s
60
+ string =~ /[\s\/:;.,{}]/ ? '"' + string + '"' : string
61
+ end
42
62
  end
43
63
  end
@@ -2,67 +2,156 @@
2
2
 
3
3
  module BoostInfo
4
4
  class Node
5
- attr_accessor :level, :name, :value, :parent, :children
5
+ attr_accessor :root, :key, :value, :parent, :childrens
6
6
 
7
- def initialize(level, name, value=nil)
8
- # Instance initializer
9
- @level = level
10
- @name = name
11
- @value = value
12
- @parent = nil
13
- @children = []
7
+ def initialize(params={})
8
+ @root = params[:root]
9
+ @key = params[:key].to_s if params[:key] # normalize key
10
+ @value = params[:value]
11
+ @parent = params[:parent]
12
+
13
+ @childrens = nil
14
14
  end
15
15
 
16
- def to_s
17
- # String representation for node
18
- if @value
19
- "#{'| '*@level}#{@level} #{@name} = #{@value}"
16
+ def get(key_to_get, params={})
17
+ return [] unless @childrens
18
+
19
+ if key_to_get.is_a?(Regexp)
20
+ @childrens.select { |c| c.key =~ key_to_get }
20
21
  else
21
- "#{'| '*@level}#{@level} #{@name}"
22
+ @childrens.select { |c| c.key == key_to_get.to_s }
22
23
  end
23
24
  end
24
25
 
25
- def to_h(symbolize=false)
26
- key = symbolize ? @name.to_sym : @name
27
- if has_children?
28
- result = {}
29
- @children.each { |c| result.merge!(c.to_h(symbolize)) }
30
- { key => result }
31
- else
32
- { key => @value }
26
+ def find_by_path(path, params={})
27
+ if path.size > 1
28
+ next_node_key = path.shift
29
+ node = get(next_node_key).first
30
+ node.find_by_path(path) if node
31
+ elsif path.size == 1
32
+ last_key = path.last.to_s
33
+ get(last_key).first
33
34
  end
34
35
  end
35
36
 
36
- def add_child(child)
37
- # Add child node and assign parent to child node
38
- child.parent = self
39
- @children << child
37
+ def find_by_key(key, params={})
38
+ result = []
39
+ childrens.each do |node|
40
+ if key.is_a?(Regexp)
41
+ result << node if node.key =~ key
42
+ else
43
+ result << node if node.key == key.to_s
44
+ end
45
+
46
+ if node.childrens
47
+ result_from_children = node.find_by_key(key, params)
48
+ result.concat(result_from_children)
49
+ end
50
+ end
51
+ result
40
52
  end
41
53
 
42
- def has_children?
43
- # Check children existence
44
- @children.size > 0
54
+ def parents
55
+ result = []
56
+ unless root
57
+ result << parent
58
+ result.concat(parent.parents)
59
+ end
60
+ result
45
61
  end
46
62
 
47
- def has_parent?
48
- # Check parent existence
49
- @parent != nil
63
+ def siblings
64
+ parent_childrens = (parent && parent.childrens) || []
65
+ parent_childrens.reject { |c| c == self }
50
66
  end
51
67
 
52
- def find(name)
53
- # Recursive node search by name
54
- return self if @name == name
55
- result_node = nil
56
- @children.each do |node|
57
- if node.name==name then result_node = node; break end
58
- if node.has_children?
59
- found_node = node.find(name)
60
- if found_node
61
- if found_node.name==name then result_node = found_node; break end
62
- end
63
- end
68
+ def auto_insert(path, value, params={})
69
+ params[:force] ||= true
70
+ if params[:force]
71
+ params[:delete_if] = ->(node) { node.childrens.nil? }
72
+ end
73
+
74
+ current_node = self
75
+ path.each do |new_key|
76
+ existing_node = current_node.get(new_key).first
77
+ current_node = if existing_node && existing_node.childrens
78
+ existing_node
79
+ else
80
+ current_node.insert(new_key, value, params)
81
+ end
82
+ end
83
+ current_node
84
+ end
85
+
86
+ def insert(key, value, params={})
87
+ node = Node.new(key: key, value: value)
88
+ insert_node(node, params)
89
+ node
90
+ end
91
+
92
+ def insert_node(node, params={})
93
+ node.parent = self
94
+ @childrens ||= []
95
+ @childrens.delete_if(&params[:delete_if]) if params[:delete_if]
96
+
97
+ if params[:after]
98
+ insert_node_after(params[:after], node)
99
+ elsif params[:before]
100
+ insert_node_before(params[:before], node)
101
+ elsif params[:prepend]
102
+ @childrens.unshift(node)
103
+ else
104
+ @childrens << node
64
105
  end
65
- result_node
106
+ node
107
+ end
108
+
109
+ def insert_node_after(key, node)
110
+ index = @childrens.index { |c| c.key == key.to_s }
111
+ index += 1 if index
112
+ index = @childrens.size if index > @childrens.size || index.nil?
113
+ @childrens.insert(index, node)
114
+ node
115
+ end
116
+
117
+ def insert_node_before(key, node)
118
+ index = @childrens.index { |c| c.key == key.to_s }
119
+ index ||= 0
120
+ @childrens.insert(index, node)
121
+ node
122
+ end
123
+
124
+ def delete(key)
125
+ node = find_children(key.to_s)
126
+ return unless node
127
+
128
+ delete_children(node)
129
+ end
130
+
131
+ def find_children(key_to_find)
132
+ return unless @childrens
133
+ key_to_find = key_to_find.to_s
134
+ @childrens.find { |children| children.key == key_to_find }
135
+ end
136
+
137
+ def delete_children(node)
138
+ return unless @childrens
139
+
140
+ node.parent = nil
141
+
142
+ index = @childrens.index(node)
143
+ @childrens.delete_at(index)
144
+ @childrens = nil if @childrens.empty?
145
+
146
+ node
147
+ end
148
+
149
+ def to_h(params={})
150
+ BoostInfo::Generator.new(self, params).to_hash
151
+ end
152
+
153
+ def to_info(params={})
154
+ BoostInfo::Generator.new(self, params).to_info
66
155
  end
67
156
  end
68
157
  end
@@ -1,138 +1,130 @@
1
1
  require 'boost_info/node'
2
2
 
3
3
  module BoostInfo
4
- class ParseError < StandardError; end
5
-
6
4
  class Parser
7
- attr_accessor :nodes
5
+ class ParseError < StandardError; end
8
6
 
9
- # Boost info tokens
10
- @@tokens = '{}'
11
- @@open_token = '{'
12
- @@close_token = '}'
7
+ attr_accessor :root_node
13
8
 
14
- def initialize(source, opts={})
15
- unless source.is_a?(String)
16
- raise TypeError, "no implicit conversion of #{source.class} into String"
17
- end
9
+ OPEN_TOKEN = '{'
10
+ CLOSE_TOKEN ='}'
18
11
 
19
- # Initialize instance variables
20
- @nodes = []
21
- @level = 0
12
+ def initialize(source, opts={})
22
13
  @source = source
23
- @tokens = []
24
14
  @opts = opts
25
15
  @opts[:symbolize_keys] ||= false
26
-
27
- # without empty lines and comments
28
- tokenize! { |token| @tokens << token }
16
+ @root_node = Node.new(root: true)
29
17
  end
30
18
 
31
- def parse
32
- # Parse source
33
- opens = @source.count(@@open_token)
34
- closes = @source.count(@@close_token)
35
- if opens != closes
36
- raise BoostInfo::ParseError, "open [#{opens}] and close tokens [#{closes}] does not match..."
37
- end
38
- nodes = {}
39
- @tokens.each do |token|
40
- if @@open_token == token
41
- @level += 1
42
- elsif @@close_token == token
43
- @level -= 1
44
- else
45
- list = token.split
46
- if list.size > 2
47
- val = list[1..-1].join(" ")
48
- else
49
- val = list[1]
50
- end
51
- val = val.gsub(/\A["]+|["]+\z/, "") if val
52
- node = Node.new(@level, list[0], val)
53
- nodes[@level] = node
54
- if @level == 0
55
- @nodes << node
56
- else
57
- nodes[@level] = node unless nodes.has_key?(@level)
58
- nodes[@level-1].add_child(node)
59
- end
60
- end
19
+ def self.from_info(source, opts={})
20
+ unless source.is_a?(String)
21
+ fail TypeError, "no implicit conversion of #{source.class} into String"
61
22
  end
62
- self
23
+ new(source, opts).parse_info
63
24
  end
64
25
 
65
- def find(name)
66
- # Recursive node search by name
67
- result_node = nil
68
- @nodes.each do |node|
69
- result_node = node.find(name)
70
- break if result_node
26
+ def self.from_hash(source, opts={})
27
+ unless source.is_a?(Hash)
28
+ fail TypeError, "no implicit conversion of #{source.class} into Hash"
71
29
  end
72
- result_node
30
+ new(source, opts).parse_hash
73
31
  end
74
32
 
75
- def [](offset)
76
- # Get elements by key
77
- find(offset)
78
- end
33
+ def parse_info
34
+ lines = process_source(@source)
35
+ traverse_info(@root_node, lines, -1)
79
36
 
80
- def print_node(node)
81
- # Print node with children
82
- puts node
83
- node.children.each { |child| print_node(child) }
84
- puts "#{'| '*node.level}#{node.level} #{node.name} close" if node.has_children?
37
+ @root_node
85
38
  end
86
39
 
87
- def print_tree
88
- # Print parsed tree
89
- @nodes.each { |node| print_node(node) }
40
+ def parse_hash
41
+ traverse_hash(@source, @root_node)
42
+
43
+ @root_node
90
44
  end
91
45
 
92
- def child_source(node)
93
- result = ' '*node.level + node.name + (node.value ? " #{ node.value }" : '')
94
- if node.has_children?
95
- result += " {\n"
96
- node.children.each { |child| result += self.child_source(child) }
97
- result += ' '*node.level + "}\n"
98
- else
99
- result += "\n"
46
+ private
47
+
48
+ def process_source(source)
49
+ lines = []
50
+ source.each_line do |line|
51
+ new_line = line.sub(/;.+[^"']$/, '').strip # remove comments
52
+ next if new_line.empty?
53
+ # include file
54
+ if new_line.match(/#include\s+(\S+)/)
55
+ file_path = strip_quotes(Regexp.last_match(1))
56
+ file = File.open(file_path)
57
+ lines_to_include = process_source(file)
58
+ lines.concat(lines_to_include)
59
+ # process inline section
60
+ elsif new_line.match(/#{OPEN_TOKEN}.*#{CLOSE_TOKEN}/)
61
+ splitted_lines = line.sub('{', "{\n").sub('}', "\n}").lines
62
+ .map(&:strip)
63
+ .reject(&:empty?)
64
+ lines.concat(splitted_lines)
65
+ #just add the line
66
+ else
67
+ lines << new_line
68
+ end
100
69
  end
101
- result
102
- end
103
70
 
104
- def to_s
105
- self.to_h.to_info
71
+ open_tokens = lines.count { |line| line.end_with?(OPEN_TOKEN) }
72
+ close_tokens = lines.count { |line| line.end_with?(CLOSE_TOKEN) }
73
+ if open_tokens != close_tokens
74
+ fail ParseError, "open [#{open_tokens}] and close tokens [#{close_tokens}] does not match..."
75
+ end
76
+ lines
106
77
  end
107
78
 
108
- def to_h
109
- result = {}
110
- @nodes.each { |n| result.merge!(n.to_h(@opts[:symbolize_keys])) }
111
- result
112
- end
79
+ def traverse_info(parent_node, lines, index_start)
80
+ index_end = lines.size - 1
81
+ lines.each_with_index do |line,index|
82
+ next if index <= index_start
113
83
 
114
- private
84
+ index_end = index
115
85
 
116
- def tokenize!
117
- # tokenize boost info format
118
- lines = []
119
- @source.split("\n").each do |line|
120
- lines << line.split(';')[0].strip unless line.strip.empty? || line.strip[0] == ';'
86
+ key, value = extract_key_and_value(line)
87
+ node = Node.new(key: key, value: value)
88
+ parent_node.insert_node(node) if key
89
+
90
+ case line
91
+ when /#{OPEN_TOKEN}/
92
+ index_start = traverse_info(node, lines, index)
93
+ when /#{CLOSE_TOKEN}/
94
+ parent_node.childrens ||= []
95
+ break
121
96
  end
122
- lines.each do |line|
123
- token = ''
124
- line.each_char do |char|
125
- if '{}'.include?(char)
126
- token = token.strip
127
- yield token unless token.empty?
128
- yield char
129
- token = ''
130
- else
131
- token += char
132
- end
133
- end
134
- yield token.strip unless token.empty?
97
+ end
98
+ index_end
99
+ end
100
+
101
+ def traverse_hash(hash, parent_node)
102
+ hash.each do |k,v|
103
+ node = parent_node.insert(k, v)
104
+ if v.is_a?(Hash)
105
+ node.childrens = []
106
+ traverse_hash(v, node)
135
107
  end
136
108
  end
109
+ parent_node
110
+ end
111
+
112
+ def extract_key_and_value(string)
113
+ key = nil
114
+ value = nil
115
+
116
+ tokens = string.split(/\s+/)
117
+ raw_key = tokens.shift.sub('}', '').strip
118
+ key = raw_key unless raw_key.empty?
119
+
120
+ tokens.pop if tokens.last == OPEN_TOKEN
121
+ value = strip_quotes(tokens.join(' ')) unless tokens.empty?
122
+ value = value.to_i if value.to_s =~ /^\d+$/
123
+ [key, value]
124
+ end
125
+
126
+ def strip_quotes(string)
127
+ string.gsub(/^["']+|["']+$/, '')
128
+ end
137
129
  end
138
130
  end
@@ -1,3 +1,3 @@
1
1
  module BoostInfo
2
- VERSION = '0.1.3'
2
+ VERSION = '0.2.0'
3
3
  end
data/lib/boost_info.rb CHANGED
@@ -5,16 +5,22 @@ require 'boost_info/generator'
5
5
 
6
6
  module BoostInfo
7
7
  def self.parse(source, opts={})
8
- BoostInfo::Parser.new(source, opts).parse.to_h
8
+ root_node = BoostInfo::Parser.from_info(source, opts)
9
+ root_node.to_h(opts)
9
10
  end
10
11
 
11
- def self.generate(hash, opts={})
12
- BoostInfo::Generator.new(hash, opts).generate
12
+ def self.from_hash(hash, opts={})
13
+ BoostInfo::Parser.from_hash(hash, opts)
14
+ end
15
+
16
+ def self.from_info(source, opts={})
17
+ BoostInfo::Parser.from_info(source, opts)
13
18
  end
14
19
  end
15
20
 
16
21
  class Hash
17
22
  def to_info(opts={})
18
- BoostInfo.generate(self, opts)
23
+ root_node = BoostInfo::Parser.from_hash(self, opts)
24
+ root_node.to_info(opts)
19
25
  end
20
26
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: boost_info
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Igor Vetrov
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-05-14 00:00:00.000000000 Z
12
+ date: 2015-07-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -53,7 +53,7 @@ dependencies:
53
53
  - - "~>"
54
54
  - !ruby/object:Gem::Version
55
55
  version: '5.6'
56
- description:
56
+ description: Simple parser for Boost INFO format.
57
57
  email:
58
58
  - capybarov@gmail.com
59
59
  executables: []
@@ -95,8 +95,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
95
  version: '0'
96
96
  requirements: []
97
97
  rubyforge_project:
98
- rubygems_version: 2.4.5
98
+ rubygems_version: 2.4.6
99
99
  signing_key:
100
100
  specification_version: 4
101
101
  summary: Simple parser for Boost INFO format
102
102
  test_files: []
103
+ has_rdoc: