king_placeholder 0.0.2 → 0.0.3
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/.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
|