king_placeholder 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +5 -0
- data/README.rdoc +25 -18
- data/lib/king_placeholder/{parse_context.rb → parser.rb} +38 -40
- data/lib/king_placeholder/version.rb +1 -1
- data/lib/king_placeholder.rb +15 -25
- data/placeholder_state.dia +0 -0
- data/spec/king_placeholder_spec.rb +6 -8
- metadata +7 -5
data/README.rdoc
CHANGED
@@ -1,15 +1,24 @@
|
|
1
1
|
= KingPlaceholder
|
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
|
+
This gem was extracted from SalesKing, where it does placeholders substitution
|
4
|
+
for user supplied strings in Email-, Text- and Export-Templates.
|
2
5
|
|
3
|
-
|
4
|
-
|
6
|
+
Placeholders are declared in each class and afterwards any strings containing
|
7
|
+
[placeholders] can be parsed in the scope of the model.
|
5
8
|
|
6
|
-
|
9
|
+
Parsing is done by a simple statemachine using regex for placeholder detection.
|
10
|
+
|
11
|
+
== Usage
|
12
|
+
|
13
|
+
Define the available methods in your class with 'has_placeholders'
|
7
14
|
|
8
15
|
class User
|
9
16
|
include KingPlaceholder
|
10
17
|
has_many :comments
|
11
|
-
|
18
|
+
has_one :company
|
19
|
+
has_placeholders :firstname, :email
|
12
20
|
end
|
21
|
+
|
13
22
|
class Comment
|
14
23
|
include KingPlaceholder
|
15
24
|
has_placeholders :text
|
@@ -17,11 +26,11 @@ Define the available methods in your class
|
|
17
26
|
|
18
27
|
Use placeholder names in square brackets:
|
19
28
|
|
20
|
-
@user.
|
21
|
-
|
22
|
-
|
29
|
+
@user = User.new( name: "Schorsch", email: 'a@b.com')
|
30
|
+
@user.expand_placeholders("Hey [name] your address is [email]")
|
31
|
+
=> "Hey Schorsch your address is a@b.com"
|
23
32
|
|
24
|
-
|
33
|
+
Handle collections
|
25
34
|
|
26
35
|
@user.expand_placeholders("[comments][text][/comments]")
|
27
36
|
=> All comment texts
|
@@ -29,12 +38,18 @@ It can also handle relations and collections
|
|
29
38
|
@user.expand_placeholders("[comments.1.text]")
|
30
39
|
=> First comment text
|
31
40
|
|
41
|
+
Handle relations
|
42
|
+
|
43
|
+
@user.expand_placeholders("[company.name]")
|
44
|
+
=> MyCompany
|
45
|
+
|
46
|
+
Also see specs
|
32
47
|
|
33
48
|
== TODO
|
34
49
|
|
35
|
-
This gems still relies on king_views with king_format, for money, date
|
50
|
+
This gems still relies on gem king_views with king_format, for money, date
|
36
51
|
formatting. We will outsource king_format into its own gem and remove more
|
37
|
-
SalesKing internal dependencies.
|
52
|
+
Rails and SalesKing internal dependencies.
|
38
53
|
|
39
54
|
== Installation
|
40
55
|
|
@@ -42,18 +57,10 @@ Add this line to your application's Gemfile:
|
|
42
57
|
|
43
58
|
gem 'king_placeholder'
|
44
59
|
|
45
|
-
And then execute:
|
46
|
-
|
47
|
-
$ bundle
|
48
|
-
|
49
60
|
Or install it yourself as:
|
50
61
|
|
51
62
|
$ gem install king_placeholder
|
52
63
|
|
53
|
-
== Usage
|
54
|
-
|
55
|
-
See specs
|
56
|
-
|
57
64
|
== Contributing
|
58
65
|
|
59
66
|
1. Fork it
|
@@ -2,6 +2,7 @@ require 'statemachine'
|
|
2
2
|
require 'action_view' # king_views related suxs
|
3
3
|
require 'action_controller' # king_views
|
4
4
|
require 'king_views'
|
5
|
+
|
5
6
|
module KingPlaceholder
|
6
7
|
# Statemachine for placeholder substitution
|
7
8
|
# The statemachine is created and its state updated from within the Context
|
@@ -20,7 +21,6 @@ module KingPlaceholder
|
|
20
21
|
context machine_context
|
21
22
|
end
|
22
23
|
end
|
23
|
-
|
24
24
|
end
|
25
25
|
|
26
26
|
# Statemachine context for placeholder substitution
|
@@ -30,7 +30,7 @@ module KingPlaceholder
|
|
30
30
|
# # send match event
|
31
31
|
# machine.sm.match
|
32
32
|
# machine.result
|
33
|
-
class
|
33
|
+
class Parser
|
34
34
|
include ::KingFormat::FormattingHelper
|
35
35
|
# reference to statemachine
|
36
36
|
attr_accessor :sm
|
@@ -40,14 +40,12 @@ module KingPlaceholder
|
|
40
40
|
attr_accessor :result
|
41
41
|
# the current object
|
42
42
|
attr_accessor :obj
|
43
|
-
#
|
43
|
+
# parse-options hash
|
44
44
|
attr_accessor :opts
|
45
45
|
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
# opts<Hash{Symbol=>Mixed}>:: parse options
|
50
|
-
# :only<Array> => parse only the given placeholders
|
46
|
+
# @param [Object] obj object responding to has_placeholder
|
47
|
+
# @param [String] content containing placeholders
|
48
|
+
# @param [Hash{Symbol=>Mixed}] opts parser options (none so far)
|
51
49
|
def initialize(obj, content, opts={})
|
52
50
|
@sm = ParseMachine.create_with(self) # init statemachine
|
53
51
|
@obj = obj
|
@@ -71,20 +69,20 @@ module KingPlaceholder
|
|
71
69
|
# When finished matching, triggers finished_matching event on statemachine
|
72
70
|
def parse
|
73
71
|
while match = @result.match(/\[((\w|\.)+)\]/)
|
74
|
-
@
|
75
|
-
@
|
72
|
+
@placeholder = match[0] # with brackets - current placeholder
|
73
|
+
@field = match[1] # without brackets - current field
|
76
74
|
|
77
75
|
check_current_prefix
|
78
76
|
|
79
|
-
if @
|
77
|
+
if @field['.']
|
80
78
|
sub_object
|
81
|
-
elsif obj.respond_to?(@
|
79
|
+
elsif obj.respond_to?(@field) && ( @cur_collection = obj.send(@field) ).is_a?(Array)
|
82
80
|
sub_collection
|
83
81
|
else
|
84
82
|
sub_string
|
85
83
|
end
|
86
84
|
# If placeholder still exists here, it can't be recognized, sub with error
|
87
|
-
@result.gsub!(@
|
85
|
+
@result.gsub!(@placeholder, "UNKNOWN for #{obj.class.to_s}: #{@field}")
|
88
86
|
end
|
89
87
|
@sm.finished_matching
|
90
88
|
end
|
@@ -100,35 +98,34 @@ module KingPlaceholder
|
|
100
98
|
# Final destination of each placeholder in it's simple notation without
|
101
99
|
# namespace e.g. [price_to_pay]
|
102
100
|
def sub_string
|
103
|
-
return unless obj.is_placeholder?(@
|
104
|
-
|
105
|
-
|
106
|
-
value = strfval(obj, @c_field)
|
107
|
-
@result.gsub!(@c_placeholder, value.to_s) if value
|
101
|
+
return unless obj.is_placeholder?(@field)
|
102
|
+
value = strfval(obj, @field)
|
103
|
+
@result.gsub!(@placeholder, value.to_s) if value
|
108
104
|
end
|
109
105
|
|
110
|
-
# Namespaced notation, for related objects.
|
111
|
-
#
|
106
|
+
# Namespaced notation, for related objects. E.g an invoice belonging to a
|
107
|
+
# company:
|
112
108
|
# [company.default_address.zip]
|
113
109
|
# instead of
|
114
110
|
# [invoice.company.default_address.zip]
|
115
111
|
def sub_object
|
116
|
-
|
117
|
-
object_name =
|
118
|
-
field_names =
|
119
|
-
return unless object_name && obj.respond_to?(object_name)
|
112
|
+
splits= @field.split('.')
|
113
|
+
object_name = splits.first
|
114
|
+
field_names = splits[1..-1] # all but the first
|
115
|
+
return unless object_name && obj.respond_to?(object_name)
|
120
116
|
object = obj.send(object_name)
|
121
|
-
#
|
122
|
-
|
117
|
+
# Its a collection => invoice.items and access is done by ary index:
|
118
|
+
# first item => [items.1.name]
|
119
|
+
if object.is_a?(Array) && ary_index = field_names.first[/\A\d*\z/]
|
123
120
|
field_names.delete_at(0) # remove entry from field_names ary
|
124
121
|
# replace with empty string if the index does not exist or obj is empty
|
125
|
-
@result.gsub!(@
|
122
|
+
@result.gsub!(@placeholder, '') unless object = object[ary_index.to_i-1]
|
126
123
|
end
|
127
124
|
|
128
|
-
# Recurse and
|
125
|
+
# Recurse and let the referenced object do the expanding
|
129
126
|
if object.respond_to?(:expand_placeholders)
|
130
127
|
value = object.expand_placeholders("[#{field_names.join('.')}]")
|
131
|
-
@result.gsub!(@
|
128
|
+
@result.gsub!(@placeholder, value)
|
132
129
|
end
|
133
130
|
end
|
134
131
|
|
@@ -136,30 +133,31 @@ module KingPlaceholder
|
|
136
133
|
# [/field_name]
|
137
134
|
# e.g. [items] Item price: [price] \n [/items]
|
138
135
|
def sub_collection
|
139
|
-
if match = @result.match(/\[#{@
|
136
|
+
if match = @result.match(/\[#{@field}\](.*)\[\/#{@field}\]/m) # the /m makes the dot match newlines, too!
|
140
137
|
whole_group = match[0]
|
141
138
|
inner_placeholders = match[1]
|
142
139
|
inner_result = ''
|
143
|
-
# Let the referenced object do the expanding by recursion if the collection
|
140
|
+
# Let the referenced object do the expanding by recursion if the collection knows placeholders
|
144
141
|
@cur_collection.each do |item|
|
145
142
|
inner_result << item.expand_placeholders(inner_placeholders)
|
146
143
|
end if @cur_collection.first.respond_to?(:expand_placeholders)
|
147
144
|
@result.gsub!(whole_group, inner_result)
|
148
145
|
else
|
149
|
-
@result.gsub!(@
|
146
|
+
@result.gsub!(@placeholder, "END MISSING FOR #{@field}")
|
150
147
|
end
|
151
148
|
end
|
152
149
|
|
153
|
-
# Checks if the
|
154
|
-
#
|
155
|
-
#
|
150
|
+
# Checks if the field name contains the current object's class name.
|
151
|
+
# Kick the prefix if the current class matches it, e.g:
|
152
|
+
# Inside a client object
|
153
|
+
# client.number => number
|
156
154
|
def check_current_prefix
|
157
|
-
if @
|
158
|
-
|
159
|
-
object_name =
|
155
|
+
if @field['.']
|
156
|
+
splits = @field.split('.')
|
157
|
+
object_name = splits.first
|
160
158
|
if object_name && obj.class.name == object_name.classify
|
161
|
-
|
162
|
-
@
|
159
|
+
splits.delete_at(0) # kick the object portion => invoice
|
160
|
+
@field = splits.join('.') # glue the rest back together [number]
|
163
161
|
end
|
164
162
|
end
|
165
163
|
end
|
data/lib/king_placeholder.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'king_placeholder/
|
1
|
+
require 'king_placeholder/parser'
|
2
2
|
require 'active_support'
|
3
3
|
require 'active_support/version'
|
4
4
|
|
@@ -9,7 +9,7 @@ require 'active_support/version'
|
|
9
9
|
|
10
10
|
module KingPlaceholder
|
11
11
|
|
12
|
-
# sets :placeholders and init Class.placeholders
|
12
|
+
# sets :placeholders and init Class.placeholders array on inclusion
|
13
13
|
def self.included(base)
|
14
14
|
if ActiveSupport::VERSION::MAJOR == 3 && ActiveSupport::VERSION::MINOR > 0
|
15
15
|
base.class_attribute :placeholders
|
@@ -21,26 +21,18 @@ module KingPlaceholder
|
|
21
21
|
end
|
22
22
|
|
23
23
|
module ClassMethods
|
24
|
-
|
25
|
-
#
|
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
|
24
|
+
# Define fields(methods) available to placeholder substitution
|
25
|
+
# @param [Array[<Symbol>] fieldnames
|
30
26
|
def has_placeholders(*fieldnames)
|
31
27
|
self.placeholders = fieldnames
|
32
28
|
include InstanceMethods
|
33
29
|
end
|
34
|
-
end
|
30
|
+
end
|
35
31
|
|
36
32
|
module InstanceMethods
|
37
|
-
|
38
33
|
# Check if a given field is declared as placeholder
|
39
|
-
#
|
40
|
-
#
|
41
|
-
# fieldname<String|Symbol>:: the field to search in the placeholders array
|
42
|
-
# ==== Returns
|
43
|
-
# <Boolean>:: true if in
|
34
|
+
# @param [Object] fieldname to search in placeholders array
|
35
|
+
# @return [Boolean]true if available
|
44
36
|
def is_placeholder?(fieldname)
|
45
37
|
self.class.placeholders.include?(fieldname.to_sym)
|
46
38
|
end
|
@@ -50,11 +42,11 @@ module KingPlaceholder
|
|
50
42
|
# and returns data with the same data type e.g. if you put a hash, you will
|
51
43
|
# get a hash.
|
52
44
|
#
|
53
|
-
#
|
45
|
+
# @examples
|
54
46
|
#
|
55
47
|
# Placeholders in text strings can be written in different notations.
|
56
48
|
#
|
57
|
-
#
|
49
|
+
# === Simple Notation:
|
58
50
|
#
|
59
51
|
# => [first_name]
|
60
52
|
# The fieldname is directly looked up on the current class:
|
@@ -63,7 +55,7 @@ module KingPlaceholder
|
|
63
55
|
# invoice.expand_placeholders(["You owe me [price_to_pay]", "Invoice Nr. [number]"])
|
64
56
|
# => ["You owe me 495,00 EUR", "Invoice Nr. 123"]
|
65
57
|
#
|
66
|
-
#
|
58
|
+
# === Namespaced Notation
|
67
59
|
#
|
68
60
|
# => [company.organisation]
|
69
61
|
# If the prefix equals the type of the current object the field is looked up on it.
|
@@ -78,7 +70,7 @@ module KingPlaceholder
|
|
78
70
|
# invoice.expand_placeholders("[client.company.default_address.zip]")
|
79
71
|
# => "50999"
|
80
72
|
#
|
81
|
-
#
|
73
|
+
# === Namespaced Notation with multiple related objects
|
82
74
|
#
|
83
75
|
# In a has_many relationship, related objects reside in an array, which can
|
84
76
|
# be reached using two different strategies.
|
@@ -91,11 +83,9 @@ module KingPlaceholder
|
|
91
83
|
# invoice.expand_placeholders("You bought an [items.0.name] for [items.0.price]")
|
92
84
|
# => "You bought an Apple for 12 EUR"
|
93
85
|
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
# === Returns
|
98
|
-
# <Hash,Array, String>:: whatever type you threw in is also returned
|
86
|
+
# @param [Hash|Array|String] content with placeholders
|
87
|
+
# @param [Object] opts - unused for now
|
88
|
+
# @return [Hash|Array|String] whatever type you throw in is also returned
|
99
89
|
def expand_placeholders(content, opts={})
|
100
90
|
if content.is_a?(Array) # Expand all array elements and go recursive
|
101
91
|
result = []
|
@@ -108,7 +98,7 @@ module KingPlaceholder
|
|
108
98
|
else # Only proceed with strings
|
109
99
|
return content unless content.is_a?(String)
|
110
100
|
end
|
111
|
-
parser = KingPlaceholder::
|
101
|
+
parser = KingPlaceholder::Parser.new(self, content, opts)
|
112
102
|
parser.sm.match
|
113
103
|
parser.result if parser.sm.state == :finished
|
114
104
|
end
|
Binary file
|
@@ -1,25 +1,23 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
# Construct dummy classes
|
4
|
+
class Master
|
5
5
|
include KingPlaceholder
|
6
|
-
end
|
7
|
-
|
8
|
-
# Construct dummy models
|
9
|
-
class Master < BaseModel
|
10
6
|
attr_accessor :string_field
|
11
7
|
attr_accessor :details
|
12
8
|
attr_accessor :side
|
13
9
|
has_placeholders :string_field
|
14
10
|
end
|
15
11
|
|
16
|
-
class Side
|
12
|
+
class Side
|
13
|
+
include KingPlaceholder
|
17
14
|
attr_accessor :field
|
18
15
|
attr_accessor :master
|
19
16
|
has_placeholders :field
|
20
17
|
end
|
21
18
|
|
22
|
-
class Detail
|
19
|
+
class Detail
|
20
|
+
include KingPlaceholder
|
23
21
|
include KingFormat::MoneyFields
|
24
22
|
attr_accessor :int_field, :money_field, :secret_field, :currency
|
25
23
|
attr_accessor :master
|
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.
|
4
|
+
version: 0.0.3
|
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-
|
12
|
+
date: 2012-07-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: statemachine
|
@@ -163,14 +163,16 @@ extensions: []
|
|
163
163
|
extra_rdoc_files: []
|
164
164
|
files:
|
165
165
|
- .gitignore
|
166
|
+
- .travis.yml
|
166
167
|
- Gemfile
|
167
168
|
- LICENSE
|
168
169
|
- README.rdoc
|
169
170
|
- Rakefile
|
170
171
|
- king_placeholder.gemspec
|
171
172
|
- lib/king_placeholder.rb
|
172
|
-
- lib/king_placeholder/
|
173
|
+
- lib/king_placeholder/parser.rb
|
173
174
|
- lib/king_placeholder/version.rb
|
175
|
+
- placeholder_state.dia
|
174
176
|
- spec/king_placeholder_spec.rb
|
175
177
|
- spec/spec_helper.rb
|
176
178
|
homepage: https://github.com/salesking/king_placeholder.git
|
@@ -187,7 +189,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
187
189
|
version: '0'
|
188
190
|
segments:
|
189
191
|
- 0
|
190
|
-
hash:
|
192
|
+
hash: 2706534140845333133
|
191
193
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
192
194
|
none: false
|
193
195
|
requirements:
|
@@ -196,7 +198,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
196
198
|
version: '0'
|
197
199
|
segments:
|
198
200
|
- 0
|
199
|
-
hash:
|
201
|
+
hash: 2706534140845333133
|
200
202
|
requirements: []
|
201
203
|
rubyforge_project:
|
202
204
|
rubygems_version: 1.8.24
|