humanized 0.0.1.alpha → 0.0.1

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.
@@ -14,7 +14,7 @@
14
14
  #
15
15
  # (c) 2011 by Hannes Georg
16
16
  #
17
-
17
+ require 'logger'
18
18
  require "facets/module/home.rb"
19
19
  require "facets/module/basename.rb"
20
20
  require "facets/module/anonymous.rb"
@@ -24,6 +24,27 @@ require "facets/module/alias_method_chain.rb"
24
24
  # readable output.
25
25
  module Humanized
26
26
 
27
+ class FailedInterpolation < String
28
+
29
+ ERROR_STRING = '[error]'.freeze
30
+
31
+ attr_reader :exception, :string, :variables
32
+
33
+ def initialize(exception, string, variables)
34
+ @exception = exception
35
+ @string = string
36
+ @variables = variables
37
+ super(ERROR_STRING)
38
+ end
39
+
40
+ end
41
+
42
+ class << self
43
+ attr_accessor :logger
44
+ end
45
+
46
+ self.logger = Logger.new(STDERR)
47
+
27
48
  module HasNaturalGenus
28
49
 
29
50
  def self.included(base)
@@ -44,9 +65,8 @@ module Humanized
44
65
  end
45
66
 
46
67
  end
47
- require "humanized/ref"
48
68
  require "humanized/humanizer"
49
- require "humanized/scope"
69
+ require "humanized/query"
50
70
  Dir[File.expand_path('humanized/core_ext/*.rb', File.dirname(__FILE__))].each do |file|
51
71
  require file
52
72
  end
@@ -16,28 +16,30 @@
16
16
  #
17
17
 
18
18
  module Humanized
19
- class Compiler
20
-
21
- class Compiled < Proc
22
-
23
- attr_accessor :str
24
-
25
- def initialize(str)
26
- @str = str
27
- super()
28
- return self
29
- end
19
+
20
+ class Compiled < Proc
30
21
 
31
- def to_s
32
- @str
33
- end
34
-
22
+ attr_accessor :str
23
+
24
+ def initialize(str)
25
+ @str = str
26
+ super()
27
+ return self
28
+ end
29
+
30
+ def to_s
31
+ @str
35
32
  end
33
+
34
+ end
36
35
 
36
+ class CompilerBase
37
+
37
38
  def initialize
38
39
  @compiled = Hash.new{|hsh,x| hsh[x] = compile!(x)}
39
40
  end
40
41
 
42
+
41
43
  # Compiles a String into a Proc
42
44
  # @param [String] str A formated String
43
45
  # @return [Compiled] A Proc, which will handle interpolation.
@@ -45,6 +47,16 @@ class Compiler
45
47
  def compile(str)
46
48
  @compiled[str]
47
49
  end
50
+
51
+ protected
52
+ def compile!(str)
53
+ raise NoMethodError, "Please implement a compile!-method."
54
+ end
55
+
56
+ end
57
+
58
+ class Compiler < CompilerBase
59
+
48
60
  protected
49
61
 
50
62
  VAR_REGEXP = /^%([a-z_]+)/
@@ -53,18 +65,23 @@ protected
53
65
 
54
66
  TRANSFORMER = lambda{|token|
55
67
  if token.kind_of? Array
56
- "[#{token.map(&TRANSFORMER).join(',')}].join()"
68
+ "[#{token.map(&TRANSFORMER).join(',')}].map(&:to_s).join()"
57
69
  elsif token.kind_of? String
58
70
  token.inspect
59
71
  elsif token.kind_of? Symbol
60
72
  "variables[#{token.inspect}]"
61
73
  elsif token.kind_of? Hash
62
- "interpolater.#{token[:method]}(humanizer,#{token[:args].map(&TRANSFORMER).join(',')})"
74
+ if token[:args].any?
75
+ "o.#{token[:method]}(humanizer,#{token[:args].map(&TRANSFORMER).join(',')})"
76
+ else
77
+ "o.#{token[:method]}(humanizer)"
78
+ end
63
79
  end
64
80
  }
65
81
 
66
82
  def compile!(str)
67
- return eval('Compiled.new(str){|humanizer,interpolater,variables| ' + TRANSFORMER.call(read(str)) +' }')
83
+ str = str.dup.freeze
84
+ return eval('Compiled.new(str) do |humanizer,variables| o = humanizer.interpolater.object ;' + TRANSFORMER.call(read(str)) +' end')
68
85
  end
69
86
 
70
87
  def read(str)
@@ -124,4 +141,4 @@ protected
124
141
 
125
142
  end
126
143
 
127
- end
144
+ end
@@ -19,7 +19,7 @@ class Array
19
19
  if self.any?
20
20
  return self[0]._(*self[1..-1])._(*args,&block)
21
21
  else
22
- Humanized::Scope::None
22
+ Humanized::Query::None
23
23
  end
24
24
  end
25
25
  end
@@ -16,10 +16,14 @@
16
16
  #
17
17
  class Hash
18
18
  def _(*args,&block)
19
- if self.class == Hash
20
- Humanized::Scope::None._(*args,&block).with_variables(self)
19
+ if humanized_variables?
20
+ Humanized::Query::Root._(*args,&block).with_variables(self)
21
21
  else
22
22
  super
23
23
  end
24
24
  end
25
+
26
+ def humanized_variables?
27
+ self.class == Hash
28
+ end
25
29
  end
@@ -16,7 +16,7 @@
16
16
  #
17
17
  class Module
18
18
 
19
- # Generates a {Humanized::Scope scope} for a Module or Class. This will be used by default by
19
+ # Generates a {Humanized::Query query} for a Module or Class. This will be used by default by
20
20
  # this Module and by all Objects of this Class.
21
21
  def humanization_key!
22
22
  if self.anonymous?
@@ -26,7 +26,7 @@ class Module
26
26
  if h != Object and h.respond_to? :humanization_key
27
27
  result = h.humanization_key + self.basename.downcase.to_sym
28
28
  else
29
- result = Humanized::Scope::Root.+(*self.name.split('::').map{|s| s.downcase.to_sym })
29
+ result = Humanized::Query::Root.+(*self.name.split('::').map{|s| s.downcase.to_sym })
30
30
  end
31
31
  thiz = self
32
32
  if defined? thiz.superclass and self.superclass != Object
@@ -16,6 +16,6 @@
16
16
  #
17
17
  class NilClass
18
18
  def _(*args,&block)
19
- Humanized::Scope::None._(*args,&block)
19
+ Humanized::Query::None._(*args,&block)
20
20
  end
21
21
  end
@@ -14,9 +14,15 @@
14
14
  #
15
15
  # (c) 2011 by Hannes Georg
16
16
  #
17
+ require 'facets/object/dup'
17
18
  class Object
18
19
  def humanization_key
19
- self.class.humanization_key
20
+ if self.frozen? or !self.dup?
21
+ i = self
22
+ else
23
+ i = self.dup.freeze
24
+ end
25
+ self.class.humanization_key.optionally(:instance).with_variables({:self => i })
20
26
  end
21
27
  def _(*args,&block)
22
28
  self.humanization_key._(*args,&block)
@@ -16,6 +16,6 @@
16
16
  #
17
17
  class String
18
18
  def _(*args,&block)
19
- Humanized::Scope::None._(*args,&block).with_default(self)
19
+ Humanized::Query::Root._(*args,&block).with_default(self.dup)
20
20
  end
21
21
  end
@@ -16,6 +16,6 @@
16
16
  #
17
17
  class Symbol
18
18
  def _(*args,&block)
19
- Humanized::Scope.new([[self]])._(*args,&block)
19
+ Humanized::Query.new([[self]])._(*args,&block)
20
20
  end
21
21
  end
@@ -17,9 +17,11 @@
17
17
  require 'facets/array/extract_options.rb'
18
18
  require 'facets/hash/deep_merge.rb'
19
19
  require 'sync'
20
+ require 'logger'
20
21
  require 'set'
21
22
  require 'humanized/compiler.rb'
22
23
  require 'humanized/source.rb'
24
+ require 'humanized/interpolater.rb'
23
25
  module Humanized
24
26
  # A Humanizer has one simple task: <b>create strings in its language/dialect/locale/whatever!</b>
25
27
  #
@@ -31,63 +33,211 @@ module Humanized
31
33
  # The most important method you may use is {#[]}.
32
34
  class Humanizer
33
35
 
34
- # This is a simple object without public methods. You can use this as a collection for interpolation methods.
35
- class PrivatObject
36
- public_instance_methods.each do |meth|
37
- private meth
36
+ IS_STRING = lambda{|x| x.kind_of? String }
37
+
38
+ class << self
39
+
40
+ private
41
+
42
+ # Defines a component on this class and all subclasses.
43
+ def component( name, options = {}, &initializer )
44
+ @components ||= {}
45
+ options = options.dup
46
+ if !block_given?
47
+ initializer = lambda{|value, old| value}
48
+ end
49
+ options[:initializer] = initializer
50
+ options.freeze
51
+ @components[name.to_sym] = options
52
+ attr_accessor name
53
+ public name.to_sym
54
+ protected "#{name.to_s}=".to_sym
55
+ if options[:delegate]
56
+ if options[:delegate].kind_of? Hash
57
+ options[:delegate].each do | from, to |
58
+ module_eval <<RB
59
+ def #{from.to_s}(*args, &block)
60
+ #{name.to_s}.#{to.to_s}(*args,&block)
61
+ end
62
+ RB
63
+ end
64
+ elsif options[:delegate].respond_to? :each
65
+ options[:delegate].each do | from |
66
+ module_eval <<RB
67
+ def #{from.to_s}(*args, &block)
68
+ #{name.to_s}.#{from.to_s}(*args,&block)
69
+ end
70
+ RB
71
+ end
72
+ end
73
+
74
+ end
75
+ # TODO: iterater superclass combinations
76
+ if @component_combinations
77
+ cc = @component_combinations
78
+ @component_combinations = []
79
+ cc.each do | rest, block |
80
+ when_combining_components(rest, block)
81
+ end
82
+ end
38
83
  end
39
84
 
40
- public :extend
85
+ def when_combining_components(*args,&block)
86
+ rest = args.map( &:to_sym ).uniq - self.components.to_a
87
+ if rest.none?
88
+ block.call( self )
89
+ else
90
+ @component_combinations = [] unless @component_combinations
91
+ @component_combinations << [ rest, block ]
92
+ end
93
+ end
94
+
95
+ public
96
+ def each_component
97
+ klass = self
98
+ components = Set.new
99
+ while( klass )
100
+ a = klass.instance_variable_get("@components")
101
+ if a
102
+ a.each do |name, options|
103
+ unless components.include?(name)
104
+ yield(name, options) if block_given?
105
+ components << name
106
+ end
107
+ end
108
+ end
109
+ klass = klass.superclass
110
+ end
111
+ return components
112
+ end
41
113
 
114
+ alias_method :components, :each_component
115
+
116
+ end
117
+
118
+ component :interpolater do |value, old|
119
+ value || Interpolater.new
120
+ end
121
+
122
+ component :source, :delegate =>[:package, :load , :<<, :get ] do |value, old|
123
+ if value.kind_of? Source
124
+ value
125
+ elsif value.kind_of? Hash
126
+ Source.new(value)
127
+ elsif value.nil?
128
+ Source.new
129
+ else
130
+ raise ArgumentError, "Expected :source to be a kind of Humanized::Source, Hash or nil."
131
+ end
132
+ end
133
+
134
+ component :compiler do |value, old|
135
+ value || Compiler.new
42
136
  end
43
137
 
44
- attr_reader :interpolater, :source, :compiler
138
+ component :logger do |value, old|
139
+ if value.kind_of? Logger
140
+ value
141
+ elsif value.respond_to? :write and value.respond_to? :close
142
+ Logger.new(value)
143
+ elsif value.nil?
144
+ Humanized.logger
145
+ elsif value.kind_of? FalseClass
146
+ value
147
+ else
148
+ raise ArgumentError, "Expected :logger to be a kind of Logger, IO, nil or false."
149
+ end
150
+ end
45
151
 
46
152
  # Creates a new Humanizer
47
153
  #
48
154
  # @option components [Object] :interpolater This object which has all interpolation methods defined as public methods.
49
155
  # @option components [Compiler] :compiler A compiler which can compile strings into procs. (see Compiler)
50
156
  # @option components [Source] :source A source which stores translated strings. (see Source)
51
- def initialize(components = {})
52
- @interpolater = (components[:interpolater] || PrivatObject.new)
53
- @compiler = (components[:compiler] || Compiler.new)
54
- @source = (components[:source] || Source.new)
157
+ # @option components [Logger, IO, false] :logger A logger for this Humanizer or false to disable logging.
158
+ def initialize(*args)
159
+
160
+ components = args.last.kind_of?(Hash) ? args.pop : {}
161
+
162
+ used_keys = Set.new
163
+
164
+ args.each do | mod |
165
+ if mod.kind_of? Module
166
+ extend mod
167
+ end
168
+ end
169
+
170
+ if args.first.kind_of? Humanized::Humanizer
171
+
172
+ humanizer = args.first
173
+
174
+ modules = (class << humanizer; included_modules ; end ) - self.class.included_modules
175
+
176
+ modules.each do | mod |
177
+
178
+ extend mod
179
+
180
+ end
181
+
182
+ (class << self ; self ; end).each_component do |name, options|
183
+ if humanizer.respond_to? name
184
+ if components.key? name
185
+ self.send("#{name}=".to_sym, options[:initializer].call(components[name],humanizer.respond_to?(name) ? humanizer.send(name) : nil ))
186
+ else
187
+ self.send("#{name}=".to_sym, humanizer.send(name))
188
+ end
189
+ else
190
+ self.send("#{name}=".to_sym, options[:initializer].call(components[name],nil))
191
+ end
192
+ used_keys << name
193
+ end
194
+ else
195
+
196
+ (class << self ; self ; end).each_component do |name, options|
197
+ self.send("#{name}=".to_sym, options[:initializer].call(components[name], nil))
198
+ used_keys << name
199
+ end
200
+
201
+ end
202
+
203
+ components.each do | k, v |
204
+ warn "Unused key #{k.inspect} with value #{v.inspect} in Humanizer.initialize" unless used_keys.include? k
205
+ end
206
+
207
+
55
208
  end
56
209
 
210
+
57
211
  # Creates a new Humanizer which uses the interpolater, compiler and source of this Humanizer unless other values for them were specified.
58
212
  # @see #initialize
59
- def renew(components)
60
- self.class.new({:interpolater=>@interpolater,:compiler=>@compiler,:source=>@source}.update(components))
213
+ def new(components)
214
+ self.class.new(self, components)
61
215
  end
62
216
 
63
217
  # Creates a String from the input. This will be the most used method in application code.
64
- # It expects a {Scope} as argument. Anything that is not a {Scope} will be converted into a {Scope} using the "_"-method.
218
+ # It expects a {Query} as argument. Anything that is not a {Query} will be converted into a {Query} using the "_"-method.
65
219
  # This enables you to pass any object to this method. The result is mainly determined by result of the "_"-method.
66
220
  # For
67
221
  #
68
- # @param [Scope, #_, Object] *args
222
+ # @param [Query, #_, Object] *args
69
223
  # @return [String]
70
224
  def [](*args)
71
225
  it = args._
72
226
 
73
227
  vars = it.variables
74
228
  default = it.default
75
- result = @source.get(it, default)
229
+ result = @source.get(it, :default=>default, :accepts=>IS_STRING)
76
230
  result = default unless result.kind_of? String
77
231
  if result.kind_of? String
78
232
  return interpolate(result,vars)
79
- elsif default.__id__ != result.__id__
80
- warn "[] should be only used for strings. For anything else use get."
233
+ else
234
+ if logger
235
+ logger.error do
236
+ "Expected to retrieve a String, but got: #{result.inspect}\n\tQuery: #{it.inspect}"
237
+ end
238
+ end
239
+ return ""
81
240
  end
82
- return result
83
- end
84
-
85
- # This is a wrapper for @source.get.
86
- # The only thing it does additionally is converting all params into a Scope.
87
- # @see Source#get
88
- def get(base,*rest)
89
- it = base._(*rest)
90
- return @source.get(it, it.default)
91
241
  end
92
242
 
93
243
  # Stores a translation
@@ -96,26 +246,20 @@ class Humanizer
96
246
  @source.store(it._(*rest).first,last)
97
247
  end
98
248
 
99
- # This is an alias for @source.package
100
- # @see Source#package
101
- def package(*args,&block)
102
- @source.package(*args,&block)
103
- end
104
-
105
- # This is an alias for @source.load
106
- # @see Source#load
107
- def load(*args,&block)
108
- @source.load(*args,&block)
109
- end
110
-
111
- # This is an alias for @source.<<
112
- # @see Source#<<
113
- def <<(x)
114
- @source << x
249
+ def interpolate(str,vars={})
250
+ return @compiler.compile(str).call(self,vars)
251
+ rescue Exception => e
252
+ return handle_interpolation_exception(e, str, vars)
115
253
  end
116
254
 
117
- def interpolate(str,vars={})
118
- @compiler.compile(str).call(self,@interpolater,vars)
255
+ protected
256
+ def handle_interpolation_exception(e, str, vars)
257
+ if logger
258
+ logger.error do
259
+ "Failed interpolating \"#{str}\"\n\tVariables: #{vars.inspect}\n\tMessage: #{e.message}\n\tTrace:\t" + e.backtrace.join("\n\t\t")
260
+ end
261
+ end
262
+ return FailedInterpolation.new(e, str, vars)
119
263
  end
120
264
 
121
265
  end