amatsuda-i18n_generators 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -63,7 +63,10 @@ class I18nGenerator < Rails::Generator::NamedBase
63
63
  private
64
64
  def defined_in_rails_i18n_repository?
65
65
  begin
66
- OpenURI.open_uri("http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale/#{locale_name}.yml").status == %w[200 OK]
66
+ uri = "http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale/#{locale_name}.yml"
67
+ OpenURI.open_uri(uri) do |res|
68
+ (res.base_uri.to_s == uri) && (res.status == %w[200 OK])
69
+ end
67
70
  rescue
68
71
  false
69
72
  end
@@ -0,0 +1,185 @@
1
+ require 'yaml'
2
+
3
+ module I18nLocaleGeneratorModule
4
+ class Node
5
+ attr_reader :document, :indent_level
6
+ attr_accessor :line
7
+
8
+ def initialize(parent, line_index, text)
9
+ @document, @line, @text = parent.document, line_index, text.to_s
10
+ @text =~ /(^\s*)/
11
+ @indent_level = $1.nil? ? 0 : $1.size
12
+ @yaml = YAML.load(@text.to_s + ' ')
13
+ end
14
+
15
+ def parent
16
+ @parent ||= document.parent_of self
17
+ end
18
+
19
+ def children
20
+ @children ||= document.children_of(self)
21
+ end
22
+ alias :nodes :children
23
+
24
+ def [](node_name)
25
+ if node = nodes.detect {|n| n.key.to_s == node_name.to_s}
26
+ node
27
+ else
28
+ nodes.add "#{' ' * (@indent_level + 2)}#{node_name}: "
29
+ nodes.last
30
+ end
31
+ end
32
+
33
+ def key
34
+ @yaml.is_a?(Hash) ? @yaml.keys.first : nil
35
+ end
36
+
37
+ def value
38
+ @yaml.is_a?(Hash) ? @yaml.values.first : nil
39
+ end
40
+
41
+ def value=(val)
42
+ if @yaml[self.key] != val
43
+ @yaml[self.key] = val
44
+ @changed = true
45
+ end
46
+ end
47
+
48
+ def text
49
+ if @changed
50
+ v = if self.value.is_a?(Array)
51
+ "[#{self.value * ', '}]"
52
+ else
53
+ %Q["#{self.value}"]
54
+ end
55
+ "#{' ' * self.indent_level}#{self.key}: #{v}"
56
+ else
57
+ @text
58
+ end
59
+ end
60
+ alias :to_s :text
61
+
62
+ def changed?
63
+ @changed
64
+ end
65
+
66
+ def is_blank_or_comment?
67
+ @text.sub(/#.*$/, '').gsub(/\s/, '').empty?
68
+ end
69
+
70
+ def path
71
+ @path ||= "#{self.parent.path}/#{self.key}"
72
+ end
73
+
74
+ def descendant_nodes(&block)
75
+ yield self if self.value
76
+ self.children.each {|child| child.descendant_nodes(&block)} if self.children
77
+ end
78
+
79
+ def <=>(other)
80
+ self.line <=> other.line
81
+ end
82
+ end
83
+
84
+ class YamlDocument < Node
85
+ attr_accessor :lines
86
+ alias :nodes :lines
87
+
88
+ def initialize(yml_path, locale_name)
89
+ @locale_name, @lines, @current_line, @indent_level = locale_name, Nodes.new(self), -1, -2
90
+ if File.exists? yml_path
91
+ File.open(yml_path) do |file|
92
+ file.each_with_index do |line_text, i|
93
+ n = Node.new(self, i, line_text.chomp)
94
+ @lines << ((((n.key == 'en-US') || (n.key == 'en')) && n.value.blank?) ? Node.new(self, i, "#{locale_name}:") : n)
95
+ end
96
+ @lines.delete_at(-1) if @lines[-1].text.blank?
97
+ end
98
+ end
99
+ end
100
+
101
+ def next
102
+ return false if @lines.size == 0
103
+ @current_line += 1
104
+ return false if @current_line >= @lines.size
105
+ @lines[@current_line].is_blank_or_comment? ? self.next : @lines[@current_line]
106
+ end
107
+
108
+ def prev
109
+ return false if @current_line == 0
110
+ @current_line -= 1
111
+ @lines[@current_line].is_blank_or_comment? ? self.prev : @lines[@current_line]
112
+ end
113
+
114
+ def parent_of(child)
115
+ @current_line = child.line
116
+ while n = self.prev
117
+ return n if n.indent_level == child.indent_level - 2
118
+ end
119
+ self
120
+ end
121
+
122
+ def children_of(parent)
123
+ nodes = Nodes.new(parent)
124
+ @current_line = parent.line
125
+ while n = self.next
126
+ if n.indent_level < parent.indent_level + 2
127
+ break
128
+ elsif n.indent_level == parent.indent_level + 2
129
+ nodes << n
130
+ end
131
+ end
132
+ nodes
133
+ end
134
+
135
+ def document
136
+ self
137
+ end
138
+
139
+ def path
140
+ ''
141
+ end
142
+
143
+ def line
144
+ @current_line
145
+ end
146
+
147
+ def to_s
148
+ @lines.inject('') do |ret, n|
149
+ ret << n.text + "\n"
150
+ end
151
+ end
152
+ end
153
+
154
+ class Nodes < Array
155
+ def initialize(parent)
156
+ super()
157
+ @parent = parent
158
+ end
159
+
160
+ def [](index)
161
+ if index.is_a?(String) || index.is_a?(Symbol)
162
+ return self.detect {|node| node.key == index} || add(index)
163
+ end
164
+ super
165
+ end
166
+
167
+ def last_leaf
168
+ c = @parent
169
+ loop do
170
+ return c if c.children.blank?
171
+ c = c.children.last
172
+ end
173
+ end
174
+
175
+ def add(node_name)
176
+ target_line = self.last_leaf.line + 1
177
+ @parent.document.nodes.each {|n| n.line += 1 if n.line >= target_line}
178
+ node = Node.new(@parent, target_line, node_name)
179
+ @parent.document.lines << node
180
+ @parent.document.lines.sort!
181
+ self << node unless @parent.is_a? YamlDocument
182
+ end
183
+ end
184
+ end
185
+
@@ -1,19 +1 @@
1
- <%= locale_name %>:
2
- activerecord:
3
- models:
4
- <% models.each do |model| -%>
5
- <%= "#{model.english_name}: #{model.translated_name}" %>
6
- <% end -%>
7
- attributes:
8
- <% models.each do |model| -%>
9
- <%= "#{model.english_name}:" %>
10
- <% model.content_columns.each do |col| -%>
11
- <% unless %w[created_at updated_at].include?(col.name) -%>
12
- <%= "#{col.name}: #{col.translated_name}" %>
13
- <% end -%>
14
- <% end -%>
15
- <% end -%>
16
- <% translations.each do |key, value| -%>
17
- <%= "#{key}: #{value}" %>
18
- <% end -%>
19
-
1
+ <%= translations %>
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
  require 'rails_generator'
3
3
  require 'rails_generator/commands'
4
4
  require 'gettext'
5
- require File.join(File.dirname(__FILE__), 'lib/yaml')
5
+ require File.join(File.dirname(__FILE__), '../i18n/lib/yaml')
6
6
  require File.join(File.dirname(__FILE__), 'lib/cldr')
7
7
  include I18nLocaleGeneratorModule
8
8
 
@@ -95,8 +95,13 @@ module I18nGenerator::Generator
95
95
  return arr.join("\n")
96
96
  end
97
97
  end
98
- # hope you're all using Ruby >= 1.8.7...
99
- end_row = arr.respond_to?(:rindex) ? arr.rindex {|l| l =~ /^\s*end\s*/} : arr.size - 1
98
+ arr.each_with_index do |l, i|
99
+ if l =~ /Rails::Initializer\.run do \|config\|/
100
+ arr[i] = "Rails::Initializer.run do |config|\n config.i18n.default_locale = '#{locale_name}'"
101
+ return arr.join("\n")
102
+ end
103
+ end
104
+ end_row = RUBY_VERSION >= '1.8.7' ? arr.rindex {|l| l =~ /^\s*end\s*/} : arr.size - 1
100
105
  ((arr[0...end_row] << " config.i18n.default_locale = '#{locale_name}'") + arr[end_row..-1]).join("\n")
101
106
  end
102
107
 
@@ -104,7 +109,7 @@ module I18nGenerator::Generator
104
109
  original_yml = I18n.load_path.detect {|lp| lp =~ /\/lib\/#{filename_base}\/locale\/(en|en-US)\.yml$/}
105
110
  doc = YamlDocument.new(original_yml, locale_name)
106
111
  yield doc
107
- file('i18n:base.yml', "config/locale/#{filename_base}_#{locale_name}.yml") do |f|
112
+ file('i18n:base.yml', "config/locales/#{filename_base}_#{locale_name}.yml") do |f|
108
113
  doc.to_s
109
114
  end
110
115
  end
@@ -6,7 +6,10 @@ module I18nLocaleGeneratorModule
6
6
  class CldrDocument
7
7
  def initialize(locale_name)
8
8
  @locale_name = locale_name
9
- @summaries = [load_cldr_data(locale_name.tr('-', '_')), load_cldr_data(locale_name.sub(/-.*/, ''))]
9
+ @summaries = [load_cldr_data(locale_name.tr('-', '_'))]
10
+ if locale_name =~ /^[a-zA-Z]{2}[-_][a-zA-Z]{2}$/
11
+ @summaries << load_cldr_data(locale_name.to(1))
12
+ end
10
13
  end
11
14
 
12
15
  def lookup(path)
@@ -108,10 +111,11 @@ module I18nLocaleGeneratorModule
108
111
  def search(n1, n2, g)
109
112
  pattern = Regexp.new /<tr><td>\d*<\/td><td class='[ng]'>#{Regexp.quote(n1)}<\/td><td class='[ng]'>#{Regexp.quote(n2)}<\/td><td class='[ng]'>#{Regexp.quote(g)}<\/td>/
110
113
  extract_pattern = /<td class='v'>(?:<span.*?>)?(.*?)(?:<\/span>)?<\/td><td>/
111
- _, value = *extract_pattern.match(@summaries[0].grep(pattern).first)
112
- return value unless value.nil?
113
- _, value = *extract_pattern.match(@summaries[1].grep(pattern).first)
114
- value
114
+ @summaries.each do |summary|
115
+ _, value = *extract_pattern.match(summary.grep(pattern).first)
116
+ return value unless value.nil?
117
+ end
118
+ nil
115
119
  end
116
120
 
117
121
  def convert_date_pattern(val)
@@ -3,6 +3,7 @@ require 'rails_generator/commands'
3
3
  require File.join(File.dirname(__FILE__), 'lib/translator')
4
4
  require File.join(File.dirname(__FILE__), 'lib/recording_backend')
5
5
  require File.join(File.dirname(__FILE__), 'lib/erb_executer')
6
+ require File.join(File.dirname(__FILE__), '../i18n/lib/yaml')
6
7
  include I18nTranslationGeneratorModule
7
8
 
8
9
  module I18nGenerator::Generator
@@ -10,56 +11,41 @@ module I18nGenerator::Generator
10
11
  module Create
11
12
  def translation_yaml
12
13
  I18n.locale = locale_name
13
- threads = []
14
- now = Time.now
15
14
  models = model_filenames.map do |model_name|
16
15
  model = begin
17
16
  m = model_name.camelize.constantize
18
17
  next unless m.respond_to?(:content_columns)
18
+ m.class_eval %Q[def self.english_name; "#{model_name}"; end]
19
19
  m
20
20
  rescue
21
21
  next
22
22
  end
23
- threads << Thread.new do
24
- Thread.pass
25
- registered_t_name = I18n.t("activerecord.models.#{model_name}", :default => model_name, :locale => locale_name)
26
-
27
- model.class_eval <<-END
28
- def self.english_name
29
- "#{model_name}"
30
- end
31
-
32
- def self.translated_name
33
- "#{registered_t_name != model_name ? registered_t_name : self.translator.translate(model_name)}"
34
- end
35
- END
36
- model.content_columns.each do |col|
37
- next if %w[created_at updated_at].include? col.name
38
- registered_t_name = I18n.t("activerecord.attributes.#{model_name}.#{col.name}", :default => col.name, :locale => locale_name)
39
- col.class_eval <<-END
40
- def translated_name
41
- "#{registered_t_name != col.name ? registered_t_name : self.translator.translate(col.name)}"
42
- end
43
- END
44
- end
45
- end
46
- model
47
23
  end.compact
48
- threads.each {|t| t.join}
49
- logger.debug "took #{Time.now - now} secs to translate #{models.count} models."
24
+ translation_keys = []
25
+ translation_keys += models.map {|m| "activerecord.models.#{m.english_name}"}
26
+ models.each do |model|
27
+ translation_keys += model.content_columns.map {|c| "activerecord.attributes.#{model.english_name}.#{c.name}"}
28
+ end
29
+ logger.debug "#{models.size} models found."
30
+
50
31
  # pick all translated keywords from view files
32
+ original_backend = I18n.backend
51
33
  I18n.backend = RecordingBackend.new
52
34
 
53
35
  Dir["#{RAILS_ROOT}/app/views/**/*.erb"].each do |f|
54
36
  ErbExecuter.new.exec_erb f
55
37
  end
56
- keys_in_view = I18n.backend.keys
57
- keys_in_view -= models.map {|m| m.english_name.to_sym}
58
- keys_in_view -= models.inject([]) {|a, m| a + m.content_columns.map {|c| "#{m.english_name}.#{c.name}".to_sym}}
38
+ logger.debug "#{I18n.backend.keys.size} translation keys found in views."
39
+ (translation_keys += I18n.backend.keys).uniq!
40
+ I18n.backend = original_backend
41
+
42
+ # translate all keys and generate the YAML file
59
43
  now = Time.now
60
- translated_hash = translate_all keys_in_view
61
- logger.debug "took #{Time.now - now} secs to translate #{keys_in_view.count} words."
62
- generate_yaml(locale_name, models, translated_hash)
44
+ translations = translate_all(translation_keys)
45
+ logger.debug "took #{Time.now - now} secs to translate."
46
+
47
+ yaml = generate_yaml(locale_name, translations)
48
+ template 'i18n:translation.yml', "config/locales/translation_#{locale_name}.yml", :assigns => {:locale_name => locale_name, :translations => yaml.to_s}
63
49
  end
64
50
 
65
51
  private
@@ -69,22 +55,56 @@ module I18nGenerator::Generator
69
55
  end
70
56
  end
71
57
 
72
- def generate_yaml(locale_name, models, translations)
73
- template 'i18n:translation.yml', "config/locales/translation_#{locale_name}.yml", :assigns => {:locale_name => locale_name, :models => models, :translations => translations}
58
+ # mixin translations into existing yaml file
59
+ def generate_yaml(locale_name, translations)
60
+ yaml = YamlDocument.new("config/locales/translation_#{locale_name}.yml", locale_name)
61
+ each_value [], translations do |parents, value|
62
+ node = parents.inject(yaml[locale_name]) {|node, parent| node[parent]}
63
+ node.value = value
64
+ end
65
+ yaml
74
66
  end
75
67
 
76
68
  # receives an array of keys and returns :key => :translation hash
77
69
  def translate_all(keys)
78
- threads = []
79
- ret = keys.inject({}) do |hash, key|
80
- threads << Thread.new do
81
- Thread.pass
82
- hash[key] = translator.translate key
70
+ returning ActiveSupport::OrderedHash.new do |oh|
71
+ # fix the order first(for multi thread translating)
72
+ keys.each do |key|
73
+ if key.to_s.include? '.'
74
+ key_prefix, key_suffix = key.to_s.split('.')[0...-1], key.to_s.split('.')[-1]
75
+ key_prefix.inject(oh) {|h, k| h[k] ||= ActiveSupport::OrderedHash.new}[key_suffix] = nil
76
+ else
77
+ oh[key] = nil
78
+ end
79
+ end
80
+ threads = []
81
+ keys.each do |key|
82
+ threads << Thread.new do
83
+ logger.debug "translating #{key} ..."
84
+ Thread.pass
85
+ if key.to_s.include? '.'
86
+ key_prefix, key_suffix = key.to_s.split('.')[0...-1], key.to_s.split('.')[-1]
87
+ existing_translation = I18n.t(key, :default => key_suffix, :locale => locale_name)
88
+ key_prefix.inject(oh) {|h, k| h[k]}[key_suffix] = existing_translation != key_suffix ? existing_translation : translator.translate(key_suffix)
89
+ else
90
+ existing_translation = I18n.t(key, :default => key, :locale => locale_name)
91
+ oh[key] = existing_translation != key ? existing_translation : translator.translate(key)
92
+ end
93
+ end
94
+ end
95
+ threads.each {|t| t.join}
96
+ end
97
+ end
98
+
99
+ # iterate through all values
100
+ def each_value(parents, src, &block)
101
+ src.each do |k, v|
102
+ if v.is_a?(ActiveSupport::OrderedHash)
103
+ each_value parents + [k], v, &block
104
+ else
105
+ yield parents + [k], v
83
106
  end
84
- hash
85
107
  end
86
- threads.each {|t| t.join}
87
- ret
88
108
  end
89
109
  end
90
110
  end
@@ -9,10 +9,12 @@ module I18nTranslationGeneratorModule
9
9
  class Executer
10
10
  extend ERB::DefMethod
11
11
  include ActionView::Helpers::TranslationHelper
12
- include ThroughRyoku
12
+ include I18nTranslationGeneratorModule::ThroughRyoku
13
+
13
14
  nil.class_eval do
14
15
  def method_missing(method, *args, &block); nil; end
15
16
  end
17
+
16
18
  def_erb_method 'execute', '#{filename}'
17
19
  end
18
20
  EOS
@@ -7,7 +7,8 @@ module I18nTranslationGeneratorModule
7
7
  end
8
8
 
9
9
  def translate(locale, key, options = {})
10
- @keys << key.to_sym
10
+ # @keys << key.to_sym
11
+ @keys << (Array(options[:scope]) + [key]).map.flatten.join('.')
11
12
  end
12
13
  alias :t :translate
13
14
  end
@@ -1,6 +1,8 @@
1
- module ThroughRyoku
2
- def method_missing(method, *args, &block)
3
- nil
1
+ module I18nTranslationGeneratorModule
2
+ module ThroughRyoku
3
+ def method_missing(method, *args, &block)
4
+ nil
5
+ end
4
6
  end
5
7
  end
6
8
 
@@ -51,9 +51,9 @@ end"
51
51
  it 'adds the default_locale config inside the config block and sets locale_name value' do
52
52
  @command.send(:add_locale_config, @config).should == "
53
53
  Rails::Initializer.run do |config|
54
+ config.i18n.default_locale = 'ja'
54
55
  something goes here.
55
56
  bla bla bla...
56
- config.i18n.default_locale = 'ja'
57
57
  end"
58
58
  end
59
59
  end
@@ -0,0 +1,23 @@
1
+ require File.join(File.dirname(__FILE__), '/../generators/i18n_translation/i18n_translation_command')
2
+
3
+ describe I18nGenerator::Generator::Commands::Create do
4
+ before do
5
+ (@command = Object.new).extend I18nGenerator::Generator::Commands::Create
6
+ @command.stub!(:locale_name).and_return('ja')
7
+ end
8
+
9
+ describe 'each_value' do
10
+ it 'iterates through each value' do
11
+ hash = ActiveSupport::OrderedHash.new
12
+ hash[:parent1] = ActiveSupport::OrderedHash.new
13
+ hash[:parent1][:child1] = 'child one'
14
+ hash[:parent2] = ActiveSupport::OrderedHash.new
15
+ hash[:parent2][:child2] = 'child two'
16
+ hash[:parent2][:child3] = 'child three'
17
+ @command.__send__(:each_value, [], hash) do |parents, value|
18
+ p "#{parents.join('.')} #{value}"
19
+ end
20
+ end
21
+ end
22
+ end
23
+
data/spec/yaml_spec.rb CHANGED
@@ -1,20 +1,20 @@
1
- require File.join(File.dirname(__FILE__), '/../generators/i18n_locale/lib/yaml')
1
+ require File.join(File.dirname(__FILE__), '/../generators/i18n/lib/yaml')
2
2
  include I18nLocaleGeneratorModule
3
3
 
4
4
  describe 'Yaml' do
5
- before do
6
- @yaml = YamlDocument.new File.join(File.dirname(__FILE__), 'data/yml/active_record/en-US.yml'), 'ja-JP'
5
+ before :each do
6
+ @yaml = YamlDocument.new File.join(File.dirname(__FILE__), 'data/yml/active_record/en-US.yml'), 'ja'
7
7
  end
8
8
 
9
9
  describe YamlDocument do
10
10
  it 'should return the top level node with the square bracket method' do
11
- node = @yaml['ja-JP']
11
+ node = @yaml['ja']
12
12
  node.should be_an_instance_of(Node)
13
- node.key.should == 'ja-JP'
13
+ node.key.should == 'ja'
14
14
  end
15
15
 
16
16
  it 'should generate a path string on the top node' do
17
- @yaml['ja-JP'].path.should == '/ja-JP'
17
+ @yaml['ja'].path.should == '/ja'
18
18
  end
19
19
  end
20
20
 
@@ -32,7 +32,7 @@ describe 'Yaml' do
32
32
  end
33
33
 
34
34
  it 'should generate a path string on any node' do
35
- @yaml['ja-JP']['activerecord']['errors']['messages'].path.should == '/ja-JP/activerecord/errors/messages'
35
+ @yaml['ja']['activerecord']['errors']['messages'].path.should == '/ja-JP/activerecord/errors/messages'
36
36
  end
37
37
  end
38
38
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: amatsuda-i18n_generators
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Akira Matsuda
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-12-08 00:00:00 -08:00
12
+ date: 2008-12-17 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -35,6 +35,7 @@ files:
35
35
  - Rakefile
36
36
  - generators/i18n/USAGE
37
37
  - generators/i18n/i18n_generator.rb
38
+ - generators/i18n/lib/yaml.rb
38
39
  - generators/i18n/templates/base.yml
39
40
  - generators/i18n/templates/i18n_config.rb
40
41
  - generators/i18n/templates/translation.yml
@@ -42,7 +43,6 @@ files:
42
43
  - generators/i18n_locale/i18n_locale_command.rb
43
44
  - generators/i18n_locale/i18n_locale_generator.rb
44
45
  - generators/i18n_locale/lib/cldr.rb
45
- - generators/i18n_locale/lib/yaml.rb
46
46
  - generators/i18n_scaffold/i18n_scaffold_generator.rb
47
47
  - generators/i18n_scaffold/templates/controller.rb
48
48
  - generators/i18n_scaffold/templates/functional_test.rb
@@ -65,6 +65,7 @@ files:
65
65
  - spec/data/cldr/ja.html
66
66
  - spec/data/yml/active_record/en-US.yml
67
67
  - spec/i18n_locale_command_spec.rb
68
+ - spec/i18n_translation_command_spec.rb
68
69
  - spec/translator_spec.rb
69
70
  - spec/yaml_spec.rb
70
71
  has_rdoc: false
@@ -1,141 +0,0 @@
1
- require 'yaml'
2
-
3
- module I18nLocaleGeneratorModule
4
- class YamlDocument
5
- def initialize(yml_path, locale_name)
6
- @locale_name, @nodes = locale_name, []
7
- File.read(yml_path).each_with_index do |line_text, i|
8
- n = Node.new(self, i, line_text.chomp)
9
- @nodes << (((n.key == 'en-US') || (n.key == 'en')) ? Node.new(self, i, "#{locale_name}:") : n)
10
- end
11
- end
12
-
13
- def next
14
- @current_line += 1
15
- return false if @current_line == @nodes.size
16
- @nodes[@current_line].is_blank_or_comment? ? self.next : @nodes[@current_line]
17
- end
18
-
19
- def prev
20
- return false if @current_line == 0
21
- @current_line -= 1
22
- @nodes[@current_line].is_blank_or_comment? ? self.prev : @nodes[@current_line]
23
- end
24
-
25
- def parent_of(child)
26
- @current_line = child.line
27
- while n = self.prev
28
- return n if n.indent_level == child.indent_level - 2
29
- end
30
- self
31
- end
32
-
33
- def children_of(parent)
34
- nodes = Nodes.new
35
- @current_line = parent.line
36
- while n = self.next
37
- if n.indent_level < parent.indent_level + 2
38
- break
39
- elsif n.indent_level == parent.indent_level + 2
40
- nodes << n
41
- end
42
- end
43
- nodes
44
- end
45
-
46
- def [](node_name)
47
- @current_line = @nodes.detect {|n| (n.indent_level == 0) && (n.key == node_name)}.line
48
- @nodes[@current_line]
49
- end
50
-
51
- def document
52
- self
53
- end
54
-
55
- def path
56
- ''
57
- end
58
-
59
- def to_s
60
- @nodes.inject('') do |ret, n|
61
- ret << n.text + "\n"
62
- end
63
- end
64
- end
65
-
66
- class Node
67
- attr_reader :line, :indent_level
68
-
69
- def initialize(doc, line_index, text)
70
- @doc, @line, @text = doc, line_index, text
71
- @text =~ /(^\s*)/
72
- @indent_level = $1.nil? ? 0 : $1.size
73
- @yaml = YAML.load(@text + ' ')
74
- end
75
-
76
- def parent
77
- @parent ||= @doc.parent_of self
78
- end
79
-
80
- def children
81
- @children ||= @doc.children_of(self)
82
- end
83
-
84
- def [](node_name)
85
- children.detect {|c| c.key == node_name}
86
- end
87
-
88
- def key
89
- @yaml.is_a?(Hash) ? @yaml.keys.first : nil
90
- end
91
-
92
- def value
93
- @yaml.is_a?(Hash) ? @yaml.values.first : nil
94
- end
95
-
96
- def value=(val)
97
- if @yaml[self.key] != val
98
- @yaml[self.key] = val
99
- @changed = true
100
- end
101
- end
102
-
103
- def text
104
- if @changed
105
- v = if self.value.is_a?(Array)
106
- "[#{self.value * ', '}]"
107
- else
108
- %Q["#{self.value}"]
109
- end
110
- "#{' ' * self.indent_level}#{self.key}: #{v}"
111
- else
112
- @text
113
- end
114
- end
115
-
116
- def changed?
117
- @changed
118
- end
119
-
120
- def is_blank_or_comment?
121
- @text.sub(/#.*$/, '').gsub(/\s/, '').empty?
122
- end
123
-
124
- def path
125
- @path ||= "#{self.parent.path}/#{self.key}"
126
- end
127
-
128
- def descendant_nodes(&block)
129
- yield self if self.value
130
- self.children.each {|child| child.descendant_nodes(&block)} if self.children
131
- end
132
- end
133
-
134
- class Nodes < Array
135
- def[](index)
136
- return detect {|node| node.key == index} if index.is_a?(String)
137
- super
138
- end
139
- end
140
- end
141
-