humanized 0.0.1.alpha

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.
@@ -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