king_placeholder 0.0.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,10 +1,11 @@
1
1
  = KingPlaceholder
2
2
  {<img src="https://secure.travis-ci.org/salesking/king_placeholder.png?branch=master" alt="Build Status" />}[http://travis-ci.org/salesking/king_placeholder]
3
+
3
4
  This gem was extracted from SalesKing, where it does placeholders substitution
4
5
  for user supplied strings in Email-, Text- and Export-Templates.
5
6
 
6
7
  Placeholders are declared in each class and afterwards any strings containing
7
- [placeholders] can be parsed in the scope of the model.
8
+ <b>[placeholders]</b> can be parsed in the scope of the model.
8
9
 
9
10
  Parsing is done by a simple statemachine using regex for placeholder detection.
10
11
 
@@ -16,7 +17,7 @@ Define the available methods in your class with 'has_placeholders'
16
17
  include KingPlaceholder
17
18
  has_many :comments
18
19
  has_one :company
19
- has_placeholders :firstname, :email
20
+ has_placeholders :name, :email
20
21
  end
21
22
 
22
23
  class Comment
@@ -43,6 +44,33 @@ Handle relations
43
44
  @user.expand_placeholders("[company.name]")
44
45
  => MyCompany
45
46
 
47
+ Set a custom formatting method(in format_placeholder) throught which all fields
48
+ are run, if you have special money or date formatters.
49
+
50
+ class User
51
+ include KingPlaceholder
52
+ has_placeholders :created_at
53
+
54
+ def format_placeholder(field)
55
+ I18n.l( self.send(field) )
56
+ end
57
+ end
58
+
59
+ Use callbacks to setup / teardown env variables
60
+
61
+ class User
62
+ include KingPlaceholder
63
+ has_placeholders :name, :email
64
+
65
+ def before_expand_placeholders
66
+ I18n.locale = self.language
67
+ end
68
+ def after_expand_placeholders
69
+ I18n.locale = nil
70
+ end
71
+ end
72
+
73
+
46
74
  Also see specs
47
75
 
48
76
  == TODO
@@ -18,9 +18,6 @@ Gem::Specification.new do |gem|
18
18
  gem.add_runtime_dependency 'statemachine'
19
19
  gem.add_runtime_dependency 'i18n'
20
20
  gem.add_runtime_dependency 'activesupport'
21
- gem.add_runtime_dependency 'actionpack'
22
- #gem.add_runtime_dependency 'actionview'
23
- #gem.add_runtime_dependency 'king_views'
24
21
 
25
22
  gem.add_development_dependency 'activerecord'
26
23
  gem.add_development_dependency 'rdoc'
@@ -1,8 +1,5 @@
1
1
  require 'statemachine'
2
- require 'action_view' # king_views related suxs
3
- require 'action_controller' # king_views
4
- require 'king_views'
5
-
2
+ require 'active_support/core_ext/string/inflections'
6
3
  module KingPlaceholder
7
4
  # Statemachine for placeholder substitution
8
5
  # The statemachine is created and its state updated from within the Context
@@ -31,7 +28,6 @@ module KingPlaceholder
31
28
  # machine.sm.match
32
29
  # machine.result
33
30
  class Parser
34
- include ::KingFormat::FormattingHelper
35
31
  # reference to statemachine
36
32
  attr_accessor :sm
37
33
  # incoming string
@@ -53,13 +49,9 @@ module KingPlaceholder
53
49
  @opts = opts
54
50
  end
55
51
 
56
- # Before matching is done this method set's up environment variables like
57
- # current_language => i18n.locale
58
- # TODO: decouple and outsource into before_method block to set env vars from outside
52
+ # Before matching is done deep copy content, because otherwise result.gsub!
53
+ # breaks recursion
59
54
  def prepare
60
- init_locale #I18n.locale
61
- set_format_opts # merge date/money format into thread var
62
- # deep copy content, because otherwise result.gsub! breaks recursion
63
55
  @result = @content.dup
64
56
  end
65
57
 
@@ -87,10 +79,8 @@ module KingPlaceholder
87
79
  @sm.finished_matching
88
80
  end
89
81
 
90
- # When finished this method is called to cleanup f.ex. environment variables
91
- # like I18n.locale
92
82
  def cleanup
93
- @current_language && @current_language.reset_locale
83
+
94
84
  end
95
85
 
96
86
  private
@@ -99,7 +89,12 @@ module KingPlaceholder
99
89
  # namespace e.g. [price_to_pay]
100
90
  def sub_string
101
91
  return unless obj.is_placeholder?(@field)
102
- value = strfval(obj, @field)
92
+ value = if @opts[:formatter]
93
+ obj.send(@opts[:formatter], @field)
94
+ else
95
+ obj.send @field
96
+ end
97
+ #value = strfval(obj, @field)
103
98
  @result.gsub!(@placeholder, value.to_s) if value
104
99
  end
105
100
 
@@ -162,44 +157,5 @@ module KingPlaceholder
162
157
  end
163
158
  end
164
159
 
165
- # set format options for KingFormat date/money helpers
166
- # => strfval + strfmoney
167
- # very important, without those all formatting in views screws up
168
- def set_format_opts
169
- Thread.current[:default_currency_format] = if defined?(::Company) && ::Company.current
170
- ::Company.current.money_format
171
- elsif obj.respond_to?(:company) && obj.company
172
- obj.company.money_format
173
- elsif defined?(::Company) && obj.is_a?(::Company)
174
- obj.money_format
175
- else
176
- nil
177
- end
178
-
179
- Thread.current[:default_date_format] = if defined?(::Company) && ::Company.current
180
- ::Company.current.date_format
181
- elsif obj.respond_to?(:company) && obj.company
182
- obj.company.date_format
183
- elsif defined?(::Company) && obj.is_a?(::Company)
184
- obj.date_format
185
- else
186
- nil
187
- end
188
- end
189
-
190
- # Set i18n.locale to given or present custom company locale
191
- # locale is memoized in instance @current_language.
192
- # Only set if a company and the language is available
193
- # === Parameter
194
- # locale<String>:: locale code en, de,..
195
- def init_locale(locale=nil)
196
- if (locale || (obj.respond_to?(:language) && obj.language)) \
197
- && obj.respond_to?(:company) && obj.company
198
- @current_language ||= begin
199
- # find lang in scope of company
200
- ::Language.init_locale(obj.company, locale || obj.language)
201
- end
202
- end
203
- end
204
160
  end #class
205
161
  end #module
@@ -1,3 +1,3 @@
1
1
  module KingPlaceholder
2
- VERSION = "0.0.3"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -21,15 +21,33 @@ module KingPlaceholder
21
21
  end
22
22
 
23
23
  module ClassMethods
24
- # Define fields(methods) available to placeholder substitution
24
+ # Define which fields(methods) are available to placeholder substitution.
25
+ #
26
+ # `before/after_expand_placeholders` hooks are run before the statemachine
27
+ # parsing. Define those methods to setup env variables like I18n.locale or
28
+ # whatever is required to format output strings.
29
+ # The block gets the current parsed object as method argument
30
+ # @example
31
+ # def before_expand_placeholders
32
+ # I18n.locale = self.language
33
+ # end
34
+ # def after_expand_placeholders
35
+ # I18n.locale = nil
36
+ # end
37
+ #
25
38
  # @param [Array[<Symbol>] fieldnames
26
39
  def has_placeholders(*fieldnames)
27
40
  self.placeholders = fieldnames
41
+ include ActiveSupport::Callbacks
42
+ define_callbacks :expand_placeholders
43
+ set_callback :expand_placeholders, :before, :before_parsing
44
+ set_callback :expand_placeholders, :after, :after_parsing
28
45
  include InstanceMethods
29
46
  end
30
47
  end
31
48
 
32
49
  module InstanceMethods
50
+
33
51
  # Check if a given field is declared as placeholder
34
52
  # @param [Object] fieldname to search in placeholders array
35
53
  # @return [Boolean]true if available
@@ -87,20 +105,31 @@ module KingPlaceholder
87
105
  # @param [Object] opts - unused for now
88
106
  # @return [Hash|Array|String] whatever type you throw in is also returned
89
107
  def expand_placeholders(content, opts={})
90
- if content.is_a?(Array) # Expand all array elements and go recursive
108
+ if content.is_a?(Array)
91
109
  result = []
92
110
  content.each{|element| result << self.expand_placeholders(element, opts) }
93
111
  return result
94
- elsif content.is_a?(Hash) # Expand all hash elements and go recursive
112
+ elsif content.is_a?(Hash)
95
113
  result = {}
96
114
  content.each_pair{ |key,val| result[key] = self.expand_placeholders(val, opts) }
97
115
  return result
98
116
  else # Only proceed with strings
99
117
  return content unless content.is_a?(String)
100
118
  end
101
- parser = KingPlaceholder::Parser.new(self, content, opts)
102
- parser.sm.match
103
- parser.result if parser.sm.state == :finished
119
+ run_callbacks :expand_placeholders do
120
+ opts[:formatter] = :format_placeholder if self.respond_to?(:format_placeholder)
121
+ parser = KingPlaceholder::Parser.new(self, content, opts)
122
+ parser.sm.match
123
+ parser.result if parser.sm.state == :finished
124
+ end
104
125
  end
126
+
127
+ protected
128
+ def before_parsing
129
+ before_expand_placeholders if self.respond_to?(:before_expand_placeholders)
130
+ end
131
+ def after_parsing
132
+ after_expand_placeholders if self.respond_to?(:after_expand_placeholders)
133
+ end
105
134
  end # instance methods
106
135
  end # KingPlaceholders
@@ -18,39 +18,47 @@ end
18
18
 
19
19
  class Detail
20
20
  include KingPlaceholder
21
- include KingFormat::MoneyFields
22
21
  attr_accessor :int_field, :money_field, :secret_field, :currency
23
22
  attr_accessor :master
24
- has_money_fields :money_field
25
23
  has_placeholders :int_field, :money_field
26
24
  end
27
25
 
28
26
  describe 'Class with placeholders' do
29
27
 
30
28
  before :each do
31
- I18n.locale = :en_master
32
- # Thread.current[:default_currency_format] = I18n.t(:'number.currency.format')
33
29
  @record = Detail.new
34
- @record.int_field = 1000
35
- @record.money_field = 12.34
30
+ @record.int_field = 1002
31
+ @record.money_field = 12.333
36
32
  end
37
33
 
38
34
  it 'should have native values' do
39
- @record.int_field.should == 1000
40
- @record.money_field.should == 12.34
35
+ @record.int_field.should == 1002
36
+ @record.money_field.should == 12.333
41
37
  end
42
38
 
43
- it 'should have placeholder values' do
44
- @record.expand_placeholders('[int_field]').should == '1000'
45
- @record.expand_placeholders('[money_field]').should == '$12.34'
39
+ it 'should have placeholder value' do
40
+ @record.expand_placeholders('[int_field]').should == '1002'
41
+ @record.expand_placeholders('[money_field]').should == '12.333'
46
42
  end
47
-
43
+ it 'should expand placeholders in an array' do
44
+ @record.expand_placeholders(['[int_field]', '[money_field]', 'static']).should == ['1002', '12.333', 'static']
45
+ end
46
+
47
+ it 'should expand placeholders in a hash' do
48
+ @record.expand_placeholders( :key1 => '[int_field]',
49
+ :key2 => '[money_field]',
50
+ :key3 => 'static'
51
+ ).should ==
52
+ { :key1 => '1002',
53
+ :key2 => '12.333',
54
+ :key3 => 'static' }
55
+ end
48
56
  end
49
57
 
50
- describe 'Expanding of strings containing placeholder' do
58
+ describe 'Placeholder substitution' do
51
59
 
52
60
  before :each do
53
- I18n.locale = :en_master
61
+ I18n.locale = :en
54
62
  @side = Side.new
55
63
  @side.field = 123
56
64
  @master = Master.new
@@ -72,100 +80,94 @@ describe 'Expanding of strings containing placeholder' do
72
80
  @master.details = [@detail1, @detail2]
73
81
  end
74
82
 
75
- it 'should expand placeholders with no placeholders there' do
76
- @detail1.expand_placeholders('without placeholder').should == 'without placeholder'
77
- @detail1.expand_placeholders('[]').should == '[]'
78
- @detail1.expand_placeholders('').should == ''
79
- @detail1.expand_placeholders(nil).should == nil
80
- @detail1.expand_placeholders("\n").should == "\n"
81
- end
82
-
83
- it 'should expand placeholders with simple fieldname' do
84
- @detail1.expand_placeholders('[int_field]').should == '1001'
85
- @detail1.expand_placeholders("[int_field]\n").should == "1001\n"
86
- @detail1.expand_placeholders('[int_field]---[int_field]').should == '1001---1001'
87
- @detail1.expand_placeholders('[int_field]---[money_field]').should == '1001---$12.34'
88
- end
83
+ context 'with direct lookup' do
89
84
 
90
- it 'should not expand placeholder for secret field' do
91
- @detail1.expand_placeholders('[secret_field]').should == 'UNKNOWN for Detail: secret_field'
92
- end
85
+ it 'should return without placeholders' do
86
+ @detail1.expand_placeholders('without placeholder').should == 'without placeholder'
87
+ @detail1.expand_placeholders('[]').should == '[]'
88
+ @detail1.expand_placeholders('').should == ''
89
+ @detail1.expand_placeholders(nil).should == nil
90
+ @detail1.expand_placeholders("\n").should == "\n"
91
+ end
93
92
 
94
- it 'should expand placeholder with namespaced fieldname' do
95
- @detail1.expand_placeholders('[master.string_field]').should == 'foo'
96
- @detail1.expand_placeholders('[master.string_field] [int_field]').should == 'foo 1001'
97
- end
93
+ it 'should expand with simple fieldname' do
94
+ @detail1.expand_placeholders('[int_field]').should == '1001'
95
+ @detail1.expand_placeholders("[int_field]\n").should == "1001\n"
96
+ @detail1.expand_placeholders('[int_field]---[int_field]--[money_field]').should == '1001---1001--12.34'
97
+ end
98
98
 
99
- it 'should expand placeholder with namespaced to self fieldname' do
100
- @detail1.expand_placeholders('[detail.int_field]').should == '1001'
101
- @detail1.expand_placeholders('[detail.master.string_field] [detail.int_field]').should == 'foo 1001'
102
- end
99
+ it 'should not parse unknown field' do
100
+ @detail1.expand_placeholders('[secret_field]').should == 'UNKNOWN for Detail: secret_field'
101
+ end
103
102
 
104
- it 'should expand with multiple steps' do
105
- @detail1.expand_placeholders('[master.side.field]').should == '123'
106
- end
103
+ it 'should expand placeholder with not existing namespaces' do
104
+ @detail1.expand_placeholders('[nothing.string_field]').should == 'UNKNOWN for Detail: nothing.string_field'
105
+ end
107
106
 
108
- it 'should expand placeholder with not existing namespaces' do
109
- @detail1.expand_placeholders('[nothing.string_field]').should == 'UNKNOWN for Detail: nothing.string_field'
107
+ it 'should expand placeholder with wrong namespaces' do
108
+ @detail1.expand_placeholders('[master.this.are.too.much.namespaces]').should == 'UNKNOWN for Master: this.are.too.much.namespaces'
109
+ @detail1.expand_placeholders('[this.are.too.much.namespaces]').should == 'UNKNOWN for Detail: this.are.too.much.namespaces'
110
+ @detail1.expand_placeholders('[...]').should == 'UNKNOWN for Detail: ...'
111
+ @detail1.expand_placeholders('[unknown]').should == 'UNKNOWN for Detail: unknown'
112
+ end
110
113
  end
111
114
 
112
- it 'should expand placeholder with wrong namespaces' do
113
- @detail1.expand_placeholders('[master.this.are.too.much.namespaces]').should == 'UNKNOWN for Master: this.are.too.much.namespaces'
114
- @detail1.expand_placeholders('[this.are.too.much.namespaces]').should == 'UNKNOWN for Detail: this.are.too.much.namespaces'
115
- @detail1.expand_placeholders('[...]').should == 'UNKNOWN for Detail: ...'
116
- @detail1.expand_placeholders('[unknown]').should == 'UNKNOWN for Detail: unknown'
117
- end
115
+ context 'with namespace' do
118
116
 
119
- it 'should expand placeholder group' do
120
- @master.expand_placeholders('[details][int_field]\n[/details]').should == '1001\n1002\n'
121
- @master.expand_placeholders('[details]Test:[int_field][/details]').should == 'Test:1001Test:1002'
122
- @master.expand_placeholders("[details][int_field]\n[/details]").should == "1001\n1002\n"
117
+ it 'should parse referenced object fields' do
118
+ @detail1.expand_placeholders('[master.string_field]').should == 'foo'
119
+ @detail1.expand_placeholders('[master.string_field] [int_field]').should == 'foo 1001'
120
+ end
123
121
 
124
- @master.expand_placeholders('[details][foo][/details]').should == 'UNKNOWN for Detail: fooUNKNOWN for Detail: foo'
125
- end
122
+ it 'should parse self referenced field' do
123
+ @detail1.expand_placeholders('[detail.int_field]').should == '1001'
124
+ @detail1.expand_placeholders('[detail.master.string_field] [detail.int_field]').should == 'foo 1001'
125
+ end
126
126
 
127
- it 'should expand valid but empty placeholder group with empty string' do
128
- master = Master.new
129
- master.details = []
130
- master.expand_placeholders('[details][int_field][/details]').should == ''
127
+ it 'should parse multiple steps' do
128
+ @detail1.expand_placeholders('[master.side.field]').should == '123'
129
+ end
131
130
  end
132
131
 
133
- it 'should expand single item from a placeholder group' do
134
- @master.details.inspect
135
- @master.expand_placeholders('[details.1.int_field]').should == '1001'
136
- @master.expand_placeholders('[details.2.int_field]').should == '1002'
132
+ context 'with empty fields' do
133
+ it 'should expand valid but empty placeholder group with empty string' do
134
+ master = Master.new
135
+ master.details = []
136
+ master.expand_placeholders('[details][int_field][/details]').should == ''
137
+ end
138
+ it 'should expand single item group with empty string' do
139
+ @master.expand_placeholders('[details.10.int_field]').should == ''
140
+ end
137
141
  end
138
142
 
139
- it 'should expand single item in empty placeholder group with empty string' do
140
- master = Master.new
141
- master.details = []
142
- master.expand_placeholders('[details.0.int_field]').should == ''
143
- end
143
+ context 'with collection' do
144
+ it 'should expand' do
145
+ @master.expand_placeholders('[details][int_field]\n[/details]').should == '1001\n1002\n'
146
+ @master.expand_placeholders('[details]Test:[int_field][/details]').should == 'Test:1001Test:1002'
147
+ @master.expand_placeholders("[details][int_field]\n[/details]").should == "1001\n1002\n"
148
+ @master.expand_placeholders('[details][foo][/details]').should == 'UNKNOWN for Detail: fooUNKNOWN for Detail: foo'
149
+ end
144
150
 
145
- it 'should expand empty single item from placeholder group with empty string' do
146
- @master.expand_placeholders('[details.10.int_field]').should == ''
147
- end
151
+ it 'should expand single item ' do
152
+ @master.expand_placeholders('[details.1.int_field]').should == '1001'
153
+ @master.expand_placeholders('[details.2.int_field]').should == '1002'
154
+ end
148
155
 
149
- it 'should expand placeholder group for non ending group' do
150
- @master.expand_placeholders('[details][int_field]').should == 'END MISSING FOR detailsUNKNOWN for Master: int_field'
156
+ it 'should show error for missing closing' do
157
+ @master.expand_placeholders('[details][int_field]').should == 'END MISSING FOR detailsUNKNOWN for Master: int_field'
158
+ end
151
159
  end
152
160
 
153
- # TODO: Make this possible!
154
- # it "should expand placeholder group if referenced with namespace" do
155
- # @master.expand_placeholders("[master.details][int_field][end]").should == "10011002"
156
- # end
161
+ context 'with custom formatter' do
162
+ before :each do
163
+ I18n.locale = :en_master
164
+ # Thread.current[:default_currency_format] = I18n.t(:'number.currency.format')
165
+ end
157
166
 
158
- it 'should expand placeholders in an array' do
159
- @detail1.expand_placeholders(['[int_field]', '[money_field]', 'static']).should == ['1001', '$12.34', 'static']
167
+ xit 'should format money' do
168
+ @record.expand_placeholders('[money_field]').should == '$12.34'
169
+ end
160
170
  end
161
171
 
162
- it 'should expand placeholders in a hash' do
163
- @detail1.expand_placeholders( :key1 => '[int_field]',
164
- :key2 => '[money_field]',
165
- :key3 => 'static'
166
- ).should ==
167
- { :key1 => '1001',
168
- :key2 => '$12.34',
169
- :key3 => 'static' }
170
- end
172
+
171
173
  end
data/spec/spec_helper.rb CHANGED
@@ -1,20 +1,16 @@
1
1
  # encoding: utf-8
2
2
  $:.unshift(File.dirname(__FILE__))
3
3
  $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
- ENV["RAILS_ENV"] ||= 'test'
5
4
 
6
5
  require 'simplecov'
7
6
  SimpleCov.start do
8
- add_filter "/json/"
7
+ #add_filter "/json/"
9
8
  end
10
9
  SimpleCov.coverage_dir 'coverage'
11
10
 
12
- require 'rubygems'
13
11
  require 'rspec'
14
12
  require 'active_record'
15
13
  require 'king_placeholder'
16
14
 
17
-
18
-
19
15
  RSpec.configure do |config|
20
16
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: king_placeholder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-28 00:00:00.000000000 Z
12
+ date: 2012-07-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: statemachine
@@ -59,22 +59,6 @@ dependencies:
59
59
  - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
- - !ruby/object:Gem::Dependency
63
- name: actionpack
64
- requirement: !ruby/object:Gem::Requirement
65
- none: false
66
- requirements:
67
- - - ! '>='
68
- - !ruby/object:Gem::Version
69
- version: '0'
70
- type: :runtime
71
- prerelease: false
72
- version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
- requirements:
75
- - - ! '>='
76
- - !ruby/object:Gem::Version
77
- version: '0'
78
62
  - !ruby/object:Gem::Dependency
79
63
  name: activerecord
80
64
  requirement: !ruby/object:Gem::Requirement
@@ -189,7 +173,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
189
173
  version: '0'
190
174
  segments:
191
175
  - 0
192
- hash: 2706534140845333133
176
+ hash: 3312225675603911462
193
177
  required_rubygems_version: !ruby/object:Gem::Requirement
194
178
  none: false
195
179
  requirements:
@@ -198,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
198
182
  version: '0'
199
183
  segments:
200
184
  - 0
201
- hash: 2706534140845333133
185
+ hash: 3312225675603911462
202
186
  requirements: []
203
187
  rubyforge_project:
204
188
  rubygems_version: 1.8.24