humanized 0.0.1.alpha → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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