fto 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/CONTRIBUTORS.txt +5 -0
  2. data/LICENCE.txt +202 -0
  3. data/README.txt +75 -0
  4. data/doc/classes/FormatText.html +120 -0
  5. data/doc/classes/FormatText/Context.html +240 -0
  6. data/doc/classes/FormatText/Context.src/M000001.html +26 -0
  7. data/doc/classes/FormatText/Context.src/M000004.html +26 -0
  8. data/doc/classes/FormatText/Context.src/M000007.html +26 -0
  9. data/doc/classes/FormatText/Context.src/M000008.html +26 -0
  10. data/doc/classes/FormatText/Context.src/M000009.html +26 -0
  11. data/doc/classes/FormatText/Effector.html +411 -0
  12. data/doc/classes/FormatText/Effector.src/M000004.html +38 -0
  13. data/doc/classes/FormatText/Effector.src/M000005.html +39 -0
  14. data/doc/classes/FormatText/Effector.src/M000007.html +38 -0
  15. data/doc/classes/FormatText/Effector.src/M000008.html +42 -0
  16. data/doc/classes/FormatText/Effector.src/M000009.html +43 -0
  17. data/doc/classes/FormatText/Effector.src/M000010.html +19 -0
  18. data/doc/classes/FormatText/Effector.src/M000011.html +18 -0
  19. data/doc/classes/FormatText/Effector.src/M000012.html +19 -0
  20. data/doc/classes/FormatText/Effector.src/M000013.html +18 -0
  21. data/doc/classes/FormatText/Effector.src/M000014.html +18 -0
  22. data/doc/classes/FormatText/FTO.html +305 -0
  23. data/doc/classes/FormatText/FTO.src/M000001.html +19 -0
  24. data/doc/classes/FormatText/FTO.src/M000002.html +34 -0
  25. data/doc/classes/FormatText/FTO.src/M000003.html +22 -0
  26. data/doc/classes/FormatText/FTO.src/M000004.html +19 -0
  27. data/doc/classes/FormatText/FTO.src/M000005.html +22 -0
  28. data/doc/classes/FormatText/FTO.src/M000006.html +20 -0
  29. data/doc/classes/FormatText/FTO.src/M000007.html +63 -0
  30. data/doc/classes/FormatText/FTO.src/M000008.html +63 -0
  31. data/doc/created.rid +1 -0
  32. data/doc/files/fto_rb.html +141 -0
  33. data/doc/files/lib/fto_rb.html +150 -0
  34. data/doc/fr_class_index.html +30 -0
  35. data/doc/fr_file_index.html +27 -0
  36. data/doc/fr_method_index.html +39 -0
  37. data/doc/index.html +24 -0
  38. data/doc/rdoc-style.css +208 -0
  39. data/lib/fto.rb +1349 -0
  40. data/test/test_fto_api.rb +143 -0
  41. data/test/test_fto_effectors.rb +440 -0
  42. data/test/test_helper.rb +3 -0
  43. metadata +115 -0
@@ -0,0 +1,30 @@
1
+
2
+ <?xml version="1.0" encoding="iso-8859-1"?>
3
+ <!DOCTYPE html
4
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
5
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
6
+
7
+ <!--
8
+
9
+ Classes
10
+
11
+ -->
12
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
13
+ <head>
14
+ <title>Classes</title>
15
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
16
+ <link rel="stylesheet" href="rdoc-style.css" type="text/css" />
17
+ <base target="docwin" />
18
+ </head>
19
+ <body>
20
+ <div id="index">
21
+ <h1 class="section-bar">Classes</h1>
22
+ <div id="index-entries">
23
+ <a href="classes/FormatText.html">FormatText</a><br />
24
+ <a href="classes/FormatText/Context.html">FormatText::Context</a><br />
25
+ <a href="classes/FormatText/Effector.html">FormatText::Effector</a><br />
26
+ <a href="classes/FormatText/FTO.html">FormatText::FTO</a><br />
27
+ </div>
28
+ </div>
29
+ </body>
30
+ </html>
@@ -0,0 +1,27 @@
1
+
2
+ <?xml version="1.0" encoding="iso-8859-1"?>
3
+ <!DOCTYPE html
4
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
5
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
6
+
7
+ <!--
8
+
9
+ Files
10
+
11
+ -->
12
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
13
+ <head>
14
+ <title>Files</title>
15
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
16
+ <link rel="stylesheet" href="rdoc-style.css" type="text/css" />
17
+ <base target="docwin" />
18
+ </head>
19
+ <body>
20
+ <div id="index">
21
+ <h1 class="section-bar">Files</h1>
22
+ <div id="index-entries">
23
+ <a href="files/lib/fto_rb.html">lib/fto.rb</a><br />
24
+ </div>
25
+ </div>
26
+ </body>
27
+ </html>
@@ -0,0 +1,39 @@
1
+
2
+ <?xml version="1.0" encoding="iso-8859-1"?>
3
+ <!DOCTYPE html
4
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
5
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
6
+
7
+ <!--
8
+
9
+ Methods
10
+
11
+ -->
12
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
13
+ <head>
14
+ <title>Methods</title>
15
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
16
+ <link rel="stylesheet" href="rdoc-style.css" type="text/css" />
17
+ <base target="docwin" />
18
+ </head>
19
+ <body>
20
+ <div id="index">
21
+ <h1 class="section-bar">Methods</h1>
22
+ <div id="index-entries">
23
+ <a href="classes/FormatText/FTO.html#M000004">destroyEffector (FormatText::FTO)</a><br />
24
+ <a href="classes/FormatText/Effector.html#M000010">disable (FormatText::Effector)</a><br />
25
+ <a href="classes/FormatText/FTO.html#M000005">disableEffector (FormatText::FTO)</a><br />
26
+ <a href="classes/FormatText/Effector.html#M000011">disabled? (FormatText::Effector)</a><br />
27
+ <a href="classes/FormatText/Effector.html#M000012">enable (FormatText::Effector)</a><br />
28
+ <a href="classes/FormatText/FTO.html#M000003">enableEffector (FormatText::FTO)</a><br />
29
+ <a href="classes/FormatText/Effector.html#M000013">enabled? (FormatText::Effector)</a><br />
30
+ <a href="classes/FormatText/FTO.html#M000006">findEffectors (FormatText::FTO)</a><br />
31
+ <a href="classes/FormatText/FTO.html#M000007">format (FormatText::FTO)</a><br />
32
+ <a href="classes/FormatText/Effector.html#M000009">new (FormatText::Effector)</a><br />
33
+ <a href="classes/FormatText/Context.html#M000008">new (FormatText::Context)</a><br />
34
+ <a href="classes/FormatText/FTO.html#M000001">new (FormatText::FTO)</a><br />
35
+ <a href="classes/FormatText/FTO.html#M000002">registerEffector (FormatText::FTO)</a><br />
36
+ </div>
37
+ </div>
38
+ </body>
39
+ </html>
@@ -0,0 +1,24 @@
1
+ <?xml version="1.0" encoding="iso-8859-1"?>
2
+ <!DOCTYPE html
3
+ PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
4
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
5
+
6
+ <!--
7
+
8
+ RDoc Documentation
9
+
10
+ -->
11
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
12
+ <head>
13
+ <title>RDoc Documentation</title>
14
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
15
+ </head>
16
+ <frameset rows="20%, 80%">
17
+ <frameset cols="25%,35%,45%">
18
+ <frame src="fr_file_index.html" title="Files" name="Files" />
19
+ <frame src="fr_class_index.html" name="Classes" />
20
+ <frame src="fr_method_index.html" name="Methods" />
21
+ </frameset>
22
+ <frame src="files/lib/fto_rb.html" name="docwin" />
23
+ </frameset>
24
+ </html>
@@ -0,0 +1,208 @@
1
+
2
+ body {
3
+ font-family: Verdana,Arial,Helvetica,sans-serif;
4
+ font-size: 90%;
5
+ margin: 0;
6
+ margin-left: 40px;
7
+ padding: 0;
8
+ background: white;
9
+ }
10
+
11
+ h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; }
12
+ h1 { font-size: 150%; }
13
+ h2,h3,h4 { margin-top: 1em; }
14
+
15
+ a { background: #eef; color: #039; text-decoration: none; }
16
+ a:hover { background: #039; color: #eef; }
17
+
18
+ /* Override the base stylesheet's Anchor inside a table cell */
19
+ td > a {
20
+ background: transparent;
21
+ color: #039;
22
+ text-decoration: none;
23
+ }
24
+
25
+ /* and inside a section title */
26
+ .section-title > a {
27
+ background: transparent;
28
+ color: #eee;
29
+ text-decoration: none;
30
+ }
31
+
32
+ /* === Structural elements =================================== */
33
+
34
+ div#index {
35
+ margin: 0;
36
+ margin-left: -40px;
37
+ padding: 0;
38
+ font-size: 90%;
39
+ }
40
+
41
+
42
+ div#index a {
43
+ margin-left: 0.7em;
44
+ }
45
+
46
+ div#index .section-bar {
47
+ margin-left: 0px;
48
+ padding-left: 0.7em;
49
+ background: #ccc;
50
+ font-size: small;
51
+ }
52
+
53
+
54
+ div#classHeader, div#fileHeader {
55
+ width: auto;
56
+ color: white;
57
+ padding: 0.5em 1.5em 0.5em 1.5em;
58
+ margin: 0;
59
+ margin-left: -40px;
60
+ border-bottom: 3px solid #006;
61
+ }
62
+
63
+ div#classHeader a, div#fileHeader a {
64
+ background: inherit;
65
+ color: white;
66
+ }
67
+
68
+ div#classHeader td, div#fileHeader td {
69
+ background: inherit;
70
+ color: white;
71
+ }
72
+
73
+
74
+ div#fileHeader {
75
+ background: #057;
76
+ }
77
+
78
+ div#classHeader {
79
+ background: #048;
80
+ }
81
+
82
+
83
+ .class-name-in-header {
84
+ font-size: 180%;
85
+ font-weight: bold;
86
+ }
87
+
88
+
89
+ div#bodyContent {
90
+ padding: 0 1.5em 0 1.5em;
91
+ }
92
+
93
+ div#description {
94
+ padding: 0.5em 1.5em;
95
+ background: #efefef;
96
+ border: 1px dotted #999;
97
+ }
98
+
99
+ div#description h1,h2,h3,h4,h5,h6 {
100
+ color: #125;;
101
+ background: transparent;
102
+ }
103
+
104
+ div#validator-badges {
105
+ text-align: center;
106
+ }
107
+ div#validator-badges img { border: 0; }
108
+
109
+ div#copyright {
110
+ color: #333;
111
+ background: #efefef;
112
+ font: 0.75em sans-serif;
113
+ margin-top: 5em;
114
+ margin-bottom: 0;
115
+ padding: 0.5em 2em;
116
+ }
117
+
118
+
119
+ /* === Classes =================================== */
120
+
121
+ table.header-table {
122
+ color: white;
123
+ font-size: small;
124
+ }
125
+
126
+ .type-note {
127
+ font-size: small;
128
+ color: #DEDEDE;
129
+ }
130
+
131
+ .xxsection-bar {
132
+ background: #eee;
133
+ color: #333;
134
+ padding: 3px;
135
+ }
136
+
137
+ .section-bar {
138
+ color: #333;
139
+ border-bottom: 1px solid #999;
140
+ margin-left: -20px;
141
+ }
142
+
143
+
144
+ .section-title {
145
+ background: #79a;
146
+ color: #eee;
147
+ padding: 3px;
148
+ margin-top: 2em;
149
+ margin-left: -30px;
150
+ border: 1px solid #999;
151
+ }
152
+
153
+ .top-aligned-row { vertical-align: top }
154
+ .bottom-aligned-row { vertical-align: bottom }
155
+
156
+ /* --- Context section classes ----------------------- */
157
+
158
+ .context-row { }
159
+ .context-item-name { font-family: monospace; font-weight: bold; color: black; }
160
+ .context-item-value { font-size: small; color: #448; }
161
+ .context-item-desc { color: #333; padding-left: 2em; }
162
+
163
+ /* --- Method classes -------------------------- */
164
+ .method-detail {
165
+ background: #efefef;
166
+ padding: 0;
167
+ margin-top: 0.5em;
168
+ margin-bottom: 1em;
169
+ border: 1px dotted #ccc;
170
+ }
171
+ .method-heading {
172
+ color: black;
173
+ background: #ccc;
174
+ border-bottom: 1px solid #666;
175
+ padding: 0.2em 0.5em 0 0.5em;
176
+ }
177
+ .method-signature { color: black; background: inherit; }
178
+ .method-name { font-weight: bold; }
179
+ .method-args { font-style: italic; }
180
+ .method-description { padding: 0 0.5em 0 0.5em; }
181
+
182
+ /* --- Source code sections -------------------- */
183
+
184
+ a.source-toggle { font-size: 90%; }
185
+ div.method-source-code {
186
+ background: #262626;
187
+ color: #ffdead;
188
+ margin: 1em;
189
+ padding: 0.5em;
190
+ border: 1px dashed #999;
191
+ overflow: hidden;
192
+ }
193
+
194
+ div.method-source-code pre { color: #ffdead; overflow: hidden; }
195
+
196
+ /* --- Ruby keyword styles --------------------- */
197
+
198
+ .standalone-code { background: #221111; color: #ffdead; overflow: hidden; }
199
+
200
+ .ruby-constant { color: #7fffd4; background: transparent; }
201
+ .ruby-keyword { color: #00ffff; background: transparent; }
202
+ .ruby-ivar { color: #eedd82; background: transparent; }
203
+ .ruby-operator { color: #00ffee; background: transparent; }
204
+ .ruby-identifier { color: #ffdead; background: transparent; }
205
+ .ruby-node { color: #ffa07a; background: transparent; }
206
+ .ruby-comment { color: #b22222; font-weight: bold; background: transparent; }
207
+ .ruby-regexp { color: #ffa07a; background: transparent; }
208
+ .ruby-value { color: #7fffd4; background: transparent; }
@@ -0,0 +1,1349 @@
1
+ #
2
+ # = fto.rb - Formatted Text Output
3
+ #
4
+ # Author:: Ken Coar
5
+ # Copyright:: Copyright © 2009 Ken Coar
6
+ # License:: Apache Licence 2.0
7
+ #
8
+ # == Synopsis
9
+ #
10
+ # require 'fto'
11
+ # include FormatText
12
+ # formatString = FTO.new("This will include a string: !AS", "string1")
13
+ # puts formatString.format
14
+ # puts formatString.format("string2")
15
+ #
16
+ # == Description
17
+ #
18
+ # FTO is a Ruby library for formatting text strings. In function it
19
+ # is similar to <tt>printf(3)</tt>; however, the syntax of the format
20
+ # effectors (sometimes called 'format descriptors') and the selection
21
+ # of effectors bundled with the package are based on the SYS$FAO
22
+ # user-mode system service found on OpenVMS.
23
+ #
24
+ # The FTO class is an extension of String, enhancing the constructor,
25
+ # adding the <i>format()</i> instance method, and the
26
+ # <i>registerEffector()</i> class method.
27
+ #
28
+ # In addition to the included list of effectors, FTO provides easy
29
+ # extensibility by allowing the developer to write her own effector
30
+ # handlers and registering them with <i>FTO.registerEffector()</i>.
31
+ #
32
+ #--
33
+ # Copyright 2009 Ken Coar
34
+ #
35
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
36
+ # may not use this file except in compliance with the License. You may
37
+ # obtain a copy of the License at
38
+ #
39
+ # http://www.apache.org/licenses/LICENSE-2.0
40
+ #
41
+ # Unless required by applicable law or agreed to in writing, software
42
+ # distributed under the License is distributed on an "AS IS" BASIS,
43
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
44
+ # implied. See the License for the specific language governing
45
+ # permissions and limitations under the License.
46
+ #++
47
+
48
+
49
+ #--
50
+ # Notes about markup (docco being hard to find). This will be deleted
51
+ # ere long.
52
+ #
53
+ # \:arg:, \:args:
54
+ #
55
+ # \:call-seq:
56
+ # Alternative(s) to default invocation built by rdoc
57
+ #
58
+ # \:doc:
59
+ #
60
+ # \:enddoc:
61
+ # Stop documenting the current tree of items
62
+ #
63
+ # \:include:
64
+ # Pull in another file.
65
+ #
66
+ # \:main:
67
+ #
68
+ # \:nodoc: all
69
+ #
70
+ # \:nodoc:
71
+ # Temporarily turn off rdoc interpretation
72
+ #
73
+ # \:notnew:, \:not_new:, \:not-new:
74
+ # Document constructor as 'initialize' rather than as 'new'
75
+ #
76
+ # \:section:
77
+ #
78
+ # \:title:
79
+ # Duh.
80
+ #
81
+ # \:yield:, \:yields:
82
+ # Display an alternative to the automatic 'yields' rdoc makes up
83
+ #
84
+ # Stuff from SYS$FAO that isn't yet implemented (and may not be
85
+ # reasonable outside of OpenVMS).
86
+ #
87
+ # @TODO: !%T Inserts the system time. It takes one parameter: the
88
+ # address of a quadword time value to be converted to
89
+ # ASCII. If you specify 0, the current system time is
90
+ # inserted.
91
+ # @TODO: !%D Inserts the system date and time. It takes one
92
+ # parameter: the address of a quadword time value to be
93
+ # converted to ASCII. If you specify 0, the current system
94
+ # date and time is inserted.
95
+ # @TODO: !n%C Inserts a character string when the most recently
96
+ # evaluated argument has the value n. (Recommended for use
97
+ # with multilingual products.)
98
+ # @TODO: !%E Inserts a character string when the value of the most
99
+ # recently evaluated argument does not match any preceding
100
+ # !n%C directives. (Recommended for use with multilingual
101
+ # products.)
102
+ # @TODO: !%F Makes the end of a plurals statement.
103
+ #
104
+ #++
105
+
106
+ require 'rubygems'
107
+ require 'gettext'
108
+
109
+ module FormatText
110
+
111
+ include GetText
112
+
113
+ #
114
+ # Each time an effector's pattern is matched, its _code_ attribute
115
+ # is called with this structure, which is used to communicate
116
+ # details to the code block, and by the code block to pass back some
117
+ # behaviour modification notes. Most of the attributes should be
118
+ # treated as read-only, with exceptions noted below in the attribute
119
+ # descriptions.
120
+ #
121
+ # This class is used internally by the +fto+ library and isn't
122
+ # really intended for external consumption.
123
+ #
124
+ class Context
125
+
126
+ #
127
+ # <i>FormatText::Effector object</i>. The _Effector_ object
128
+ # involved. Read-only.
129
+ #
130
+ attr_accessor :effectorObj
131
+
132
+ #
133
+ # <i>FTO object</i>. The _FTO_ object being processed.
134
+ # Read-only.
135
+ #
136
+ attr_accessor :ftoObj
137
+
138
+ #
139
+ # _String_. The string that matched the effector and triggered
140
+ # its processing. Read-only.
141
+ #
142
+ attr_accessor :sMatched
143
+
144
+ #
145
+ # _Array_. Arguments remaining to be processed. Usually
146
+ # read-only, but see the description of the _usedArgs_ attribute
147
+ # for exceptions.
148
+ #
149
+ attr_accessor :usedArgs
150
+
151
+ #
152
+ # _Array_. List of arguments already processed. Usually
153
+ # read-only. The effector function is responsible for using the
154
+ # values in this array to perform its task.
155
+ #
156
+ # The only time this should be considered read/write is when the
157
+ # effector is intended to modify the list of arguments being used
158
+ # to build the final string. The _argList_ attribute should be
159
+ # modified in conjunction with _usedArgs_ to maintain continuity.
160
+ # By default, after the effector function returns, the caller (the
161
+ # <i>FTO#format</i> method) will take the element from the front
162
+ # of the _argList_ array and push it onto the end of the
163
+ # _usedArgs_ array.
164
+ #
165
+ attr_accessor :argList
166
+
167
+ #
168
+ # _Boolean_. The effector function sets this to +true+ to inhibit
169
+ # the <i>FTO#format()</i> method from modifying the argument list
170
+ # after the function returns. See the descriptions under the
171
+ # _argList_ and _usedArgs_ attributes.
172
+ #
173
+ attr_accessor :reuseArg
174
+
175
+ #
176
+ # Create a new <i>FormatText::Context</i> object. There may only
177
+ # be a single argument to the constructor, and it must be a hash,
178
+ # using symbols for the names of the attributes to set:
179
+ #
180
+ # x = FormatText::Context.new({ :sMatched => 'string', :reuseArg => false })
181
+ #
182
+ # This class is used internally by the +fto+ library and isn't
183
+ # really intended for external consumption.
184
+ #
185
+ # :call-seq:
186
+ # new<i>(Hash)</i> => <i>FormatText::Context object</i>
187
+ #
188
+ def initialize(*argsp)
189
+ (@effectorObj, @ftoObj) = nil
190
+ @sMatched = ''
191
+ @usedArgs = []
192
+ @argList = []
193
+ @reuseArg = false
194
+ if (argsp[0].class != Hash)
195
+ raise RuntimeError, self.class.name + _(' requires a Hash to create')
196
+ end
197
+ argsp[0].each { |key,val| eval("self.#{key.to_s} = val") }
198
+ end # def initialize
199
+
200
+ end # class Context
201
+
202
+ #
203
+ # = Description
204
+ #
205
+ # The _FTO_ class is the user interface; all others are for
206
+ # developers modifying or extending the +fto+ library.
207
+ #
208
+ # _FTO_ is a subclass of _String_, so all _String_ methods work on
209
+ # an _FTO_ object. _FTO_ provides the additional <i>format()</i>
210
+ # method.
211
+ #
212
+ # In addition to string text, the constructor (<i>FTO.new</i>) can
213
+ # take more than a single argument. Additional arguments will be
214
+ # stored as part of the object and will be available to the
215
+ # <i>FTO#format()</i> method at runtime.
216
+ #
217
+ # An _FTO_ object can be created as just a formatting string, or the
218
+ # constructor invocation can also include values to be applied by
219
+ # the <i>FTO#format()</i> method. At runtime the <i>format()</i> method
220
+ # can override any argument list provided at instantiation, but the
221
+ # latter is not lost.
222
+ #
223
+ class FTO < String
224
+
225
+ #
226
+ # Class variables
227
+ #
228
+
229
+ #
230
+ # Hash of all registered effector objects, keyed by their IDs.
231
+ #
232
+ @@RegisteredEffectors = {} unless (defined?(@@RegisteredEffectors))
233
+
234
+ #
235
+ # Hash of all currently enabled effectors, keyed by their sort
236
+ # key.
237
+ #
238
+ @@EnabledEffectors = {} unless (defined?(@@EnabledEffectors))
239
+
240
+ #
241
+ # Ordered array of effector keys (used to index
242
+ # @@EnabledEffectors)
243
+ #
244
+ @@EffectorKeys = [] unless (defined?(@@EffectorKeys))
245
+
246
+ # :stopdoc:
247
+ #
248
+ # We do this in order to call super on String, but since our
249
+ # argument list is different, we need to finesse it a little.
250
+ # Nobody's business but ours.
251
+ #
252
+ alias_method(:String_initialize, :initialize) # :nodoc:
253
+ # :startdoc:
254
+
255
+ #
256
+ # Debugging class method to access list of registered effectors
257
+ #
258
+ def self.effectors() # :nodoc:
259
+ @@RegisteredEffectors
260
+ end # def self.effectors()
261
+
262
+ #
263
+ # Debugging class method to access list of effector keys.
264
+ #
265
+ def self.eKeys() # :nodoc:
266
+ @@EffectorKeys
267
+ end # def self.eKeys()
268
+
269
+ #
270
+ # Debugging class method to access regular expression used to find
271
+ # effectors.
272
+ #
273
+ def self.regex() # :nodoc:
274
+ @@regex
275
+ end # def self.regex()
276
+
277
+ #
278
+ # Any argument list is supplied at object instantiation can be
279
+ # temporarily overridden when the <i>FTO#format()</i> method is
280
+ # invoked.
281
+ #
282
+ # :call-seq:
283
+ # new<i>()</i> => <i>FTO object</i>
284
+ # new<i>(String)</i> => <i>FTO object</i>
285
+ # new<i>(String, arg [, ...])</i> => <i>FTO object</i>
286
+ #
287
+ def initialize(text=nil, *args)
288
+ String_initialize(text)
289
+ @args = args
290
+ end # def initialize
291
+
292
+ #
293
+ # Add an effector description to the list of those which will be
294
+ # processed by the <i>FTO#format()</i> method.
295
+ #
296
+ # :call-seq:
297
+ # FTO.registerEffector<i>(FormatText::Effector)</i> => <i>nil</i>
298
+ # FTO.registerEffector<i>({ :symattr => value [, ...] })</i> => <i>nil</i>
299
+ #
300
+ def self.registerEffector(*args)
301
+ if ((args.length == 1) && (args[0].class.name.match(/Effector$/))
302
+ newE = args[0]
303
+ else
304
+ newE = Effector.new('placeholder')
305
+ if ((args.length == 1) && (args[0].class == Hash))
306
+ args[0].each do |key,val|
307
+ eval("newE.#{key.to_s} = val")
308
+ end
309
+ else
310
+ newE = Effector.new(args)
311
+ end
312
+ end
313
+ @@RegisteredEffectors[newE.id] = newE
314
+ key = sprintf('%06d-%s', newE.priority, newE.name)
315
+ newE.sortKey = key
316
+ self.rebuildEffectorList()
317
+ nil
318
+ end # def self.registerEffector()
319
+
320
+ # :stopdoc:
321
+ #
322
+ # This class method rebuilds the regular expression and the hash
323
+ # of enabled effectors. It needs to be invoked any time an
324
+ # effector is added, destroyed, enabled, or disabled. It's for
325
+ # internal use only.
326
+ #
327
+ def self.rebuildEffectorList()
328
+ enabled = @@RegisteredEffectors.select { |id,e| e.enabled? }
329
+ @@EffectorKeys = enabled.collect { |k,e| e.sortKey }.sort
330
+ @@EnabledEffectors = {}
331
+ enabled.each { |k,e| @@EnabledEffectors[e.sortKey] = e }
332
+ @@regex = Regexp.new("(#{@@EffectorKeys.collect {|k| @@EnabledEffectors[k].reMatch}.join(')|(')})")
333
+ nil
334
+ end # def self.rebuildEffectorList()
335
+ # :startdoc:
336
+
337
+ #
338
+ # Enables the effector with the specified ID (found in the
339
+ # effector's _id_ attribute). This is a no-op if the effector is
340
+ # already enabled.
341
+ #
342
+ # :call-seq:
343
+ # FTO.enableEffector<i>(Fixnum)</i> => <i>nil</i>
344
+ #
345
+ def self.enableEffector(id)
346
+ if ((e = @@RegisteredEffectors[id]).nil?)
347
+ raise RuntimeError, _('No such effector ') + "ID\##{id}"
348
+ end
349
+ e.enabled = true
350
+ self.rebuildEffectorList()
351
+ end # def self.enableEffector()
352
+
353
+ #
354
+ # Completely removes the effector with the specified ID from the
355
+ # FTO system. <strong>THIS IS NOT REVERSIBLE!</strong>
356
+ #
357
+ # :call-seq:
358
+ # FTO.destroyEffector<i>(Fixnum)</i> => <i>nil</i>
359
+ #
360
+ def self.destroyEffector(id)
361
+ @@RegisteredEffectors.delete(id)
362
+ self.rebuildEffectorList()
363
+ end # def self.destroyEffector()
364
+
365
+ #
366
+ # Disables the effector with the specified ID (such as from
367
+ # <i>FTO.findEffectors()</i>). This is a no-op if the effector is
368
+ # already disabled.
369
+ #
370
+ # :call-seq:
371
+ # FTO.disableEffector<i>(Fixnum)</i> => <i>nil</i>
372
+ #
373
+ def self.disableEffector(id)
374
+ if ((e = @@RegisteredEffectors[id]).nil?)
375
+ raise RuntimeError, _('No such effector ') + "ID\##{id}"
376
+ end
377
+ e.disable
378
+ nil
379
+ end # def self.disableEffector()
380
+
381
+ #
382
+ # Returns an array of registered effectors whose names (_name_
383
+ # attribute) match the specified pattern.
384
+ #
385
+ # :call-seq:
386
+ # FTO.findEffectors<i>(String)</i> => <i>Array</i>
387
+ # FTO.findEffectors<i>(Regexp)</i> => <i>Array</i>
388
+ #
389
+ def self.findEffectors(pattern)
390
+ pattern = Regexp.new(pattern) unless (pattern.class == Regexp)
391
+ matches = @@RegisteredEffectors.select { |id,e| e.name.match(pattern) }
392
+ matches.collect { |id,e| e }
393
+ end # def self.findEffector()
394
+
395
+ #
396
+ # Process the formatting string, optionally with a runtime
397
+ # argument list. The argument list can either be a list of
398
+ # values, an array of values, or an <i>FormatText::Context</i>
399
+ # object. (The latter is intended only for internal use with
400
+ # recursion.)
401
+ #
402
+ # :call-seq:
403
+ # format<i>()</i> => <i>String</i>
404
+ # format<i>(arg [, ...])</i> => <i>String</i>
405
+ # format<i>(Array)</i> => <i>String</i>
406
+ # format<i>(FormatText::Context)</i> => <i>String</i> (<u>internal use only</u>)
407
+ #
408
+ def format(*argListp)
409
+ argList = argListp.empty? ? @args.clone : argListp
410
+ if ((argList.length == 1) && (argList[0].class == Array)) &&
411
+ argList = argList[0]
412
+ end
413
+ #
414
+ # It's possible we were passed a Context object so we can
415
+ # recurse. If so, use its values for some of these.
416
+ #
417
+ if ((argList.length == 1) && (argList[0].class == FormatText::Context))
418
+ eContext = argList[0]
419
+ usedArgs = eContext.usedArgs
420
+ argList = eContext.argList
421
+ else
422
+ usedArgs = []
423
+ eContext = Context.new({
424
+ :ftoObj => self,
425
+ :usedArgs => usedArgs,
426
+ :argList => argList
427
+ })
428
+ end
429
+ result = self.to_s
430
+ effector = sMatched = nil
431
+ while (m = result.match(@@regex))
432
+ #
433
+ # Find out which effector was matched. The index in .captures
434
+ # will be the same as the index in @effectors.
435
+ #
436
+ m.captures.length.times do |i|
437
+ next if (m.captures[i].nil?)
438
+ eContext.effectorObj = effector = @@EnabledEffectors[@@EffectorKeys[i]]
439
+ eContext.sMatched = sMatched = m.captures[i]
440
+ eContext.reuseArg = false
441
+ break
442
+ end
443
+ #
444
+ # Call the workhorse for this descriptor
445
+ #
446
+ replacement = effector.code.call(eContext)
447
+ result.sub!(sMatched, replacement)
448
+ #
449
+ # Mark the item at the front of the argument list as having
450
+ # been used, if the effector agrees.
451
+ #
452
+ usedArgs.push(argList.shift) unless (eContext.reuseArg)
453
+ end
454
+ result
455
+ end # def format()
456
+
457
+ end # class FTO
458
+
459
+ #
460
+ # Class used to describe a format effector. The
461
+ # <i>FormatText::Effector</i> class is basically a container rather
462
+ # than an active class. It holds information about how an effector
463
+ # operates and what it needs.
464
+ #
465
+ # Typically <i>FormatText::Effector</i> objects aren't created
466
+ # directly, but by calling <i>FTO.registerEffector()</i>.
467
+ #
468
+ class Effector
469
+
470
+ @@NEXTID = 1 unless (defined?(@@NEXTID))
471
+
472
+ #
473
+ # _Fixnum_. ID number assigned to the effector, unique within a
474
+ # usage environment. Used by <i>FTO.enableEffector()</i>,
475
+ # <i>FTO.disableEffector()</i>, and <i>FTO.destroyEffector()</i>.
476
+ #
477
+ attr_reader :id
478
+
479
+ #
480
+ # _Boolean_. Whether this effector should be processed or
481
+ # ignored.
482
+ #
483
+ attr_accessor :enabled
484
+
485
+ #
486
+ # _String_. Human-readable name for what the effector does. Used
487
+ # for sorting.
488
+ #
489
+ attr_accessor :name
490
+
491
+ #
492
+ # _String_. Human-readable brief description.
493
+ #
494
+ attr_accessor :description
495
+
496
+ #
497
+ # _Fixnum_. Numeric priority value for sorting purposes.
498
+ #
499
+ attr_accessor :priority
500
+
501
+ #
502
+ # _String_. Key used to sort effectors, built from the name and
503
+ # priority.
504
+ #
505
+ attr_accessor :sortKey
506
+
507
+ #
508
+ # _String_. Regular expression used to recognise the effector in
509
+ # the format string.
510
+ #
511
+ attr_accessor :reMatch
512
+
513
+ #
514
+ # _String_. Optional regex pattern used to extract additional info
515
+ # (such as a field width).
516
+ #
517
+ attr_accessor :reExtra
518
+
519
+ #
520
+ # _Integer_. [<i>Numeric conversion effectors only</i>] If the
521
+ # effector is used to represent a number, this is a mask of the
522
+ # bits to be included. For instance, if the effector only
523
+ # displays 8-bit values, this value would be <tt>0xFF</tt>.
524
+ #
525
+ attr_accessor :mask
526
+
527
+ #
528
+ # _Integer_. [<i>Numeric conversion effectors only</i>] If the
529
+ # value is to be interpreted as signed, this is a mask for the
530
+ # sign bit. (For a byte, <tt>:mask</tt> would be <tt>0xFF</tt>
531
+ # and <tt>:signbit</tt> would be <tt>0x80</tt>.)
532
+ #
533
+ attr_accessor :signbit
534
+
535
+ #
536
+ # _Fixnum_. Default width for the effector result (used when
537
+ # filling).
538
+ #
539
+ attr_accessor :dWidth
540
+
541
+ #
542
+ # _Any_. Default value (used if _argList_ has been exhausted).
543
+ #
544
+ attr_accessor :dValue
545
+
546
+ #
547
+ # _String_. Character used to fill values shorter than the field
548
+ # widths.
549
+ #
550
+ attr_accessor :fill
551
+
552
+ #
553
+ # _Symbol_. Symbol value <tt>:left</tt> or <tt>:right</tt>,
554
+ # indicating against which edge of a too-wide field the value
555
+ # should abut.
556
+ #
557
+ attr_accessor :justify
558
+
559
+ #
560
+ # _Symbol_. Symbol value <tt>:left</tt> or <tt>:right</tt>
561
+ # indicating on which side too-wide results should be truncated to
562
+ # fit within the field width.
563
+ #
564
+ attr_accessor :truncate
565
+
566
+ #
567
+ # _Array_. Array of additional info for the function
568
+ # (effector-specific).
569
+ #
570
+ attr_accessor :data
571
+
572
+ #
573
+ # _Proc_. Code block (<i>e.g.</i>, a +lambda+ function) that
574
+ # actually interprets the effector. (See the
575
+ # <i>FormatText::Context</i> class for a description, or the
576
+ # various +Convert+ constants in the source.)
577
+ #
578
+ attr_accessor :code
579
+
580
+ #
581
+ # Creates a new <i>FormatText::Effector</i> object. The argument
582
+ # list can be either an order-dependent list of attribute values,
583
+ # or a hash using the symbolised attribute names as the keys.
584
+ #
585
+ # call-seq:
586
+ # new<i>(Hash)</i> => <i>FormatText::Effector object</i>
587
+ # new<i>(name, description, enabled, priority, code, reMatch, reExtra, mask, signbit, dWidth, fill, justify, truncate, data)</i> => <i>FormatText::Effector object</i>
588
+ #
589
+ def initialize(*argsp)
590
+ @id = @@NEXTID
591
+ @@NEXTID += 1
592
+ #
593
+ # Set up defaults
594
+ #
595
+ (@name, @description, @code, @reMatch, @reExtra, @mask,
596
+ @signbit, @dWidth, @dValue, @truncate) = nil
597
+ @enabled = true
598
+ @priority = 1000
599
+ @data = []
600
+ @fill = ' '
601
+ @justify = :left
602
+ #
603
+ # We can either handle an order-dependent list of arguments, or
604
+ # a hash representing keyword arguments.
605
+ #
606
+ args = (argsp.length == 1) ? argsp[0] : argsp
607
+ case args.class
608
+ when Array
609
+ (@name, @description, @enabled, @priority, @code, @reMatch,
610
+ @reExtra, @mask, @signbit, @dWidth, @fill, @justify,
611
+ @truncate, @data) = args
612
+ when Hash
613
+ args.each { |key,val| eval("@{key.to_s} = val") }
614
+ end
615
+ @data = [@data] unless (@data.nil? || (@data.class == Array))
616
+ end # def initialize
617
+
618
+ #
619
+ # Disables the effector, removing it from consideration when the
620
+ # format string is being scanned. It can be subsequently
621
+ # re-enabled with the <i>Effector#enable()</i> method. This is a
622
+ # no-op if the effector is already disabled.
623
+ #
624
+ # :call-seq:
625
+ # disable<i>()</i> => <i>nil</i>
626
+ #
627
+ def disable()
628
+ @enabled = false
629
+ FTO.rebuildEffectorList()
630
+ end # def disable()
631
+
632
+ #
633
+ # Returns +true+ if the effector is disabled (<i>i.e.</i>,
634
+ # inactive and not considered when scanning the format string);
635
+ # otherwise returns +false+.
636
+ #
637
+ # :call-seq:
638
+ # disabled? => <i>Boolean</i>
639
+ #
640
+ def disabled?()
641
+ ! @enabled
642
+ end # def disabled?()
643
+
644
+ #
645
+ # Enables the effector, making certain that it is considered when
646
+ # the format string is being scanned. This is a no-op if the
647
+ # effector is already active.
648
+ #
649
+ # :call-seq:
650
+ # enable<i>()</i> => <i>nil</i>
651
+ #
652
+ def enable()
653
+ @enabled = true
654
+ FTO.rebuildEffectorList()
655
+ end # def enable()
656
+
657
+ #
658
+ # Returns +true+ if the effector is enabled (<i>i.e.</i>,
659
+ # active and considered when scanning the format string);
660
+ # otherwise returns +false+.
661
+ #
662
+ # :call-seq:
663
+ # enabled? => <i>Boolean</i>
664
+ #
665
+ def enabled?()
666
+ @enabled
667
+ end # def enabled?()
668
+
669
+ end # class Effector
670
+
671
+ # :stopdoc:
672
+ #
673
+ # Lambda functions (for no particular reason) used by effectors.
674
+ #
675
+
676
+ #
677
+ # Handle filling, justifying, and truncating.
678
+ #
679
+ FillAndJustify = lambda {
680
+ | eObj, rText, width, overflowChar |
681
+ overflowChar = '*' if (overflowChar.nil?)
682
+ if ((! width.nil?) && (width.to_s.length != 0))
683
+ width = width.to_i
684
+ else
685
+ width = eObj.dWidth
686
+ end
687
+ return rText if (width.nil?)
688
+ width = width.to_i
689
+ if (rText.length > width)
690
+ case eObj.truncate
691
+ when :right
692
+ rText = rText[0,width]
693
+ when :left
694
+ rText = rText[rText.length - width,width]
695
+ else
696
+ rText = overflowChar * width
697
+ end
698
+ else
699
+ #
700
+ # The result is shorter than the width. How do we make up the
701
+ # difference?
702
+ #
703
+ fill = eObj.fill * (width - rText.length)
704
+ case eObj.justify
705
+ when :left then rText += fill
706
+ when :right then rText = fill + rText
707
+ end
708
+ end
709
+ rText
710
+ }
711
+
712
+ #
713
+ # Get the next argument, use the default, or raise an exception.
714
+ #
715
+ GetArgument = lambda {
716
+ | eContext |
717
+ if (! eContext.argList.empty?)
718
+ arg = eContext.argList.first
719
+ elsif ((arg = eContext.effectorObj.dValue).nil?)
720
+ raise RuntimeError,
721
+ _('Insufficient arguments to format ') + "'#{eContext.ftoObj}'"
722
+ end
723
+ arg
724
+ }
725
+
726
+ #
727
+ # Predefined conversion functions. The function is passed a Context
728
+ # object (defined above).
729
+ #
730
+ # The function is responsible for getting its argument from
731
+ # argList.first. It can modify usedArgs, argList, and reuseArg, but
732
+ # this should be done with caution.
733
+ #
734
+ # The function's return value is the formatted quantity.
735
+ #
736
+
737
+ #
738
+ # Placeholder effector function.
739
+ #
740
+ ConvertTBD = lambda {
741
+ | eContext |
742
+ eContext.reuseArg = true
743
+ return 'TBD'
744
+ }
745
+
746
+ ConvertFixedWindow = lambda {
747
+ | eContext |
748
+ eObj = eContext.effectorObj
749
+ parcels = eContext.sMatched.match(Regexp.new(eObj.reExtra))
750
+ width = parcels.captures[0].sub(/</, '').to_i
751
+ #
752
+ # Clone the Context because we want to retain the arglist array
753
+ # identities, but don't want to mess up any of the other aspects
754
+ # needed by our caller.
755
+ #
756
+ f = FTO.new(parcels.captures[1], eContext.clone)
757
+ sNext = f.format
758
+ if ((diff = width - sNext.length) <= 0)
759
+ sNext = sNext[diff.abs,width]
760
+ else
761
+ sNext = (' ' * diff) + sNext
762
+ end
763
+ eContext.reuseArg = true
764
+ sNext
765
+ }
766
+
767
+ #
768
+ # Convert a numeric argument of some sort.
769
+ #
770
+ ConvertNumeric = lambda {
771
+ | eContext |
772
+ eObj = eContext.effectorObj
773
+ #
774
+ # This bit of funk is so we can correctly handle a string like
775
+ # '0xABC'.
776
+ #
777
+ thisArg = eval("#{GetArgument.call(eContext)}").to_i & eObj.mask
778
+ #
779
+ # Sign-extend the value if appropriate.
780
+ #
781
+ if ((! eObj.signbit.nil?) && ((thisArg & eObj.signbit) != 0))
782
+ thisArg |= ~ eObj.mask
783
+ end
784
+ #
785
+ # See if we need to convert to a non-decimal base.
786
+ #
787
+ case true
788
+ when eObj.data.include?(:octal) then replacement = thisArg.to_s(8)
789
+ when eObj.data.include?(:hex) then replacement = thisArg.to_s(16).upcase
790
+ else replacement = thisArg.to_s
791
+ end
792
+ width = eContext.sMatched.match(Regexp.new(eObj.reExtra)).captures[0]
793
+ FillAndJustify.call(eObj, replacement, width, '*')
794
+ }
795
+
796
+ #
797
+ # Insert a string
798
+ #
799
+ ConvertString = lambda {
800
+ | eContext |
801
+ eObj = eContext.effectorObj
802
+ if (eObj.data.include?(:lengthAsArg))
803
+ strLen = GetArgument.call(eContext)
804
+ eContext.usedArgs.push(eContext.argList.shift)
805
+ end
806
+ replacement = GetArgument.call(eContext)
807
+ unless (strLen.nil?)
808
+ if (strLen <= replacement.length)
809
+ replacement = replacement[0,strLen]
810
+ else
811
+ replacement += "\000" * (strLen - replacement.length)
812
+ end
813
+ end
814
+ replacement.gsub!(/[^[:print:]]/, '.') if (eObj.data.include?(:printable))
815
+ eObj = eContext.effectorObj
816
+ width = eContext.sMatched.match(Regexp.new(eObj.reExtra)).captures[0]
817
+ FillAndJustify.call(eObj, replacement, width, '*')
818
+ }
819
+
820
+ #
821
+ # Insert a repeating character.
822
+ #
823
+ ConvertRepeatChar = lambda {
824
+ | eContext |
825
+ eContext.reuseArg = true
826
+ eObj = eContext.effectorObj
827
+ exInfo = eContext.sMatched.match(Regexp.new(eObj.reExtra))
828
+ width = exInfo.captures[0].to_i
829
+ replacement = exInfo.captures[1] * width
830
+ return replacement
831
+ }
832
+
833
+ ConvertIsAre = lambda {
834
+ | eContext |
835
+ #
836
+ # We don't actually use the argument list, so we need to make sure
837
+ # the argument pointer doesn't get advanced.
838
+ #
839
+ eContext.reuseArg = true
840
+ result = (eContext.usedArgs.last == 1) ? _('is') : _('are')
841
+ result.upcase! if (eContext.sMatched == '!%IS')
842
+ return result
843
+ }
844
+
845
+ ConvertPlural = lambda {
846
+ | eContext |
847
+ #
848
+ # We don't actually use the argument list, so we need to make sure
849
+ # the argument pointer doesn't get advanced.
850
+ #
851
+ eContext.reuseArg = true
852
+ m2 = eContext.sMatched.match(Regexp.new(eContext.effectorObj.reExtra))
853
+ return '' if (m2.captures[0].nil?)
854
+ return m2.captures[0] if (eContext.usedArgs.last == 1)
855
+ result = (m2.captures[0].upcase == 'S') ? 'es' : 's'
856
+ result.upcase! if (m2.captures[0].match(/[A-Z]/))
857
+ return m2.captures[0] + result
858
+ }
859
+
860
+ # :startdoc:
861
+
862
+ #
863
+ # Start registering the standard effectors.
864
+ #
865
+
866
+ #
867
+ # The simple replacements ('insert a TAB', etc.)
868
+ #
869
+ FTO.registerEffector({
870
+ :name => 'TAB',
871
+ :description => _('Insert a TAB character'),
872
+ :reMatch => '!_',
873
+ :code => lambda {
874
+ | eContext |
875
+ eContext.reuseArg = true
876
+ return "\011"
877
+ }
878
+ })
879
+ FTO.registerEffector({
880
+ :name => 'Bang',
881
+ :description => _('Insert an exclamation mark ' +
882
+ '(!) character'),
883
+ :reMatch => '!!',
884
+ :code => lambda {
885
+ | eContext |
886
+ eContext.reuseArg = true
887
+ return '!'
888
+ }
889
+ })
890
+ FTO.registerEffector({
891
+ :name => 'Formfeed',
892
+ :description => _('Insert a form-feed control ' +
893
+ 'character'),
894
+ :reMatch => '!\^',
895
+ :code => lambda {
896
+ | eContext |
897
+ eContext.reuseArg = true
898
+ return "\014"
899
+ }
900
+ })
901
+ FTO.registerEffector({
902
+ :name => 'CR',
903
+ :description => _('Insert a carriage-return ' +
904
+ 'character'),
905
+ :reMatch => '!=',
906
+ :code => lambda {
907
+ | eContext |
908
+ eContext.reuseArg = true
909
+ return "\015"
910
+ }
911
+ })
912
+ FTO.registerEffector({
913
+ :name => 'LF',
914
+ :description => _('Insert a line-feed control ' +
915
+ 'character'),
916
+ :reMatch => '!,',
917
+ :code => lambda {
918
+ | eContext |
919
+ eContext.reuseArg = true
920
+ return "\012"
921
+ }
922
+ })
923
+ FTO.registerEffector({
924
+ :name => 'CRLF',
925
+ :description => _('Insert a carriage-return and ' +
926
+ 'a line-feed'),
927
+ :reMatch => '!/',
928
+ :code => lambda {
929
+ | eContext |
930
+ eContext.reuseArg = true
931
+ return "\015\012"
932
+ }
933
+ })
934
+
935
+ #
936
+ # Effectors that frob the argument list.
937
+ #
938
+ FTO.registerEffector({
939
+ :name => 'Reuse',
940
+ :description => _('Back the argument list up ' +
941
+ 'so the last argument gets re-used'),
942
+ :reMatch => '!-',
943
+ :code => lambda {
944
+ | eContext |
945
+ eContext.reuseArg = true
946
+ eContext.argList.unshift(eContext.usedArgs.pop) unless (eContext.usedArgs.empty?)
947
+ return ''
948
+ }
949
+ })
950
+ FTO.registerEffector({
951
+ :name => 'Skip',
952
+ :description => _('Skip over the next item in ' +
953
+ 'the argument list'),
954
+ :reMatch => '!\+',
955
+ :code => lambda {
956
+ | eContext |
957
+ eContext.usedArgs.push(eContext.argList.unshift) unless (eContext.argList.empty?)
958
+ return ''
959
+ }
960
+ })
961
+ FTO.registerEffector({
962
+ :name => 'ArgWidth',
963
+ :description => _('Specify a field width using ' +
964
+ 'an argument rather than a ' +
965
+ 'hard-coded value'),
966
+ :reMatch => '!#',
967
+ :code => lambda {
968
+ | eContext |
969
+ n = GetArgument.call(eContext)
970
+ return "!#{n.to_i.to_s}"
971
+ }
972
+ })
973
+ FTO.registerEffector({
974
+ :name => 'RepeatingChar',
975
+ :description => _('Insert n occurrences of ' +
976
+ 'a character'),
977
+ :reMatch => '!\d+\*.',
978
+ :reExtra => '!(\d+)\*(.)',
979
+ :code => ConvertRepeatChar,
980
+ })
981
+ FTO.registerEffector({
982
+ :name => 'Pluralise-English',
983
+ :description => _('Add an "s" or "es" suffix if ' +
984
+ 'the last argument used was not 1'),
985
+ :reMatch => '.?!%S',
986
+ :reExtra => '(.)?!%S',
987
+ :code => ConvertPlural,
988
+ })
989
+ FTO.registerEffector({
990
+ :name => 'is/are-English',
991
+ :description => _('Insert "is" or "are" depending ' +
992
+ 'on whether the last argument ' +
993
+ 'used was 1'),
994
+ :reMatch => '!%is',
995
+ :code => ConvertIsAre,
996
+ })
997
+ FTO.registerEffector({
998
+ :name => 'IS/ARE-English',
999
+ :description => _('Insert n occurrences of a ' +
1000
+ 'character'),
1001
+ :reMatch => '!%IS',
1002
+ :code => ConvertIsAre,
1003
+ })
1004
+
1005
+ FTO.registerEffector({
1006
+ :name => 'Fixed Window',
1007
+ :description => _('Force a right-justified field ' +
1008
+ 'width on the contents between ' +
1009
+ 'the effector delimiters'),
1010
+ :priority => 10,
1011
+ :reMatch => '!\d+<.*!>',
1012
+ :reExtra => '!(\d+<)(.*)(!>)',
1013
+ :fill => ' ',
1014
+ :truncate => :right,
1015
+ :justify => :right,
1016
+ :code => ConvertFixedWindow,
1017
+ })
1018
+ #
1019
+ # Strings
1020
+ #
1021
+ FTO.registerEffector({
1022
+ :name => 'String AS',
1023
+ :description => _('Insert a simple string'),
1024
+ :reMatch => '!\d*AS',
1025
+ :reExtra => '!(\d*)AS',
1026
+ :fill => ' ',
1027
+ :truncate => :right,
1028
+ :data => [:AS],
1029
+ :code => ConvertString,
1030
+ })
1031
+ FTO.registerEffector({
1032
+ :name => 'String AN',
1033
+ :description => _('Insert a simple string, ' +
1034
+ 'replacing non-printing ' +
1035
+ 'characters with "."'),
1036
+ :reMatch => '!\d*AN',
1037
+ :reExtra => '!(\d*)AN',
1038
+ :fill => ' ',
1039
+ :truncate => :right,
1040
+ :data => [:AS, :printable],
1041
+ :code => ConvertString,
1042
+ })
1043
+ FTO.registerEffector({
1044
+ :name => 'String AD',
1045
+ :description => _('Insert the first n characters ' +
1046
+ 'of a string, drawing n from the ' +
1047
+ 'argument list'),
1048
+ :reMatch => '!\d*AD',
1049
+ :reExtra => '!(\d*)AD',
1050
+ :fill => ' ',
1051
+ :truncate => :right,
1052
+ :data => [:lengthAsArg],
1053
+ :code => ConvertString,
1054
+ })
1055
+ FTO.registerEffector({
1056
+ :name => 'String AF',
1057
+ :description => _('Insert the first n characters ' +
1058
+ 'of a string, drawing n from the ' +
1059
+ 'argument list and replacing ' +
1060
+ 'non-printing characters with "."'),
1061
+ :reMatch => '!\d*AF',
1062
+ :reExtra => '!(\d*)AF',
1063
+ :fill => ' ',
1064
+ :truncate => :right,
1065
+ :data => [:lengthAsArg, :printable],
1066
+ :code => ConvertString,
1067
+ })
1068
+ FTO.registerEffector({
1069
+ :name => 'Byte: unsigned',
1070
+ :description => _('Insert the decimal ' +
1071
+ 'representation of an unsigned ' +
1072
+ '8-bit value, left space-filled ' +
1073
+ 'to the field width'),
1074
+ :reMatch => '!\d*UB',
1075
+ :reExtra => '!(\d*)UB',
1076
+ :mask => 0xFF,
1077
+ :code => ConvertNumeric,
1078
+ })
1079
+ FTO.registerEffector({
1080
+ :name => 'Byte: unsigned, zero-filled',
1081
+ :description => _('Insert the decimal ' +
1082
+ 'representation of an unsigned ' +
1083
+ '8-bit value, left zero-filled ' +
1084
+ 'to the field width'),
1085
+ :reMatch => '!\d*ZB',
1086
+ :reExtra => '!(\d*)ZB',
1087
+ :mask => 0xFF,
1088
+ :fill => '0',
1089
+ :justify => :right,
1090
+ :code => ConvertNumeric,
1091
+ })
1092
+ FTO.registerEffector({
1093
+ :name => 'Byte: signed',
1094
+ :description => _('Insert the decimal ' +
1095
+ 'representation of a signed ' +
1096
+ '8-bit value, left space-filled ' +
1097
+ 'to the field width'),
1098
+ :reMatch => '!\d*SB',
1099
+ :reExtra => '!(\d*)SB',
1100
+ :mask => 0xFF,
1101
+ :signbit => 0x80,
1102
+ :code => ConvertNumeric,
1103
+ })
1104
+ FTO.registerEffector({
1105
+ :name => 'Byte: octal',
1106
+ :description => _('Insert the octal ' +
1107
+ 'representation of an unsigned ' +
1108
+ '8-bit value, left zero-filled ' +
1109
+ 'to the field width (default ' +
1110
+ '3)'),
1111
+ :reMatch => '!\d*OB',
1112
+ :reExtra => '!(\d*)OB',
1113
+ :mask => 0xFF,
1114
+ :dWidth => 3,
1115
+ :fill => '0',
1116
+ :justify => :right,
1117
+ :truncate => :left,
1118
+ :data => [:octal],
1119
+ :code => ConvertNumeric,
1120
+ })
1121
+ FTO.registerEffector({
1122
+ :name => 'Byte: hex',
1123
+ :description => _('Insert the hexadecimal ' +
1124
+ 'representation of an unsigned ' +
1125
+ '8-bit value, left zero-filled ' +
1126
+ 'to the field width (default ' +
1127
+ '2)'),
1128
+ :reMatch => '!\d*XB',
1129
+ :reExtra => '!(\d*)XB',
1130
+ :mask => 0xFF,
1131
+ :dWidth => 2,
1132
+ :fill => '0',
1133
+ :justify => :right,
1134
+ :truncate => :left,
1135
+ :data => [:hex],
1136
+ :code => ConvertNumeric,
1137
+ })
1138
+ FTO.registerEffector({
1139
+ :name => 'Word: unsigned',
1140
+ :description => _('Insert the decimal ' +
1141
+ 'representation of an unsigned ' +
1142
+ '8-bit value, left space-filled ' +
1143
+ 'to the field width'),
1144
+ :reMatch => '!\d*UW',
1145
+ :reExtra => '!(\d*)UW',
1146
+ :mask => 0xFFFF,
1147
+ :code => ConvertNumeric,
1148
+ })
1149
+ FTO.registerEffector({
1150
+ :name => 'Word: unsigned, zero-filled',
1151
+ :description => _('Insert the decimal ' +
1152
+ 'representation of an unsigned ' +
1153
+ '8-bit value, left zero-filled ' +
1154
+ 'to the field width'),
1155
+ :reMatch => '!\d*ZW',
1156
+ :reExtra => '!(\d*)ZW',
1157
+ :mask => 0xFFFF,
1158
+ :fill => '0',
1159
+ :justify => :right,
1160
+ :code => ConvertNumeric,
1161
+ })
1162
+ FTO.registerEffector({
1163
+ :name => 'Word: signed',
1164
+ :description => _('Insert the decimal ' +
1165
+ 'representation of a signed ' +
1166
+ '16-bit value, left space-filled ' +
1167
+ 'to the field width'),
1168
+ :reMatch => '!\d*SW',
1169
+ :reExtra => '!(\d*)SW',
1170
+ :mask => 0xFFFF,
1171
+ :signbit => 0x8000,
1172
+ :code => ConvertNumeric,
1173
+ })
1174
+ FTO.registerEffector({
1175
+ :name => 'Word: octal',
1176
+ :description => _('Insert the octal ' +
1177
+ 'representation of an unsigned ' +
1178
+ '16-bit value, left zero-filled ' +
1179
+ 'to the field width (default ' +
1180
+ '6)'),
1181
+ :reMatch => '!\d*OW',
1182
+ :reExtra => '!(\d*)OW',
1183
+ :mask => 0xFFFF,
1184
+ :dWidth => 6,
1185
+ :fill => '0',
1186
+ :justify => :right,
1187
+ :truncate => :left,
1188
+ :data => [:octal],
1189
+ :code => ConvertNumeric,
1190
+ })
1191
+ FTO.registerEffector({
1192
+ :name => 'Word: hex',
1193
+ :description => _('Insert the hexadecimal ' +
1194
+ 'representation of an unsigned ' +
1195
+ '16-bit value, left zero-filled ' +
1196
+ 'to the field width (default ' +
1197
+ '4)'),
1198
+ :reMatch => '!\d*XW',
1199
+ :reExtra => '!(\d*)XW',
1200
+ :mask => 0xFFFF,
1201
+ :dWidth => 4,
1202
+ :fill => '0',
1203
+ :justify => :right,
1204
+ :truncate => :left,
1205
+ :data => [:hex],
1206
+ :code => ConvertNumeric,
1207
+ })
1208
+ FTO.registerEffector({
1209
+ :name => 'Long: unsigned',
1210
+ :description => _('Insert the decimal ' +
1211
+ 'representation of an unsigned ' +
1212
+ '8-bit value, left space-filled ' +
1213
+ 'to the field width'),
1214
+ :reMatch => '!\d*UL',
1215
+ :reExtra => '!(\d*)UL',
1216
+ :mask => 0xFFFFFFFF,
1217
+ :code => ConvertNumeric,
1218
+ })
1219
+ FTO.registerEffector({
1220
+ :name => 'Long: unsigned, zero-filled',
1221
+ :description => _('Insert the decimal ' +
1222
+ 'representation of an unsigned ' +
1223
+ '8-bit value, left zero-filled ' +
1224
+ 'to the field width'),
1225
+ :reMatch => '!\d*ZL',
1226
+ :reExtra => '!(\d*)ZL',
1227
+ :mask => 0xFFFFFFFF,
1228
+ :fill => '0',
1229
+ :justify => :right,
1230
+ :code => ConvertNumeric,
1231
+ })
1232
+ FTO.registerEffector({
1233
+ :name => 'Long: signed',
1234
+ :description => _('Insert the decimal ' +
1235
+ 'representation of a signed ' +
1236
+ '32-bit value, left space-filled ' +
1237
+ 'to the field width'),
1238
+ :reMatch => '!\d*SL',
1239
+ :reExtra => '!(\d*)SL',
1240
+ :mask => 0xFFFFFFFF,
1241
+ :signbit => 0x80000000,
1242
+ :code => ConvertNumeric,
1243
+ })
1244
+ FTO.registerEffector({
1245
+ :name => 'Long: octal',
1246
+ :description => _('Insert the octal ' +
1247
+ 'representation of an unsigned ' +
1248
+ '32-bit value, left zero-filled ' +
1249
+ 'to the field width (default ' +
1250
+ '11)'),
1251
+ :reMatch => '!\d*OL',
1252
+ :reExtra => '!(\d*)OL',
1253
+ :mask => 0xFFFFFFFF,
1254
+ :dWidth => 11,
1255
+ :fill => '0',
1256
+ :justify => :right,
1257
+ :truncate => :left,
1258
+ :data => [:octal],
1259
+ :code => ConvertNumeric,
1260
+ })
1261
+ FTO.registerEffector({
1262
+ :name => 'Long: hex',
1263
+ :description => _('Insert the hexadecimal ' +
1264
+ 'representation of an unsigned ' +
1265
+ '32-bit value, left zero-filled ' +
1266
+ 'to the field width (default ' +
1267
+ '8)'),
1268
+ :reMatch => '!\d*XL',
1269
+ :reExtra => '!(\d*)XL',
1270
+ :mask => 0xFFFFFFFF,
1271
+ :dWidth => 8,
1272
+ :fill => '0',
1273
+ :justify => :right,
1274
+ :truncate => :left,
1275
+ :data => [:hex],
1276
+ :code => ConvertNumeric,
1277
+ })
1278
+ FTO.registerEffector({
1279
+ :name => 'Quadword: unsigned',
1280
+ :description => _('Insert the decimal ' +
1281
+ 'representation of an unsigned ' +
1282
+ '8-bit value, left space-filled ' +
1283
+ 'to the field width'),
1284
+ :reMatch => '!\d*UQ',
1285
+ :reExtra => '!(\d*)UQ',
1286
+ :mask => 0xFFFFFFFFFFFFFFFF,
1287
+ :code => ConvertNumeric,
1288
+ })
1289
+ FTO.registerEffector({
1290
+ :name => 'Quadword: unsigned, zero-filled',
1291
+ :description => _('Insert the decimal ' +
1292
+ 'representation of an unsigned ' +
1293
+ '8-bit value, left zero-filled ' +
1294
+ 'to the field width'),
1295
+ :reMatch => '!\d*ZQ',
1296
+ :reExtra => '!(\d*)ZQ',
1297
+ :mask => 0xFFFFFFFFFFFFFFFF,
1298
+ :fill => '0',
1299
+ :justify => :right,
1300
+ :code => ConvertNumeric,
1301
+ })
1302
+ FTO.registerEffector({
1303
+ :name => 'Quadword: signed',
1304
+ :description => _('Insert the decimal ' +
1305
+ 'representation of a signed ' +
1306
+ '64-bit value, left space-filled ' +
1307
+ 'to the field width'),
1308
+ :reMatch => '!\d*SQ',
1309
+ :reExtra => '!(\d*)SQ',
1310
+ :mask => 0xFFFFFFFFFFFFFFFF,
1311
+ :signbit => 0x8000000000000000,
1312
+ :code => ConvertNumeric,
1313
+ })
1314
+ FTO.registerEffector({
1315
+ :name => 'Quadword: octal',
1316
+ :description => _('Insert the octal ' +
1317
+ 'representation of an unsigned ' +
1318
+ '64-bit value, left zero-filled ' +
1319
+ 'to the field width (default ' +
1320
+ '22)'),
1321
+ :reMatch => '!\d*OQ',
1322
+ :reExtra => '!(\d*)OQ',
1323
+ :mask => 0xFFFFFFFFFFFFFFFF,
1324
+ :dWidth => 22,
1325
+ :fill => '0',
1326
+ :justify => :right,
1327
+ :truncate => :left,
1328
+ :data => [:octal],
1329
+ :code => ConvertNumeric,
1330
+ })
1331
+ FTO.registerEffector({
1332
+ :name => 'Quadword: hex',
1333
+ :description => _('Insert the hexadecimal ' +
1334
+ 'representation of an unsigned ' +
1335
+ '64-bit value, left zero-filled ' +
1336
+ 'to the field width (default ' +
1337
+ '16)'),
1338
+ :reMatch => '!\d*XQ',
1339
+ :reExtra => '!(\d*)XQ',
1340
+ :mask => 0xFFFFFFFFFFFFFFFF,
1341
+ :dWidth => 16,
1342
+ :fill => '0',
1343
+ :justify => :right,
1344
+ :truncate => :left,
1345
+ :data => [:hex],
1346
+ :code => ConvertNumeric,
1347
+ })
1348
+
1349
+ end # module FTO