humanized 0.0.1.alpha

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,36 @@
1
+ # -*- encoding : utf-8 -*-
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the Affero GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
+ # GNU General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU General Public License
13
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
14
+ #
15
+ # (c) 2011 by Hannes Georg
16
+ #
17
+
18
+ module Humanized
19
+ module Number
20
+
21
+ def number(humanizer, number, format = 'default')
22
+ if format == 'default'
23
+ it = number._(:format,:default)
24
+ else
25
+ it = number._.format( format._ | :default._ )
26
+ end
27
+ f = humanizer.get(it)
28
+ if f.kind_of? String
29
+ return sprintf(f,number)
30
+ end
31
+ warn "Unable to find Number format: #{it.inspect}."
32
+ return ''
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding : utf-8 -*-
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the Affero GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
+ # GNU General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU General Public License
13
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
14
+ #
15
+ # (c) 2011 by Hannes Georg
16
+ #
17
+
18
+ module Humanized
19
+
20
+ # A Reference can be used to redirect lookups for certain paths.
21
+ class Ref < Array
22
+
23
+ def inspect
24
+ '!ref'+super
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,264 @@
1
+ # -*- encoding : utf-8 -*-
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the Affero GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
+ # GNU General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU General Public License
13
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
14
+ #
15
+ # (c) 2011 by Hannes Georg
16
+ #
17
+
18
+ module Humanized
19
+ # A {Scope} is _the_ way to tell a {Humanizer} what you want from it.
20
+ # It contains of three parts:
21
+ # * {#path a list of paths}, which will be looked up in a {Source source}
22
+ # * {#default a default}, which will be used if nothing was found
23
+ # * {#variables variables}, which will be used to interpolate a found string
24
+ # That's all you need!
25
+ # The good thing: you'll unlikly create a scope by hand, that's done automatically with "_"!
26
+ #
27
+ # == Examples
28
+ #
29
+ # The basic steps:
30
+ # # Creates a scope which looks up ":a" with no default and no variables:
31
+ # :a._
32
+ # # Creates a scope which looks up nothing, has a default of "String" but no variables:
33
+ # "String"._
34
+ # # Creates a scope which looks up nothing, has no default but the variable :foo = "bar"
35
+ # {:foo => 'bar'}._
36
+ #
37
+ # Combining these steps brings the power:
38
+ #
39
+ # # Creates a scope which looks up ":a", has a default of "String" and the variable :foo = "bar"
40
+ # :a._ + "String"._ + {:foo => 'bar'}._
41
+ # # Shorthand for this:
42
+ # :a._("String", :foo => 'bar')
43
+ #
44
+ # The "_"-method is overloaded for many things. For example for inheritance:
45
+ #
46
+ # module Site
47
+ # class User
48
+ # end
49
+ # class Admin < User
50
+ # end
51
+ # end
52
+ # # Creates a scope matching ":site, :admin" or ":site, :user":
53
+ # Site::Admin._
54
+ # # This creates the same:
55
+ # Site::Admin.new._
56
+ #
57
+ # And for Arrays:
58
+ # # This matches ":a, :b, :c":
59
+ # [:a, :b, :c]._
60
+ #
61
+ # Finally for Scopes itself:
62
+ # # Given scope is a Scope this is always true:
63
+ # scope._._ == scope._
64
+ #
65
+ # I could continue the whole day ...
66
+ #
67
+ # == Tricks
68
+ # A Scope responds to any method giving a new Scope suffixed by the method name
69
+ # # Looks up ":a, :b, :c"
70
+ # :a._.b.c
71
+ # "_" can also take a block which is instance evaled on the scope:
72
+ # # Looks up ":a, :b, :c"
73
+ # :a._{ b.c }
74
+ # # Looks up ":a, :x" or ":a, :y"
75
+ # :a._{ x | y }
76
+ # There are two special scopes:
77
+ # # Looks up "", which will we be the whole source
78
+ # Humanized::Scope::Root
79
+ # # Looks up nothing
80
+ # Humanized::Scope::None
81
+ #
82
+ class Scope
83
+
84
+ include Enumerable
85
+ # @private
86
+ UNMAGIC_METHODS = [:to_ary]
87
+ # @private
88
+ NAME_REGEX = /[a-z_]+/.freeze
89
+ # @private
90
+ OPTIONAL_NAME_REGEX = /([a-z_]+)\?/.freeze
91
+
92
+ attr_reader :path, :depth, :variables, :default
93
+
94
+ def self.from_str(str)
95
+ Scope.new([ str.explode('.').map(&:to_sym) ])
96
+ end
97
+
98
+ def initialize(path = [[]], depth = 1, variables = {}, default = nil)
99
+ @path = path.uniq
100
+ @path.each do |path|
101
+ path.freeze
102
+ end
103
+ @path.freeze
104
+ @depth = depth
105
+ @variables = variables
106
+ @default = default
107
+ end
108
+
109
+ # This method is a here to enable awesome DSL.
110
+ #== Example
111
+ # s = Scope.new
112
+ # s.defining.a.scope.using_methods # gives: (defining.a.scope.using_methods)
113
+ # s.defining(:a,:scope,:using_methods) # gives: (defining.a.scope.using_methods)
114
+ # s.this{ is.awesome | is.awful } # gives: (this.is.awesome , this.is.awful)
115
+ #
116
+ def method_missing(name, *args, &block)
117
+ return super if UNMAGIC_METHODS.include? name
118
+ name_str = name.to_s
119
+ if OPTIONAL_NAME_REGEX =~ name_str
120
+ return ( self + $1.to_sym | self )._(*args,&block)
121
+ end
122
+ if NAME_REGEX =~ name_str
123
+ return ( self + name )._(*args,&block)
124
+ end
125
+ super
126
+ end
127
+
128
+ def ==(other)
129
+ return false unless other.kind_of? Scope
130
+ return @path == other.path
131
+ end
132
+
133
+ # Creates a {Scope scope} which matches either self or the other scope.
134
+ # @example
135
+ # # this will match ":to_be" and ":not_to_be":
136
+ # ( :to_be._ | :not_to_be._ )
137
+ #
138
+ # @param [Scope] other another scope
139
+ # @return [Scope] a new scope
140
+ def |(other)
141
+ return other if @path.none?
142
+ return self.dup if other.none?
143
+ sp = self.path
144
+ sd = self.depth
145
+ op = other.path
146
+ od = other.depth
147
+ result = []
148
+ i = 0
149
+ j = 0
150
+ while i < sp.size and j < op.size
151
+ result.concat sp[i,sd] if sp.size > i
152
+ result.concat op[j,od] if op.size > j
153
+ i = i + sd
154
+ j = j + od
155
+ end
156
+ return Scope.new( result, sd + od , self.variables.merge(other.variables), other.default)
157
+ end
158
+
159
+ # Creates a new scope which will optionally match this scope suffixed with the key.
160
+ #
161
+ # @example
162
+ # # this will match ":borat_is_stupid, :not" and ":borat_is_stupid":
163
+ # :borat_is_stupid._.optionally(:not)
164
+ #
165
+ # @param key
166
+ # @return [Scope] a new scope
167
+ def optionally(key)
168
+ return self._(key) | self
169
+ end
170
+
171
+ def [](*args)
172
+ sp = self.path
173
+ sd = self.depth
174
+ op = args
175
+ od = 1
176
+ result = []
177
+ self.each do |path|
178
+ args.each do |arg|
179
+ result << path + [arg]
180
+ end
181
+ end
182
+ return Scope.new( result, args.size )
183
+ end
184
+
185
+ # Chain scopes together
186
+ # @example
187
+ # # this will match ":a,:b,:c"
188
+ # :a._ + :b._ + :c._
189
+ #
190
+ # @param *args an array of scopes for chaining
191
+ # @return [Scope]
192
+ def +(*args)
193
+ return self if args.none?
194
+ if( args.first.kind_of? Scope )
195
+ s = args.first
196
+ return Scope.new(@path, @depth, variables.merge(s.variables), self.default || s.default ) if @path.none? or s.path.none?
197
+ # TODO: maybe modify depth too?
198
+ new_path = []
199
+ @path.each do |x|
200
+ s.each do |path|
201
+ new_path << x + path
202
+ end
203
+ end
204
+ return Scope.new(new_path, s.depth, variables.merge(s.variables), self.default || s.default )
205
+ end
206
+ if @path.none?
207
+ return self
208
+ end
209
+ return Scope.new( @path.map{|x| x + args} , @depth , @variables, @default)
210
+ end
211
+
212
+ def _(*args,&block)
213
+ thiz = self
214
+ vars = nil
215
+ loop do
216
+ break if args.none?
217
+ arg = args.shift
218
+ if arg.kind_of? Symbol or arg.kind_of? Scope
219
+ thiz += arg
220
+ elsif arg.class == Hash
221
+ vars = arg
222
+ else
223
+ thiz += arg._
224
+ end
225
+ end
226
+ if block_given?
227
+ thiz = thiz.instance_eval(&block)
228
+ end
229
+ if vars
230
+ return thiz.with_variables(vars)
231
+ else
232
+ return thiz
233
+ end
234
+ end
235
+
236
+ def with_variables(vars)
237
+ Scope.new(@path, @depth, variables.merge(vars), @default)
238
+ end
239
+
240
+ def with_default(default)
241
+ Scope.new(@path, @depth, @variables, default)
242
+ end
243
+
244
+ def inspect
245
+ return '(' + @path.map{|p| p.join '.'}.join(' , ') + ' '+depth.to_s+' v='+variables.inspect+' d='+default.inspect+')'
246
+ end
247
+
248
+ # Iterates over all possible paths.
249
+ # @yieldparam [Array] path
250
+ def each(&block)
251
+ @path.each(&block)
252
+ end
253
+
254
+ def humanization_key
255
+ return self
256
+ end
257
+
258
+ Root = self.new([[]],1)
259
+ None = self.new([],0)
260
+ Meta = self.new([[:__meta__]],1)
261
+
262
+
263
+ end
264
+ end
@@ -0,0 +1,169 @@
1
+ # -*- encoding : utf-8 -*-
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the Affero GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
+ # GNU General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU General Public License
13
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
14
+ #
15
+ # (c) 2011 by Hannes Georg
16
+ #
17
+ require 'sync'
18
+ module Humanized
19
+ # A source lets you lookup,store and load data needed for humanization.
20
+ class Source
21
+
22
+ def initialize(data = {})
23
+ @source = data
24
+ @sync = Sync.new
25
+ @loaded = Set.new
26
+ end
27
+
28
+ #
29
+ # Loads a data-file or a dir of data-files.
30
+ #
31
+ # @param [String] path to a dir or file
32
+ # @option opts [Scope] :scope the root scope, where the loaded data will be stored ( default: L )
33
+ # @option opts [String] :grep a grep to be used when a dir is given ( default: '**/*.*' )
34
+ # @return self
35
+ def load(path,opts ={})
36
+ options = {:scope => Scope::Root, :grep => '**/*.*'}.update(opts)
37
+ if File.directory?(path)
38
+ f = File.join(path,options[:grep])
39
+ package('grep:' + f) do
40
+ Dir[f].each do |file|
41
+ package('file:'+file) do
42
+ data = self.read_file(file)
43
+ if data
44
+ xpath = file[path.size..(-1-File.extname(file).size)].split('/')
45
+ xpath.shift if xpath.first == ''
46
+ xscope = options[:scope]._(*xpath.map(&:to_sym))
47
+ self.store(xscope.first,data)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ else
53
+ package('file:'+path) do
54
+ data = self.read_file(path)
55
+ if data
56
+ self.store(options[:scope].first,data)
57
+ end
58
+ end
59
+ end
60
+ return self
61
+ end
62
+
63
+ # Stores the given data on the base.
64
+ # @param [Object] data
65
+ # @see #store
66
+ def <<(data)
67
+ store([],data)
68
+ end
69
+
70
+ # This is method which will help you loading data once.
71
+ # It will load every package just one time.
72
+ # == Example
73
+ # source = Source.new
74
+ # 10.times do
75
+ # source.package('base') do |s|
76
+ # s << {:base => { :data => 'more data'}} # <= This data will be only loaded once!
77
+ # end
78
+ # end
79
+ #
80
+ # @param [String] package name
81
+ # @yield self
82
+ # @yieldparam [Source] self
83
+ def package(name)
84
+ return nil if @loaded.include? name
85
+ @sync.synchronize(Sync::EX){
86
+ return nil if @loaded.include? name
87
+ yield(self)
88
+ @loaded << name
89
+ }
90
+ end
91
+
92
+ # Retrieves data
93
+ # @param [Scope, #each] scope a scope containing the paths to search for
94
+ # @return [String, Object, nil] data
95
+ def get(scope, default = nil)
96
+ scope.each do |path|
97
+ result = find(path, @source)
98
+ return result unless result.nil?
99
+ end
100
+ return default
101
+ end
102
+
103
+ # Stores data at the path
104
+ # @param [Array] path a path to store the data at
105
+ # @param [Object] data the data to store
106
+ def store(path ,data)
107
+ store!(path, data)
108
+ end
109
+
110
+ protected
111
+
112
+ def store!(path ,str, hsh = @source)
113
+ @sync.synchronize(Sync::EX){
114
+ hshc = hsh
115
+ l = path.length - 1
116
+ if str.kind_of? Hash
117
+ l += 1
118
+ end
119
+ (0...l).each do |i|
120
+ a = path[i]
121
+ unless hshc.key?(a)
122
+ hshc[a] = {}
123
+ end
124
+ hshc = hshc[a]
125
+ while hshc.kind_of? Humanized::Ref
126
+ hshc = find(hshc, @source)
127
+ end
128
+ end
129
+ if str.kind_of? Hash
130
+ hshc.deep_merge!(str)
131
+ else
132
+ hshc[path[l]] = str
133
+ end
134
+ return nil
135
+ }
136
+ end
137
+
138
+ def read_file(file)
139
+ ext = File.extname(file)[1..-1]
140
+ meth = "read_#{ext}".to_sym
141
+ if self.respond_to?(meth)
142
+ return self.send(meth,file)
143
+ else
144
+ warn "No reader found for #{ext}."
145
+ return nil
146
+ end
147
+ end
148
+
149
+ def find(path, hsh)
150
+ hshc = hsh
151
+ l = path.length - 1
152
+ (0...l).each do |i|
153
+ a = path[i]
154
+ return nil unless hshc.key?(a)
155
+ hshc = hshc[a]
156
+ while hshc.kind_of? Humanized::Ref
157
+ hshc = find(hshc, @source)
158
+ end
159
+ return nil unless hshc.respond_to? :[]
160
+ end
161
+ hshc = hshc[path[l]]
162
+ while hshc.kind_of? Humanized::Ref
163
+ hshc = find(hshc, @source)
164
+ end
165
+ return hshc
166
+ end
167
+
168
+ end
169
+ end