wlang 0.8.5 → 0.9.1

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.
Files changed (151) hide show
  1. data/CHANGELOG.rdoc +65 -0
  2. data/README.rdoc +85 -31
  3. data/bin/wlang +5 -0
  4. data/doc/specification/dialect.wtpl +14 -0
  5. data/doc/specification/dialects.wtpl +3 -0
  6. data/doc/specification/glossary.wtpl +4 -4
  7. data/doc/specification/rulesets.wtpl +9 -9
  8. data/doc/specification/specification.css +1 -0
  9. data/doc/specification/specification.html +68 -5
  10. data/doc/specification/specification.wtpl +12 -12
  11. data/doc/specification/specification.yml +9 -9
  12. data/doc/specification/symbols.wtpl +8 -8
  13. data/lib/wlang.rb +297 -75
  14. data/lib/wlang/dialect.rb +7 -3
  15. data/lib/wlang/dialects/coderay_dialect.rb +4 -4
  16. data/lib/wlang/dialects/plain_text_dialect.rb +13 -19
  17. data/lib/wlang/dialects/redcloth_dialect.rb +16 -0
  18. data/lib/wlang/dialects/ruby_dialect.rb +16 -2
  19. data/lib/wlang/dialects/standard_dialects.rb +20 -0
  20. data/lib/wlang/dialects/xhtml_dialect.rb +24 -1
  21. data/lib/wlang/encoder.rb +1 -1
  22. data/lib/wlang/encoder_set.rb +5 -0
  23. data/lib/wlang/errors.rb +70 -6
  24. data/lib/wlang/ext/hash_methodize.rb +13 -0
  25. data/lib/wlang/{ruby_extensions.rb → ext/string.rb} +9 -5
  26. data/lib/wlang/hash_scope.rb +89 -0
  27. data/lib/wlang/hosted_language.rb +146 -0
  28. data/lib/wlang/parser.rb +189 -126
  29. data/lib/wlang/parser_state.rb +94 -0
  30. data/lib/wlang/rule_set.rb +16 -3
  31. data/lib/wlang/rulesets/basic_ruleset.rb +14 -6
  32. data/lib/wlang/rulesets/buffering_ruleset.rb +20 -29
  33. data/lib/wlang/rulesets/context_ruleset.rb +16 -20
  34. data/lib/wlang/rulesets/imperative_ruleset.rb +4 -4
  35. data/lib/wlang/rulesets/ruleset_utils.rb +26 -5
  36. data/lib/wlang/template.rb +16 -34
  37. data/lib/wlang/wlang_command.rb +4 -7
  38. data/lib/wlang/wlang_command_options.rb +5 -0
  39. data/test/blackbox/basic/execution_1.exp +1 -0
  40. data/test/blackbox/basic/execution_1.tpl +1 -0
  41. data/test/blackbox/basic/execution_2.exp +1 -0
  42. data/test/blackbox/basic/execution_2.tpl +1 -0
  43. data/test/blackbox/basic/execution_3.exp +1 -0
  44. data/test/blackbox/basic/execution_3.tpl +1 -0
  45. data/test/blackbox/basic/execution_4.exp +1 -0
  46. data/test/blackbox/basic/execution_4.tpl +1 -0
  47. data/test/blackbox/basic/inclusion_1.exp +1 -0
  48. data/test/blackbox/basic/inclusion_1.tpl +1 -0
  49. data/test/blackbox/basic/inclusion_2.exp +1 -0
  50. data/test/blackbox/basic/inclusion_2.tpl +1 -0
  51. data/test/blackbox/basic/injection_1.exp +1 -0
  52. data/test/blackbox/basic/injection_1.tpl +1 -0
  53. data/test/blackbox/basic/injection_2.exp +1 -0
  54. data/test/blackbox/basic/injection_2.tpl +1 -0
  55. data/test/blackbox/basic/modulation_1.exp +1 -0
  56. data/test/blackbox/basic/modulation_1.tpl +1 -0
  57. data/test/blackbox/basic/modulation_2.exp +1 -0
  58. data/test/blackbox/basic/modulation_2.tpl +1 -0
  59. data/test/blackbox/basic/recursive_app_1.exp +1 -0
  60. data/test/blackbox/basic/recursive_app_1.tpl +1 -0
  61. data/test/blackbox/basic/recursive_app_2.exp +1 -0
  62. data/test/blackbox/basic/recursive_app_2.tpl +1 -0
  63. data/test/blackbox/buffering/data_1.rb +1 -0
  64. data/test/blackbox/buffering/data_assignment_1.exp +1 -0
  65. data/test/blackbox/buffering/data_assignment_1.tpl +1 -0
  66. data/test/blackbox/buffering/data_assignment_2.exp +1 -0
  67. data/test/blackbox/buffering/data_assignment_2.tpl +1 -0
  68. data/test/blackbox/buffering/data_assignment_3.exp +1 -0
  69. data/test/blackbox/buffering/data_assignment_3.tpl +1 -0
  70. data/test/blackbox/buffering/data_assignment_4.exp +1 -0
  71. data/test/blackbox/buffering/data_assignment_4.tpl +1 -0
  72. data/test/blackbox/buffering/input_1.exp +1 -0
  73. data/test/blackbox/buffering/input_1.tpl +1 -0
  74. data/test/blackbox/buffering/input_2.exp +1 -0
  75. data/test/blackbox/buffering/input_2.tpl +1 -0
  76. data/test/blackbox/buffering/input_3.exp +1 -0
  77. data/test/blackbox/buffering/input_3.tpl +1 -0
  78. data/test/blackbox/buffering/input_inclusion.exp +1 -0
  79. data/test/blackbox/buffering/input_inclusion.tpl +1 -0
  80. data/test/blackbox/buffering/input_inclusion_1.exp +0 -0
  81. data/test/blackbox/buffering/input_inclusion_1.tpl +1 -0
  82. data/test/blackbox/buffering/input_inclusion_2.exp +1 -0
  83. data/test/blackbox/buffering/input_inclusion_2.tpl +1 -0
  84. data/test/blackbox/buffering/input_inclusion_3.exp +1 -0
  85. data/test/blackbox/buffering/input_inclusion_3.tpl +1 -0
  86. data/test/blackbox/buffering/input_inclusion_4.exp +0 -0
  87. data/test/blackbox/buffering/input_inclusion_4.tpl +1 -0
  88. data/test/blackbox/buffering/input_inclusion_5.exp +1 -0
  89. data/test/blackbox/buffering/input_inclusion_5.tpl +1 -0
  90. data/test/blackbox/buffering/input_inclusion_6.exp +1 -0
  91. data/test/blackbox/buffering/input_inclusion_6.tpl +1 -0
  92. data/test/blackbox/buffering/input_inclusion_7.exp +0 -0
  93. data/test/blackbox/buffering/input_inclusion_7.tpl +1 -0
  94. data/test/blackbox/buffering/text_1.txt +1 -0
  95. data/test/blackbox/buffering/wlang.txt +1 -0
  96. data/test/blackbox/context/assignment_1.exp +1 -0
  97. data/test/blackbox/context/assignment_1.tpl +1 -0
  98. data/test/blackbox/context/assignment_2.exp +1 -0
  99. data/test/blackbox/context/assignment_2.tpl +1 -0
  100. data/test/blackbox/context/assignment_3.exp +2 -0
  101. data/test/blackbox/context/assignment_3.tpl +2 -0
  102. data/test/blackbox/context/assignment_4.exp +1 -0
  103. data/test/blackbox/context/assignment_4.tpl +1 -0
  104. data/test/blackbox/context/block_assignment_1.exp +1 -0
  105. data/test/blackbox/context/block_assignment_1.tpl +1 -0
  106. data/test/blackbox/context/block_assignment_2.exp +1 -0
  107. data/test/blackbox/context/block_assignment_2.tpl +1 -0
  108. data/test/blackbox/context/modulo_assignment_1.exp +1 -0
  109. data/test/blackbox/context/modulo_assignment_1.tpl +1 -0
  110. data/test/blackbox/context/modulo_assignment_2.exp +1 -0
  111. data/test/blackbox/context/modulo_assignment_2.tpl +1 -0
  112. data/test/blackbox/data_1.rb +1 -0
  113. data/test/blackbox/test_all.rb +59 -0
  114. data/test/spec/basic_object.spec +40 -0
  115. data/test/spec/global_extensions.rb +2 -0
  116. data/test/spec/hash_scope.spec +76 -0
  117. data/test/spec/redcloth_dialect.spec +24 -0
  118. data/test/spec/test_all.rb +8 -0
  119. data/test/spec/wlang.spec +53 -0
  120. data/test/spec/xhtml_dialect.spec +23 -0
  121. data/test/{test_all.rb → unit/test_all.rb} +1 -1
  122. data/test/{wlang → unit/wlang}/anagram_bugs_test.rb +2 -2
  123. data/test/{wlang → unit/wlang}/basic_ruleset_test.rb +1 -1
  124. data/test/{wlang → unit/wlang}/buffering_ruleset_test.rb +4 -4
  125. data/test/{wlang → unit/wlang}/buffering_template1.wtpl +0 -0
  126. data/test/{wlang → unit/wlang}/buffering_template2.wtpl +0 -0
  127. data/test/{wlang → unit/wlang}/buffering_template3.wtpl +0 -0
  128. data/test/unit/wlang/buffering_template4.wtpl +1 -0
  129. data/test/unit/wlang/buffering_template5.wtpl +1 -0
  130. data/test/{wlang → unit/wlang}/context_ruleset_test.rb +0 -0
  131. data/test/{wlang → unit/wlang}/data.rb +0 -0
  132. data/test/{wlang → unit/wlang}/encoder_set_test.rb +0 -0
  133. data/test/{wlang → unit/wlang}/imperative_ruleset_test.rb +0 -0
  134. data/test/{wlang → unit/wlang}/intelligent_buffer_test.rb +0 -0
  135. data/test/{wlang → unit/wlang}/othersymbols_test.rb +0 -0
  136. data/test/{wlang → unit/wlang}/parser_test.rb +10 -11
  137. data/test/{wlang → unit/wlang}/plain_text_dialect_test.rb +0 -0
  138. data/test/{wlang → unit/wlang}/ruby_dialect_test.rb +0 -0
  139. data/test/{wlang → unit/wlang}/ruby_expected.rb +0 -0
  140. data/test/{wlang → unit/wlang}/ruby_template.wrb +0 -0
  141. data/test/{wlang → unit/wlang}/ruleset_utils_test.rb +0 -0
  142. data/test/{wlang → unit/wlang}/specification_examples_test.rb +2 -2
  143. data/test/{wlang → unit/wlang}/test_utils.rb +1 -1
  144. data/test/{wlang → unit/wlang}/wlang_test.rb +0 -0
  145. metadata +135 -42
  146. data/lib/wlang/basic_object.rb +0 -19
  147. data/lib/wlang/parser_context.rb +0 -139
  148. data/test/sandbox.rb +0 -1
  149. data/test/wlang/buffering_template4.wtpl +0 -1
  150. data/test/wlang/buffering_template5.wtpl +0 -1
  151. data/test/wlang/parser_context_test.rb +0 -29
@@ -1,27 +1,27 @@
1
1
  ---
2
2
  title: WLang
3
- version: 0.8.4
3
+ version: 0.9.1
4
4
  sections:
5
- - id: about
5
+ - identifier: about
6
6
  name: About
7
7
  file: about.rdoc
8
- - id: overview
8
+ - identifier: overview
9
9
  name: Overview
10
10
  file: overview.rdoc
11
- - id: rulesets
11
+ - identifier: rulesets
12
12
  name: Rulesets
13
13
  file: rulesets.wtpl
14
14
  links: 'spec["rulesets"].reverse'
15
- - id: dialects
15
+ - identifier: dialects
16
16
  name: Dialects
17
17
  file: dialects.wtpl
18
- - id: hosting
18
+ - identifier: hosting
19
19
  name: Hosting language
20
20
  file: hosting.rdoc
21
- - id: glossary
21
+ - identifier: glossary
22
22
  name: Glossary
23
23
  file: glossary.wtpl
24
- - id: symbols
24
+ - identifier: symbols
25
25
  name: Tag symbols
26
26
  file: symbols.wtpl
27
27
  glossary:
@@ -289,7 +289,7 @@ rulesets:
289
289
  # #={wlang/active-string}{...}{...}
290
290
  - name: "block-assignment<br/>(third block is optional)"
291
291
  symbol: "="
292
- signature: "#={wlang/active-string <as x>}{...}{...}"
292
+ signature: "#={wlang/active-string}{...}{...}"
293
293
  definition: |-
294
294
  <tt>%={+{@parser.current_dialect} as #1}{#2}{#3}</tt>
295
295
  # ^={wlang/active-string as x}{...}{...}
@@ -5,12 +5,12 @@
5
5
  <th class="meaning">meaning</th>
6
6
  <th class="remark">remark</th>
7
7
  </tr>
8
- *{spec/symbols as s}{
9
- <tr>
10
- <td><em>${s/name}</em></td>
11
- <td>${s/symbol}</td>
12
- <td>${s/meaning}</td>
13
- <td>${s/remark}</td>
14
- </tr>
15
- }
8
+ *{spec.symbols as s}{
9
+ <tr>
10
+ <td><em>${s.name}</em></td>
11
+ <td>${s.symbol}</td>
12
+ <td>${s.meaning}</td>
13
+ <td>${s.remark}</td>
14
+ </tr>
15
+ }
16
16
  </table>
@@ -1,4 +1,4 @@
1
- require 'wlang/ruby_extensions'
1
+ require 'wlang/ext/string'
2
2
  require 'stringio'
3
3
  require 'wlang/rule'
4
4
  require 'wlang/rule_set'
@@ -6,8 +6,10 @@ require 'wlang/encoder_set'
6
6
  require 'wlang/dialect'
7
7
  require 'wlang/dialect_dsl'
8
8
  require 'wlang/dialect_loader'
9
+ require 'wlang/hosted_language'
10
+ require 'wlang/hash_scope'
9
11
  require 'wlang/parser'
10
- require 'wlang/parser_context'
12
+ require 'wlang/parser_state'
11
13
  require 'wlang/intelligent_buffer'
12
14
 
13
15
  #
@@ -18,44 +20,48 @@ require 'wlang/intelligent_buffer'
18
20
  module WLang
19
21
 
20
22
  # Current version of WLang
21
- VERSION = "0.8.5".freeze
23
+ VERSION = "0.9.1".freeze
24
+
25
+ ######################################################################## About files and extensions
26
+
27
+ # Regular expression for file extensions
28
+ FILE_EXTENSION_REGEXP = /^\.[a-zA-Z0-9]+$/
29
+
30
+ # Checks that _ext_ is a valid file extension or raises an ArgumentError
31
+ def self.check_file_extension(ext)
32
+ raise ArgumentError, "Invalid file extension #{ext} (/^\.[a-zA-Z-0-9]+$/ expected)", caller\
33
+ unless FILE_EXTENSION_REGEXP =~ ext
34
+ end
35
+
36
+ # Raises an ArgumentError unless file is a real readable file
37
+ def self.check_readable_file(file)
38
+ raise ArgumentError, "File #{file} is not readable or not a file"\
39
+ unless File.exists?(file) and File.file?(file) and File.readable?(file)
40
+ end
41
+
42
+ ######################################################################## About dialects
22
43
 
23
44
  # Reusable string for building dialect name based regexps
24
45
  DIALECT_NAME_REGEXP_STR = "[-a-z]+"
25
46
 
26
- #
27
47
  # Regular expression for dialect names.
28
- #
29
48
  DIALECT_NAME_REGEXP = /^([-a-z]+)*$/
30
49
 
31
50
  # Reusable string for building dialect name based regexps
32
51
  QUALIFIED_DIALECT_NAME_REGEXP_STR = "[-a-z]+([\/][-a-z]+)*"
33
52
 
34
- #
35
53
  # Regular expression for dialect qualified names. Dialect qualified names are
36
54
  # '/' seperated names, where a name is [-a-z]+.
37
- # Examples: wlang/xhtml/uri, wlang/plain-text, ...
38
55
  #
56
+ # Examples: wlang/xhtml/uri, wlang/plain-text, ...
39
57
  QUALIFIED_DIALECT_NAME_REGEXP = /^[-a-z]+([\/][-a-z]+)*$/
40
58
 
41
- # Reusable string for building encoder name based regexps
42
- ENCODER_NAME_REGEXP_STR = "[-a-z]+"
43
-
44
- #
45
- # Regular expression for encoder names.
46
- #
47
- ENCODER_NAME_REGEXP = /^([-a-z]+)*$/
48
-
49
- #
50
- # Regular expression for encoder qualified names. Encoder qualified names are
51
- # '/' seperated names, where a name is [-a-z]+.
52
- # Examples: xhtml/entities-encoding, sql/single-quoting, ...
53
- #
54
- QUALIFIED_ENCODER_NAME_REGEXP = /^([-a-z]+)([\/][-a-z]+)*$/
55
-
56
- # Reusable string for building qualified encoder name based regexps
57
- QUALIFIED_ENCODER_NAME_REGEXP_STR = "[-a-z]+([\/][-a-z]+)*"
58
-
59
+ # Checks that _name_ is a valid qualified dialect name or raises an ArgumentError
60
+ def self.check_qualified_dialect_name(name)
61
+ raise ArgumentError, "Invalid dialect qualified name #{name} (/^[-a-z]+([\/][-a-z]+)*$/ expected)", caller\
62
+ unless QUALIFIED_DIALECT_NAME_REGEXP =~ name
63
+ end
64
+
59
65
  #
60
66
  # Provides installed {file extension => dialect} mappings. File extensions
61
67
  # (keys) contain the first dot (like .wtpl, .whtml, ...). Dialects (values) are
@@ -63,21 +69,66 @@ module WLang
63
69
  #
64
70
  FILE_EXTENSIONS = {}
65
71
 
66
- #
67
- # Provides installed {file extension => data loader} mapping. File extensions
68
- # (keys) contain the first dot (like .wtpl, .whtml, ...). Data loades are
69
- # Proc instances that take a single |uri| argument.
70
- #
71
- DATA_EXTENSIONS = {}
72
-
73
72
  #
74
73
  # Main anonymous dialect. All installed dialects are children of this one,
75
74
  # which is anonymous because it does not appear in qualified names.
76
75
  #
77
76
  @dialect = Dialect.new("", nil)
77
+
78
+ # Returns the root of the dialect tree
79
+ def self.dialect_tree
80
+ @dialect
81
+ end
78
82
 
79
83
  #
80
- # Installs or query a dialect.
84
+ # Maps a file extension to a dialect qualified name.
85
+ #
86
+ # Example:
87
+ #
88
+ # # We create an 'example' dialect
89
+ # WLang::dialect('example') do
90
+ # # see WLang::dialect about creating a dialect
91
+ # end
92
+ #
93
+ # # We map .wex file extensions to our new dialect
94
+ # WLang::file_extension_map('.wex', 'example')
95
+ #
96
+ # This method raises an ArgumentError if the extension or dialect qualified
97
+ # name is not valid.
98
+ #
99
+ def self.file_extension_map(extension, dialect_qname)
100
+ check_file_extension(extension)
101
+ check_qualified_dialect_name(dialect_qname)
102
+ WLang::FILE_EXTENSIONS[extension] = dialect_qname
103
+ end
104
+
105
+ #
106
+ # Infers a dialect from a file extension. Returns nil if no dialect is currently
107
+ # mapped to the given extension (see file_extension_map)
108
+ #
109
+ # This method never raises errors.
110
+ #
111
+ def self.infer_dialect(uri)
112
+ WLang::FILE_EXTENSIONS[File.extname(uri)]
113
+ end
114
+
115
+ #
116
+ # Ensures, installs or query a dialect.
117
+ #
118
+ # <b>When name is a Dialect</b>, returns it immediately. This helper is provided
119
+ # for methods that accept both qualified dialect name and dialect instance
120
+ # arguments. Calling <code>WLang::dialect(arg)</code> ensures that the result will
121
+ # be a Dialect instance in all cases (if the arg is valid).
122
+ #
123
+ # Example:
124
+ #
125
+ # # This methods does something with a wlang dialect. _dialect_ argument may
126
+ # # be a Dialect instance or a qualified dialect name.
127
+ # def my_method(dialect = 'wlang/active-string')
128
+ # # ensures the Dialect instance or raises an ArgumentError if the dialect
129
+ # # qualified name is invalid (returns nil otherwise !)
130
+ # dialect = WLang::dialect(dialect)
131
+ # end
81
132
  #
82
133
  # <b>When called with a block</b>, this method installs a _wlang_ dialect under
83
134
  # _name_ (which cannot be qualified). Extensions can be provided to let _wlang_
@@ -85,56 +136,219 @@ module WLang
85
136
  # is interpreted as code in the dialect DSL (domain specific language, see
86
137
  # WLang::Dialect::DSL). Returns nil in this case.
87
138
  #
139
+ # Example:
140
+ #
141
+ # # New dialect with 'my_dialect' qualified name and automatically installed
142
+ # # to recognize '.wmyd' file extensions
143
+ # WLang::dialect("my_dialect", '.wmyd') do
144
+ # # see WLang::Dialect::DSL for this part of the code
145
+ # end
146
+ #
88
147
  # <b>When called without a block</b> this method returns a Dialect instance
89
148
  # installed under name (which can be a qualified name). Extensions are ignored
90
149
  # in this case. Returns nil if not found, a Dialect instance otherwise.
91
150
  #
151
+ # Example:
152
+ #
153
+ # # Lookup for the 'wlang/xhtml' dialect
154
+ # wxhtml = WLang::dialect('wlang/xhtml')
155
+ #
156
+ # This method raises an ArgumentError if
157
+ # * _name_ is not a valid dialect qualified name
158
+ # * any of the file extension in _extensions_ is invalid
159
+ #
92
160
  def self.dialect(name, *extensions, &block)
161
+ # first case, already a dialect
162
+ return name if Dialect===name
163
+
164
+ # other cases, argument validations
165
+ check_qualified_dialect_name(name)
166
+ extensions.each {|ext| check_file_extension(ext)}
167
+
93
168
  if block_given?
169
+ # first case, dialect installation
94
170
  raise "Unsupported qualified names in dialect installation"\
95
171
  unless name.index('/').nil?
96
- Dialect::DSL.new(@dialect).dialect(name, *extensions, &block).build!
172
+ Dialect::DSL.new(@dialect).dialect(name, *extensions, &block)
97
173
  else
174
+ # second case, dialect lookup
98
175
  @dialect.dialect(name)
99
176
  end
100
177
  end
101
178
 
179
+ ######################################################################## About encoders
180
+
181
+ # Reusable string for building encoder name based regexps
182
+ ENCODER_NAME_REGEXP_STR = "[-a-z]+"
183
+
184
+ # Regular expression for encoder names.
185
+ ENCODER_NAME_REGEXP = /^([-a-z]+)*$/
186
+
187
+ # Reusable string for building qualified encoder name based regexps
188
+ QUALIFIED_ENCODER_NAME_REGEXP_STR = "[-a-z]+([\/][-a-z]+)*"
189
+
190
+ # Regular expression for encoder qualified names. Encoder qualified names are
191
+ # '/' seperated names, where a name is [-a-z]+.
192
+ #
193
+ # Examples: xhtml/entities-encoding, sql/single-quoting, ...
194
+ QUALIFIED_ENCODER_NAME_REGEXP = /^([-a-z]+)([\/][-a-z]+)*$/
195
+
196
+ # Checks that _name_ is a valid qualified encoder name or raises an ArgumentError
197
+ def self.check_qualified_encoder_name(name)
198
+ raise ArgumentError, "Invalid encoder qualified name #{name} (/^[-a-z]+([\/][-a-z]+)*$/ expected)", caller\
199
+ unless QUALIFIED_ENCODER_NAME_REGEXP =~ name
200
+ end
201
+
202
+ #
203
+ # Returns an encoder installed under a qualified name. Returns nil if not
204
+ # found. If name is already an Encoder instance, returns it immediately.
205
+ #
206
+ # Example:
207
+ #
208
+ # encoder = WLang::encoder('xhtml/entities-encoding')
209
+ # encoder.encode('something that needs html entities escaping')
210
+ #
211
+ # This method raises an ArgumentError if _name_ is not a valid encoder qualified
212
+ # name.
213
+ #
214
+ def self.encoder(name)
215
+ check_qualified_encoder_name(name)
216
+ @dialect.encoder(name)
217
+ end
218
+
219
+ #
220
+ # Shortcut for
102
221
  #
103
- # Adds a data loader for file extensions.
222
+ # WLang::encoder(encoder_qname).encode(source, options)
223
+ #
224
+ # This method raises an ArgumentError
225
+ # * if _source_ is not a String
226
+ # * if the encoder qualified name is invalid
227
+ #
228
+ # It raises a WLang::Error if the encoder cannot be found
229
+ #
230
+ def self.encode(source, encoder_qname, options = {})
231
+ raise ArgumentError, "String expected for source" unless String===source
232
+ check_qualified_encoder_name(encoder_qname)
233
+ encoder = WLang::encoder(encoder_qname)
234
+ raise WLang::Error, "Unable to find encoder #{encoder_qname}" if encoder.nil?
235
+ encoder.encode(source, options)
236
+ end
237
+
238
+ ######################################################################## About data loading
239
+
240
+ #
241
+ # Provides installed {file extension => data loader} mapping. File extensions
242
+ # (keys) contain the first dot (like .wtpl, .whtml, ...). Data loades are
243
+ # Proc instances that take a single |uri| argument.
244
+ #
245
+ DATA_EXTENSIONS = {}
246
+
247
+ #
248
+ # Adds a data loader for file extensions. A data loader is a block of arity 1,
249
+ # taking a file as parameter and returning data decoded from the file.
250
+ #
251
+ # Example:
252
+ #
253
+ # # We have some MyXMLDataLoader class that is able to create a ruby object
254
+ # # from things expressed .xml files
255
+ # WLang::data_loader('.xml') {|file|
256
+ # MyXMLDataLaoder.parse_file(file)
257
+ # }
258
+ #
259
+ # # Later in a template (see the buffering ruleset that gives you <<={...})
260
+ # <<={resources.xml as resources}
261
+ # <html>
262
+ # *{resources as r}{
263
+ # ...
264
+ # }
265
+ # </html>
266
+ #
267
+ # This method raises an ArgumentError if
268
+ # * no block is given or if the block is not of arity 1
269
+ # * any of the file extensions in _exts_ is invalid
104
270
  #
105
271
  def self.data_loader(*exts, &block)
106
- raise(ArgumentError, "Block expected") unless block_given?
107
- raise(ArgumentError, "Block of arity 1 expected") unless block.arity==1
108
- exts.each do |ext|
109
- DATA_EXTENSIONS[ext] = block
110
- end
272
+ raise(ArgumentError, "WLang::data_loader expects a block") unless block_given?
273
+ raise(ArgumentError, "WLang::data_loader expects a block of arity 1") unless block.arity==1
274
+ exts.each {|ext| check_file_extension(ext) }
275
+ exts.each {|ext| DATA_EXTENSIONS[ext] = block}
111
276
  end
112
277
 
113
278
  #
114
279
  # Loads data from a given URI. If _extension_ is omitted, tries to infer it
115
280
  # from the uri, otherwise use it directly. Returns loaded data.
116
281
  #
282
+ # This method raises a WLang::Error if no data loader is installed for the found
283
+ # extension. It raises an ArgumentError if the file extension is invalid.
284
+ #
117
285
  def self.load_data(uri, extension=nil)
118
- if extension.nil?
119
- extension = File.extname(uri)
120
- raise("Unable to infer data loader from #{uri}") if extension.nil?
121
- end
286
+ check_file_extension(extension = extension.nil? ? File.extname(uri) : extension)
122
287
  loader = DATA_EXTENSIONS[extension]
123
- raise("No data loader for #{extension}") if loader.nil?
288
+ raise ::WLang::Error("No data loader for #{extension}") if loader.nil?
124
289
  loader.call(uri)
125
290
  end
126
291
 
127
- # Infers a dialect from a file extension
128
- def self.infer_dialect(uri)
129
- WLang::FILE_EXTENSIONS[File.extname(uri)]
130
- end
292
+ ######################################################################## About templates and instantiations
131
293
 
132
294
  #
133
- # Returns an encoder installed under a qualified name. Returns nil if not
134
- # found.
295
+ # Factors a template instance for a given string source, dialect (default to
296
+ # 'wlang/active-string') and block symbols (default to :braces)
135
297
  #
136
- def self.encoder(name)
137
- @dialect.encoder(name)
298
+ # Example:
299
+ #
300
+ # # The template source code must be interpreted as wlang/xhtml
301
+ # template = WLang::template('<p>Hello ${who}!</p>', 'wlang/xhtml')
302
+ # str = template.instantiate(:hello => 'world')
303
+ #
304
+ # # We may also use other block symbols...
305
+ # template = WLang::template('<p>Hello $(who)!</p>', 'wlang/xhtml', :parentheses)
306
+ # str = template.instantiate(:hello => 'world')
307
+ #
308
+ # This method raises an ArgumentError if
309
+ # * _source_ is not a String
310
+ # * _dialect_ is not a valid dialect qualified name or Dialect instance
311
+ # * _block_symbols_ is not in [:braces, :brackets, :parentheses]
312
+ #
313
+ def self.template(source, dialect = 'wlang/active-string', block_symbols = :braces)
314
+ raise ArgumentError, "String expected for source" unless String===source
315
+ raise ArgumentError, "Invalid symbols for block #{block_symbols}"\
316
+ unless ::WLang::Template::BLOCK_SYMBOLS.keys.include?(block_symbols)
317
+ template = Template.new(source, WLang::dialect(dialect), block_symbols)
318
+ end
319
+
320
+ #
321
+ # Factors a template instance for a given file, optional dialect (if nil is
322
+ # passed, the dialect is infered from the extension) and block symbols
323
+ # (default to :braces)
324
+ #
325
+ # Example:
326
+ #
327
+ # # the file index.wtpl is a wlang source code in 'wlang/xhtml' dialect
328
+ # # (automatically infered from file extension)
329
+ # template = WLang::template('index.wtpl')
330
+ # puts template.instantiate(:who => 'world') # puts 'Hello world!'
331
+ #
332
+ # This method raises an ArgumentError
333
+ # * if _file_ does not exists, is not a file or is not readable
334
+ # * if _dialect_ is not a valid qualified dialect name, Dialect instance, or nil
335
+ # * _block_symbols_ is not in [:braces, :brackets, :parentheses]
336
+ #
337
+ # It raises a WLang::Error
338
+ # * if no dialect can be infered from the file extension (if _dialect_ was nil)
339
+ #
340
+ def self.file_template(file, dialect = nil, block_symbols = :braces)
341
+ check_readable_file(file)
342
+
343
+ # Check the dialect
344
+ dialect = self.infer_dialect(file) if dialect.nil?
345
+ raise WLang::Error, "No known dialect for file extension '#{File.extname(file)}'\n"\
346
+ "Known extensions are: " << WLang::FILE_EXTENSIONS.keys.join(", ") if dialect.nil?
347
+
348
+ # Build the template now
349
+ template = template(File.read(file), dialect, block_symbols)
350
+ template.source_file = file
351
+ template
138
352
  end
139
353
 
140
354
  #
@@ -151,35 +365,43 @@ module WLang
151
365
  # WLang.instantiate "SELECT * FROM people WHERE name='{name}'", {"who" => "Mr. O'Neil"}, "wlang/sql"
152
366
  # WLang.instantiate "Hello $(who) !", {"who" => "Mr. Jones"}, "wlang/active-string", :parentheses
153
367
  #
154
- def self.instantiate(template, context=nil, dialect="wlang/active-string", block_symbols = :braces)
155
- WLang::Template.new(template, dialect, context, block_symbols).instantiate
368
+ # This method raises an ArgumentError if
369
+ # * _source_ is not a String
370
+ # * _context_ is not nil or a Hash
371
+ # * _dialect_ is not a valid dialect qualified name or Dialect instance
372
+ # * _block_symbols_ is not in [:braces, :brackets, :parentheses]
373
+ #
374
+ # It raises a WLang::Error
375
+ # * something goes wrong during instantiation (see WLang::Error and subclasses)
376
+ #
377
+ def self.instantiate(source, context = {}, dialect="wlang/active-string", block_symbols = :braces)
378
+ raise ArgumentError, "Hash expected for context argument" unless (context.nil? or Hash===context)
379
+ template(source, dialect, block_symbols).instantiate(context || {}).to_s
156
380
  end
157
381
 
158
382
  #
159
383
  # Instantiates a file written in some _wlang_ dialect, using a given _context_
160
- # (providing instantiation data). Outputs instantiation in _buffer_ (any object
161
- # responding to <tt><<</tt>, usually a IO; String is supported as well). If
162
- # _dialect_ is nil, tries to infer it from the file extension; otherwise _dialect_
163
- # is expected to be a qualified dialect name. See instantiate about <tt>block_symbols</tt>.
164
- # Returns _buffer_.
384
+ # (providing instantiation data). If _dialect_ is nil, tries to infer it from the file
385
+ # extension; otherwise _dialect_ is expected to be a qualified dialect name or a Dialect
386
+ # instance. See instantiate about <tt>block_symbols</tt>.
165
387
  #
166
388
  # Examples:
167
389
  # Wlang.file_instantiate "template.wtpl", {"who" => "Mr. Jones"}
168
- # Wlang.file_instantiate "template.wtpl", {"who" => "Mr. Jones"}, STDOUT
169
- # Wlang.file_instantiate "template.xxx", {"who" => "Mr. Jones"}, STDOUT, "wlang/xhtml"
170
- #
171
- def self.file_instantiate(file, context=nil, buffer=nil, dialect=nil, block_symbols = :braces)
172
- raise "File '#{file}' does not exists or is unreadable"\
173
- unless File.exists?(file) and File.readable?(file)
174
- if dialect.nil?
175
- dialect = infer_dialect(file) if dialect.nil?
176
- raise("No known dialect for file extension '#{File.extname(file)}'\n"\
177
- "Known extensions are: " << WLang::FILE_EXTENSIONS.keys.join(", "))\
178
- if dialect.nil?
179
- end
180
- t = WLang::Template.new(File.read(file), dialect, context, block_symbols)
181
- t.source_file = file
182
- t.instantiate(buffer)
390
+ # Wlang.file_instantiate "template.xxx", {"who" => "Mr. Jones"}, "wlang/xhtml"
391
+ #
392
+ # This method raises an ArgumentError if
393
+ # * _file_ is not a readable file
394
+ # * _context_ is not nil or a Hash
395
+ # * _dialect_ is not a valid dialect qualified name, Dialect instance or nil
396
+ # * _block_symbols_ is not in [:braces, :brackets, :parentheses]
397
+ #
398
+ # It raises a WLang::Error
399
+ # * if no dialect can be infered from the file extension (if _dialect_ was nil)
400
+ # * something goes wrong during instantiation (see WLang::Error and subclasses)
401
+ #
402
+ def self.file_instantiate(file, context = nil, dialect = nil, block_symbols = :braces)
403
+ raise ArgumentError, "Hash expected for context argument" unless (context.nil? or Hash===context)
404
+ file_template(file, dialect, block_symbols).instantiate(context || {}).to_s
183
405
  end
184
406
 
185
407
  end