king_placeholder 0.0.2
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.
- data/.gitignore +17 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.rdoc +63 -0
- data/Rakefile +9 -0
- data/king_placeholder.gemspec +30 -0
- data/lib/king_placeholder/parse_context.rb +207 -0
- data/lib/king_placeholder/version.rb +3 -0
- data/lib/king_placeholder.rb +116 -0
- data/spec/king_placeholder_spec.rb +173 -0
- data/spec/spec_helper.rb +20 -0
- metadata +208 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Georg Leciejewski
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
= KingPlaceholder
|
2
|
+
|
3
|
+
This gem was extraced from SalesKing where it handles placeholders substitution
|
4
|
+
for user supplied email-, text- and export- templates.
|
5
|
+
|
6
|
+
Define the available methods in your class
|
7
|
+
|
8
|
+
class User
|
9
|
+
include KingPlaceholder
|
10
|
+
has_many :comments
|
11
|
+
has_placeholders :firstname
|
12
|
+
end
|
13
|
+
class Comment
|
14
|
+
include KingPlaceholder
|
15
|
+
has_placeholders :text
|
16
|
+
end
|
17
|
+
|
18
|
+
Use placeholder names in square brackets:
|
19
|
+
|
20
|
+
@user.expand_placeholders("Hello [user.first_name]")
|
21
|
+
=> Hello Schorsch
|
22
|
+
|
23
|
+
|
24
|
+
It can also handle relations and collections
|
25
|
+
|
26
|
+
@user.expand_placeholders("[comments][text][/comments]")
|
27
|
+
=> All comment texts
|
28
|
+
|
29
|
+
@user.expand_placeholders("[comments.1.text]")
|
30
|
+
=> First comment text
|
31
|
+
|
32
|
+
|
33
|
+
== TODO
|
34
|
+
|
35
|
+
This gems still relies on king_views with king_format, for money, date
|
36
|
+
formatting. We will outsource king_format into its own gem and remove more
|
37
|
+
SalesKing internal dependencies.
|
38
|
+
|
39
|
+
== Installation
|
40
|
+
|
41
|
+
Add this line to your application's Gemfile:
|
42
|
+
|
43
|
+
gem 'king_placeholder'
|
44
|
+
|
45
|
+
And then execute:
|
46
|
+
|
47
|
+
$ bundle
|
48
|
+
|
49
|
+
Or install it yourself as:
|
50
|
+
|
51
|
+
$ gem install king_placeholder
|
52
|
+
|
53
|
+
== Usage
|
54
|
+
|
55
|
+
See specs
|
56
|
+
|
57
|
+
== Contributing
|
58
|
+
|
59
|
+
1. Fork it
|
60
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
61
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
62
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
63
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/king_placeholder/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Georg Leciejewski"]
|
6
|
+
gem.email = ["gl@salesking.eu"]
|
7
|
+
gem.description = %q{}
|
8
|
+
gem.summary = %q{Placeholder Parsing in Strings}
|
9
|
+
gem.homepage = 'https://github.com/salesking/king_placeholder.git'
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "king_placeholder"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = KingPlaceholder::VERSION
|
17
|
+
|
18
|
+
gem.add_runtime_dependency 'statemachine'
|
19
|
+
gem.add_runtime_dependency 'i18n'
|
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
|
+
|
25
|
+
gem.add_development_dependency 'activerecord'
|
26
|
+
gem.add_development_dependency 'rdoc'
|
27
|
+
gem.add_development_dependency 'rspec'
|
28
|
+
gem.add_development_dependency 'simplecov'
|
29
|
+
gem.add_development_dependency 'rake', '>= 0.9.2'
|
30
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'statemachine'
|
2
|
+
require 'action_view' # king_views related suxs
|
3
|
+
require 'action_controller' # king_views
|
4
|
+
require 'king_views'
|
5
|
+
module KingPlaceholder
|
6
|
+
# Statemachine for placeholder substitution
|
7
|
+
# The statemachine is created and its state updated from within the Context
|
8
|
+
# object. This only holds the state definitions.
|
9
|
+
module ParseMachine
|
10
|
+
|
11
|
+
def self.create_with(machine_context)
|
12
|
+
return Statemachine.build do
|
13
|
+
# Origin State Event Destination State Action
|
14
|
+
# FROM EVENT TO ACTION
|
15
|
+
trans :waiting, :match, :matching, :prepare
|
16
|
+
state :matching do
|
17
|
+
on_entry :parse
|
18
|
+
end
|
19
|
+
trans :matching, :finished_matching, :finished, :cleanup
|
20
|
+
context machine_context
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
# Statemachine context for placeholder substitution
|
27
|
+
#
|
28
|
+
# === Example
|
29
|
+
# machine = ParserContext.new(obj, content)
|
30
|
+
# # send match event
|
31
|
+
# machine.sm.match
|
32
|
+
# machine.result
|
33
|
+
class ParseContext
|
34
|
+
include ::KingFormat::FormattingHelper
|
35
|
+
# reference to statemachine
|
36
|
+
attr_accessor :sm
|
37
|
+
# incoming string
|
38
|
+
attr_accessor :content
|
39
|
+
# Output string, with placeholders substituted
|
40
|
+
attr_accessor :result
|
41
|
+
# the current object
|
42
|
+
attr_accessor :obj
|
43
|
+
# hash parse options
|
44
|
+
attr_accessor :opts
|
45
|
+
|
46
|
+
# === Parameter
|
47
|
+
# obj<Object>:: any object responding to has_placeholder
|
48
|
+
# content<String>:: String containing placeholders
|
49
|
+
# opts<Hash{Symbol=>Mixed}>:: parse options
|
50
|
+
# :only<Array> => parse only the given placeholders
|
51
|
+
def initialize(obj, content, opts={})
|
52
|
+
@sm = ParseMachine.create_with(self) # init statemachine
|
53
|
+
@obj = obj
|
54
|
+
@content = content
|
55
|
+
@opts = opts
|
56
|
+
end
|
57
|
+
|
58
|
+
# Before matching is done this method set's up environment variables like
|
59
|
+
# current_language => i18n.locale
|
60
|
+
# TODO: decouple and outsource into before_method block to set env vars from outside
|
61
|
+
def prepare
|
62
|
+
init_locale #I18n.locale
|
63
|
+
set_format_opts # merge date/money format into thread var
|
64
|
+
# deep copy content, because otherwise result.gsub! breaks recursion
|
65
|
+
@result = @content.dup
|
66
|
+
end
|
67
|
+
|
68
|
+
# Match all placeholder(inside the brackets []) and replace them with their
|
69
|
+
# values
|
70
|
+
# See #expand_placeholders for docs
|
71
|
+
# When finished matching, triggers finished_matching event on statemachine
|
72
|
+
def parse
|
73
|
+
while match = @result.match(/\[((\w|\.)+)\]/)
|
74
|
+
@c_placeholder = match[0] # with brackets - current placeholder
|
75
|
+
@c_field = match[1] # without brackets - current field
|
76
|
+
|
77
|
+
check_current_prefix
|
78
|
+
|
79
|
+
if @c_field['.']
|
80
|
+
sub_object
|
81
|
+
elsif obj.respond_to?(@c_field) && ( @cur_collection = obj.send(@c_field) ).is_a?(Array)
|
82
|
+
sub_collection
|
83
|
+
else
|
84
|
+
sub_string
|
85
|
+
end
|
86
|
+
# If placeholder still exists here, it can't be recognized, sub with error
|
87
|
+
@result.gsub!(@c_placeholder, "UNKNOWN for #{obj.class.to_s}: #{@c_field}")
|
88
|
+
end
|
89
|
+
@sm.finished_matching
|
90
|
+
end
|
91
|
+
|
92
|
+
# When finished this method is called to cleanup f.ex. environment variables
|
93
|
+
# like I18n.locale
|
94
|
+
def cleanup
|
95
|
+
@current_language && @current_language.reset_locale
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# Final destination of each placeholder in it's simple notation without
|
101
|
+
# namespace e.g. [price_to_pay]
|
102
|
+
def sub_string
|
103
|
+
return unless obj.is_placeholder?(@c_field)
|
104
|
+
# only parse some placeholders
|
105
|
+
# return if opts[:only] && !opts[:only].include?(@c_field)
|
106
|
+
value = strfval(obj, @c_field)
|
107
|
+
@result.gsub!(@c_placeholder, value.to_s) if value
|
108
|
+
end
|
109
|
+
|
110
|
+
# Namespaced notation, for related objects. We are in invoice which belongs
|
111
|
+
# to a company:
|
112
|
+
# [company.default_address.zip]
|
113
|
+
# instead of
|
114
|
+
# [invoice.company.default_address.zip]
|
115
|
+
def sub_object
|
116
|
+
splitted = @c_field.split('.')
|
117
|
+
object_name = splitted.first # just the first
|
118
|
+
field_names = splitted[1..-1] # all but the first
|
119
|
+
return unless object_name && obj.respond_to?(object_name) # the field exists
|
120
|
+
object = obj.send(object_name)
|
121
|
+
#check if its a collection => invoice.items and access is done by ary index items.0.name
|
122
|
+
if object.is_a?(Array) && ary_index = field_names.first[/\A\d*\z/] # [items.5.name] match digit only string
|
123
|
+
field_names.delete_at(0) # remove entry from field_names ary
|
124
|
+
# replace with empty string if the index does not exist or obj is empty
|
125
|
+
@result.gsub!(@c_placeholder, '') unless object = object[ary_index.to_i-1]
|
126
|
+
end
|
127
|
+
|
128
|
+
# Recurse and Let the referenced object do the expanding
|
129
|
+
if object.respond_to?(:expand_placeholders)
|
130
|
+
value = object.expand_placeholders("[#{field_names.join('.')}]")
|
131
|
+
@result.gsub!(@c_placeholder, value)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# The current field is the beginning of a group, which needs to be ended by
|
136
|
+
# [/field_name]
|
137
|
+
# e.g. [items] Item price: [price] \n [/items]
|
138
|
+
def sub_collection
|
139
|
+
if match = @result.match(/\[#{@c_field}\](.*)\[\/#{@c_field}\]/m) # the /m makes the dot match newlines, too!
|
140
|
+
whole_group = match[0]
|
141
|
+
inner_placeholders = match[1]
|
142
|
+
inner_result = ''
|
143
|
+
# Let the referenced object do the expanding by recursion if the collection knowns placeholders
|
144
|
+
@cur_collection.each do |item|
|
145
|
+
inner_result << item.expand_placeholders(inner_placeholders)
|
146
|
+
end if @cur_collection.first.respond_to?(:expand_placeholders)
|
147
|
+
@result.gsub!(whole_group, inner_result)
|
148
|
+
else
|
149
|
+
@result.gsub!(@c_placeholder, "END MISSING FOR #{@c_field}")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Checks if the current field name contains the current object's class name.
|
154
|
+
# If the current class is an invoice, kick the prefix:
|
155
|
+
# invoice.number => number
|
156
|
+
def check_current_prefix
|
157
|
+
if @c_field['.']
|
158
|
+
splitted = @c_field.split('.')
|
159
|
+
object_name = splitted.first
|
160
|
+
if object_name && obj.class.name == object_name.classify
|
161
|
+
splitted.delete_at(0) # kick the object portion => invoice
|
162
|
+
@c_field = splitted.join('.') # glue the rest back together [number]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# set format options for KingFormat date/money helpers
|
168
|
+
# => strfval + strfmoney
|
169
|
+
# very important, without those all formatting in views screws up
|
170
|
+
def set_format_opts
|
171
|
+
Thread.current[:default_currency_format] = if defined?(::Company) && ::Company.current
|
172
|
+
::Company.current.money_format
|
173
|
+
elsif obj.respond_to?(:company) && obj.company
|
174
|
+
obj.company.money_format
|
175
|
+
elsif defined?(::Company) && obj.is_a?(::Company)
|
176
|
+
obj.money_format
|
177
|
+
else
|
178
|
+
nil
|
179
|
+
end
|
180
|
+
|
181
|
+
Thread.current[:default_date_format] = if defined?(::Company) && ::Company.current
|
182
|
+
::Company.current.date_format
|
183
|
+
elsif obj.respond_to?(:company) && obj.company
|
184
|
+
obj.company.date_format
|
185
|
+
elsif defined?(::Company) && obj.is_a?(::Company)
|
186
|
+
obj.date_format
|
187
|
+
else
|
188
|
+
nil
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Set i18n.locale to given or present custom company locale
|
193
|
+
# locale is memoized in instance @current_language.
|
194
|
+
# Only set if a company and the language is available
|
195
|
+
# === Parameter
|
196
|
+
# locale<String>:: locale code en, de,..
|
197
|
+
def init_locale(locale=nil)
|
198
|
+
if (locale || (obj.respond_to?(:language) && obj.language)) \
|
199
|
+
&& obj.respond_to?(:company) && obj.company
|
200
|
+
@current_language ||= begin
|
201
|
+
# find lang in scope of company
|
202
|
+
::Language.init_locale(obj.company, locale || obj.language)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end #class
|
207
|
+
end #module
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'king_placeholder/parse_context'
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/version'
|
4
|
+
|
5
|
+
# Define fields/methods of the including class as placeholders.
|
6
|
+
# A Placeholder can be used inside any text string and will be replaced with a
|
7
|
+
# stringified, formatted value(by KingViews gem => KingFormat::FormattingHelper.strfval )
|
8
|
+
# Used for text snippets, PDF creation or E-Mail templates.
|
9
|
+
|
10
|
+
module KingPlaceholder
|
11
|
+
|
12
|
+
# sets :placeholders and init Class.placeholders as emtpy array on inclusion
|
13
|
+
def self.included(base)
|
14
|
+
if ActiveSupport::VERSION::MAJOR == 3 && ActiveSupport::VERSION::MINOR > 0
|
15
|
+
base.class_attribute :placeholders
|
16
|
+
else
|
17
|
+
base.send :class_inheritable_accessor, :placeholders
|
18
|
+
end
|
19
|
+
base.placeholders = []
|
20
|
+
base.extend(ClassMethods)
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
|
25
|
+
# Defines the fields returned by self.placeholders.
|
26
|
+
# Sets self.publish if empty.
|
27
|
+
# ==== Parameter
|
28
|
+
# fieldnames<Array[Symbol]>:: the names of the fields which are available
|
29
|
+
# throught the placeholder methods
|
30
|
+
def has_placeholders(*fieldnames)
|
31
|
+
self.placeholders = fieldnames
|
32
|
+
include InstanceMethods
|
33
|
+
end
|
34
|
+
end #ClassMethods
|
35
|
+
|
36
|
+
module InstanceMethods
|
37
|
+
|
38
|
+
# Check if a given field is declared as placeholder
|
39
|
+
# TODO check usage and/or move to class methods
|
40
|
+
# ==== Parameter
|
41
|
+
# fieldname<String|Symbol>:: the field to search in the placeholders array
|
42
|
+
# ==== Returns
|
43
|
+
# <Boolean>:: true if in
|
44
|
+
def is_placeholder?(fieldname)
|
45
|
+
self.class.placeholders.include?(fieldname.to_sym)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Substitute placeholder in a string with their current values.
|
49
|
+
# It handles strings, arrays (of strings) or hashes (with string values)
|
50
|
+
# and returns data with the same data type e.g. if you put a hash, you will
|
51
|
+
# get a hash.
|
52
|
+
#
|
53
|
+
# ==== Examples
|
54
|
+
#
|
55
|
+
# Placeholders in text strings can be written in different notations.
|
56
|
+
#
|
57
|
+
# ===== Simple Notation:
|
58
|
+
#
|
59
|
+
# => [first_name]
|
60
|
+
# The fieldname is directly looked up on the current class:
|
61
|
+
# client.expand_placeholders("Hello [first_name]")
|
62
|
+
# => "Hello Herbert"
|
63
|
+
# invoice.expand_placeholders(["You owe me [price_to_pay]", "Invoice Nr. [number]"])
|
64
|
+
# => ["You owe me 495,00 EUR", "Invoice Nr. 123"]
|
65
|
+
#
|
66
|
+
# ===== Namespaced Notation
|
67
|
+
#
|
68
|
+
# => [company.organisation]
|
69
|
+
# If the prefix equals the type of the current object the field is looked up on it.
|
70
|
+
# client.expand_placeholders("Hello [client.first_name]")
|
71
|
+
# => "Hello Herbert"
|
72
|
+
#
|
73
|
+
# If the prefix is a single related object => Client :belongs_to Company,
|
74
|
+
# the substitution is delegated to that class.
|
75
|
+
# client.expand_placeholders("Belongs to [company.name]")
|
76
|
+
# => ""Belongs to Big Money Coorp."
|
77
|
+
# It goes down all the way:
|
78
|
+
# invoice.expand_placeholders("[client.company.default_address.zip]")
|
79
|
+
# => "50999"
|
80
|
+
#
|
81
|
+
# ===== Namespaced Notation with multiple related objects
|
82
|
+
#
|
83
|
+
# In a has_many relationship, related objects reside in an array, which can
|
84
|
+
# be reached using two different strategies.
|
85
|
+
#
|
86
|
+
# Access and Iterate over the whole Group:
|
87
|
+
# invoice.expand_placeholders("You bought: [items] [name] [/items]")
|
88
|
+
# => "You bought: Apple Banana Orange"
|
89
|
+
#
|
90
|
+
# Access a single object by its array index:
|
91
|
+
# invoice.expand_placeholders("You bought an [items.0.name] for [items.0.price]")
|
92
|
+
# => "You bought an Apple for 12 EUR"
|
93
|
+
#
|
94
|
+
# === Parameter
|
95
|
+
# content<Hash,Array, String>:: Collection, Hash or single string containing
|
96
|
+
# placeholders
|
97
|
+
# === Returns
|
98
|
+
# <Hash,Array, String>:: whatever type you threw in is also returned
|
99
|
+
def expand_placeholders(content, opts={})
|
100
|
+
if content.is_a?(Array) # Expand all array elements and go recursive
|
101
|
+
result = []
|
102
|
+
content.each{|element| result << self.expand_placeholders(element, opts) }
|
103
|
+
return result
|
104
|
+
elsif content.is_a?(Hash) # Expand all hash elements and go recursive
|
105
|
+
result = {}
|
106
|
+
content.each_pair{ |key,val| result[key] = self.expand_placeholders(val, opts) }
|
107
|
+
return result
|
108
|
+
else # Only proceed with strings
|
109
|
+
return content unless content.is_a?(String)
|
110
|
+
end
|
111
|
+
parser = KingPlaceholder::ParseContext.new(self, content, opts)
|
112
|
+
parser.sm.match
|
113
|
+
parser.result if parser.sm.state == :finished
|
114
|
+
end
|
115
|
+
end # instance methods
|
116
|
+
end # KingPlaceholders
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class BaseModel
|
4
|
+
# include KingFormat::FormattingHelper
|
5
|
+
include KingPlaceholder
|
6
|
+
end
|
7
|
+
|
8
|
+
# Construct dummy models
|
9
|
+
class Master < BaseModel
|
10
|
+
attr_accessor :string_field
|
11
|
+
attr_accessor :details
|
12
|
+
attr_accessor :side
|
13
|
+
has_placeholders :string_field
|
14
|
+
end
|
15
|
+
|
16
|
+
class Side < BaseModel
|
17
|
+
attr_accessor :field
|
18
|
+
attr_accessor :master
|
19
|
+
has_placeholders :field
|
20
|
+
end
|
21
|
+
|
22
|
+
class Detail < BaseModel
|
23
|
+
include KingFormat::MoneyFields
|
24
|
+
attr_accessor :int_field, :money_field, :secret_field, :currency
|
25
|
+
attr_accessor :master
|
26
|
+
has_money_fields :money_field
|
27
|
+
has_placeholders :int_field, :money_field
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'Class with placeholders' do
|
31
|
+
|
32
|
+
before :each do
|
33
|
+
I18n.locale = :en_master
|
34
|
+
# Thread.current[:default_currency_format] = I18n.t(:'number.currency.format')
|
35
|
+
@record = Detail.new
|
36
|
+
@record.int_field = 1000
|
37
|
+
@record.money_field = 12.34
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should have native values' do
|
41
|
+
@record.int_field.should == 1000
|
42
|
+
@record.money_field.should == 12.34
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should have placeholder values' do
|
46
|
+
@record.expand_placeholders('[int_field]').should == '1000'
|
47
|
+
@record.expand_placeholders('[money_field]').should == '$12.34'
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
describe 'Expanding of strings containing placeholder' do
|
53
|
+
|
54
|
+
before :each do
|
55
|
+
I18n.locale = :en_master
|
56
|
+
@side = Side.new
|
57
|
+
@side.field = 123
|
58
|
+
@master = Master.new
|
59
|
+
@master.string_field = 'foo'
|
60
|
+
@master.side = @side
|
61
|
+
@side.master = @master
|
62
|
+
|
63
|
+
@detail1 = Detail.new
|
64
|
+
@detail1.int_field = 1001
|
65
|
+
@detail1.money_field = 12.34
|
66
|
+
@detail1.secret_field = 'top-secret'
|
67
|
+
@detail1.master = @master
|
68
|
+
|
69
|
+
@detail2 = Detail.new
|
70
|
+
@detail2.int_field = 1002
|
71
|
+
@detail2.money_field = 45.67
|
72
|
+
@detail2.secret_field = 'little secret'
|
73
|
+
@detail2.master = @master
|
74
|
+
@master.details = [@detail1, @detail2]
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should expand placeholders with no placeholders there' do
|
78
|
+
@detail1.expand_placeholders('without placeholder').should == 'without placeholder'
|
79
|
+
@detail1.expand_placeholders('[]').should == '[]'
|
80
|
+
@detail1.expand_placeholders('').should == ''
|
81
|
+
@detail1.expand_placeholders(nil).should == nil
|
82
|
+
@detail1.expand_placeholders("\n").should == "\n"
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should expand placeholders with simple fieldname' do
|
86
|
+
@detail1.expand_placeholders('[int_field]').should == '1001'
|
87
|
+
@detail1.expand_placeholders("[int_field]\n").should == "1001\n"
|
88
|
+
@detail1.expand_placeholders('[int_field]---[int_field]').should == '1001---1001'
|
89
|
+
@detail1.expand_placeholders('[int_field]---[money_field]').should == '1001---$12.34'
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should not expand placeholder for secret field' do
|
93
|
+
@detail1.expand_placeholders('[secret_field]').should == 'UNKNOWN for Detail: secret_field'
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should expand placeholder with namespaced fieldname' do
|
97
|
+
@detail1.expand_placeholders('[master.string_field]').should == 'foo'
|
98
|
+
@detail1.expand_placeholders('[master.string_field] [int_field]').should == 'foo 1001'
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should expand placeholder with namespaced to self fieldname' do
|
102
|
+
@detail1.expand_placeholders('[detail.int_field]').should == '1001'
|
103
|
+
@detail1.expand_placeholders('[detail.master.string_field] [detail.int_field]').should == 'foo 1001'
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should expand with multiple steps' do
|
107
|
+
@detail1.expand_placeholders('[master.side.field]').should == '123'
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should expand placeholder with not existing namespaces' do
|
111
|
+
@detail1.expand_placeholders('[nothing.string_field]').should == 'UNKNOWN for Detail: nothing.string_field'
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should expand placeholder with wrong namespaces' do
|
115
|
+
@detail1.expand_placeholders('[master.this.are.too.much.namespaces]').should == 'UNKNOWN for Master: this.are.too.much.namespaces'
|
116
|
+
@detail1.expand_placeholders('[this.are.too.much.namespaces]').should == 'UNKNOWN for Detail: this.are.too.much.namespaces'
|
117
|
+
@detail1.expand_placeholders('[...]').should == 'UNKNOWN for Detail: ...'
|
118
|
+
@detail1.expand_placeholders('[unknown]').should == 'UNKNOWN for Detail: unknown'
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should expand placeholder group' do
|
122
|
+
@master.expand_placeholders('[details][int_field]\n[/details]').should == '1001\n1002\n'
|
123
|
+
@master.expand_placeholders('[details]Test:[int_field][/details]').should == 'Test:1001Test:1002'
|
124
|
+
@master.expand_placeholders("[details][int_field]\n[/details]").should == "1001\n1002\n"
|
125
|
+
|
126
|
+
@master.expand_placeholders('[details][foo][/details]').should == 'UNKNOWN for Detail: fooUNKNOWN for Detail: foo'
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should expand valid but empty placeholder group with empty string' do
|
130
|
+
master = Master.new
|
131
|
+
master.details = []
|
132
|
+
master.expand_placeholders('[details][int_field][/details]').should == ''
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'should expand single item from a placeholder group' do
|
136
|
+
@master.details.inspect
|
137
|
+
@master.expand_placeholders('[details.1.int_field]').should == '1001'
|
138
|
+
@master.expand_placeholders('[details.2.int_field]').should == '1002'
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'should expand single item in empty placeholder group with empty string' do
|
142
|
+
master = Master.new
|
143
|
+
master.details = []
|
144
|
+
master.expand_placeholders('[details.0.int_field]').should == ''
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'should expand empty single item from placeholder group with empty string' do
|
148
|
+
@master.expand_placeholders('[details.10.int_field]').should == ''
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'should expand placeholder group for non ending group' do
|
152
|
+
@master.expand_placeholders('[details][int_field]').should == 'END MISSING FOR detailsUNKNOWN for Master: int_field'
|
153
|
+
end
|
154
|
+
|
155
|
+
# TODO: Make this possible!
|
156
|
+
# it "should expand placeholder group if referenced with namespace" do
|
157
|
+
# @master.expand_placeholders("[master.details][int_field][end]").should == "10011002"
|
158
|
+
# end
|
159
|
+
|
160
|
+
it 'should expand placeholders in an array' do
|
161
|
+
@detail1.expand_placeholders(['[int_field]', '[money_field]', 'static']).should == ['1001', '$12.34', 'static']
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'should expand placeholders in a hash' do
|
165
|
+
@detail1.expand_placeholders( :key1 => '[int_field]',
|
166
|
+
:key2 => '[money_field]',
|
167
|
+
:key3 => 'static'
|
168
|
+
).should ==
|
169
|
+
{ :key1 => '1001',
|
170
|
+
:key2 => '$12.34',
|
171
|
+
:key3 => 'static' }
|
172
|
+
end
|
173
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
$:.unshift(File.dirname(__FILE__))
|
3
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
|
+
ENV["RAILS_ENV"] ||= 'test'
|
5
|
+
|
6
|
+
require 'simplecov'
|
7
|
+
SimpleCov.start do
|
8
|
+
add_filter "/json/"
|
9
|
+
end
|
10
|
+
SimpleCov.coverage_dir 'coverage'
|
11
|
+
|
12
|
+
require 'rubygems'
|
13
|
+
require 'rspec'
|
14
|
+
require 'active_record'
|
15
|
+
require 'king_placeholder'
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
RSpec.configure do |config|
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: king_placeholder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Georg Leciejewski
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: statemachine
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: i18n
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: activesupport
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
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
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: activerecord
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rdoc
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rspec
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: simplecov
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: rake
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: 0.9.2
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: 0.9.2
|
158
|
+
description: ''
|
159
|
+
email:
|
160
|
+
- gl@salesking.eu
|
161
|
+
executables: []
|
162
|
+
extensions: []
|
163
|
+
extra_rdoc_files: []
|
164
|
+
files:
|
165
|
+
- .gitignore
|
166
|
+
- Gemfile
|
167
|
+
- LICENSE
|
168
|
+
- README.rdoc
|
169
|
+
- Rakefile
|
170
|
+
- king_placeholder.gemspec
|
171
|
+
- lib/king_placeholder.rb
|
172
|
+
- lib/king_placeholder/parse_context.rb
|
173
|
+
- lib/king_placeholder/version.rb
|
174
|
+
- spec/king_placeholder_spec.rb
|
175
|
+
- spec/spec_helper.rb
|
176
|
+
homepage: https://github.com/salesking/king_placeholder.git
|
177
|
+
licenses: []
|
178
|
+
post_install_message:
|
179
|
+
rdoc_options: []
|
180
|
+
require_paths:
|
181
|
+
- lib
|
182
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
183
|
+
none: false
|
184
|
+
requirements:
|
185
|
+
- - ! '>='
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
segments:
|
189
|
+
- 0
|
190
|
+
hash: 2667406904980012047
|
191
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
192
|
+
none: false
|
193
|
+
requirements:
|
194
|
+
- - ! '>='
|
195
|
+
- !ruby/object:Gem::Version
|
196
|
+
version: '0'
|
197
|
+
segments:
|
198
|
+
- 0
|
199
|
+
hash: 2667406904980012047
|
200
|
+
requirements: []
|
201
|
+
rubyforge_project:
|
202
|
+
rubygems_version: 1.8.24
|
203
|
+
signing_key:
|
204
|
+
specification_version: 3
|
205
|
+
summary: Placeholder Parsing in Strings
|
206
|
+
test_files:
|
207
|
+
- spec/king_placeholder_spec.rb
|
208
|
+
- spec/spec_helper.rb
|