wlang 0.8.4

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 (73) hide show
  1. data/LICENCE.rdoc +25 -0
  2. data/README.rdoc +111 -0
  3. data/bin/wlang +24 -0
  4. data/doc/specification/about.rdoc +61 -0
  5. data/doc/specification/dialects.wtpl +0 -0
  6. data/doc/specification/examples.rb +3 -0
  7. data/doc/specification/glossary.wtpl +14 -0
  8. data/doc/specification/hosting.rdoc +0 -0
  9. data/doc/specification/overview.rdoc +116 -0
  10. data/doc/specification/rulesets.wtpl +87 -0
  11. data/doc/specification/specification.css +52 -0
  12. data/doc/specification/specification.html +1361 -0
  13. data/doc/specification/specification.js +8 -0
  14. data/doc/specification/specification.wtpl +41 -0
  15. data/doc/specification/specification.yml +430 -0
  16. data/doc/specification/symbols.wtpl +16 -0
  17. data/lib/wlang.rb +186 -0
  18. data/lib/wlang/basic_object.rb +19 -0
  19. data/lib/wlang/dialect.rb +230 -0
  20. data/lib/wlang/dialect_dsl.rb +136 -0
  21. data/lib/wlang/dialect_loader.rb +69 -0
  22. data/lib/wlang/dialects/coderay_dialect.rb +35 -0
  23. data/lib/wlang/dialects/plain_text_dialect.rb +75 -0
  24. data/lib/wlang/dialects/rdoc_dialect.rb +33 -0
  25. data/lib/wlang/dialects/ruby_dialect.rb +35 -0
  26. data/lib/wlang/dialects/sql_dialect.rb +38 -0
  27. data/lib/wlang/dialects/standard_dialects.rb +113 -0
  28. data/lib/wlang/dialects/xhtml_dialect.rb +40 -0
  29. data/lib/wlang/encoder.rb +66 -0
  30. data/lib/wlang/encoder_set.rb +117 -0
  31. data/lib/wlang/errors.rb +37 -0
  32. data/lib/wlang/intelligent_buffer.rb +94 -0
  33. data/lib/wlang/parser.rb +251 -0
  34. data/lib/wlang/parser_context.rb +146 -0
  35. data/lib/wlang/ruby_extensions.rb +21 -0
  36. data/lib/wlang/rule.rb +66 -0
  37. data/lib/wlang/rule_set.rb +93 -0
  38. data/lib/wlang/rulesets/basic_ruleset.rb +75 -0
  39. data/lib/wlang/rulesets/buffering_ruleset.rb +103 -0
  40. data/lib/wlang/rulesets/context_ruleset.rb +115 -0
  41. data/lib/wlang/rulesets/encoding_ruleset.rb +73 -0
  42. data/lib/wlang/rulesets/imperative_ruleset.rb +132 -0
  43. data/lib/wlang/rulesets/ruleset_utils.rb +296 -0
  44. data/lib/wlang/template.rb +79 -0
  45. data/lib/wlang/wlang_command.rb +54 -0
  46. data/lib/wlang/wlang_command_options.rb +158 -0
  47. data/test/sandbox.rb +1 -0
  48. data/test/test_all.rb +8 -0
  49. data/test/wlang/anagram_bugs_test.rb +111 -0
  50. data/test/wlang/basic_ruleset_test.rb +52 -0
  51. data/test/wlang/buffering_ruleset_test.rb +102 -0
  52. data/test/wlang/buffering_template1.wtpl +1 -0
  53. data/test/wlang/buffering_template2.wtpl +1 -0
  54. data/test/wlang/buffering_template3.wtpl +1 -0
  55. data/test/wlang/buffering_template4.wtpl +1 -0
  56. data/test/wlang/buffering_template5.wtpl +1 -0
  57. data/test/wlang/context_ruleset_test.rb +32 -0
  58. data/test/wlang/data.rb +3 -0
  59. data/test/wlang/encoder_set_test.rb +42 -0
  60. data/test/wlang/imperative_ruleset_test.rb +107 -0
  61. data/test/wlang/intelligent_buffer_test.rb +194 -0
  62. data/test/wlang/othersymbols_test.rb +16 -0
  63. data/test/wlang/parser_context_test.rb +29 -0
  64. data/test/wlang/parser_test.rb +89 -0
  65. data/test/wlang/plain_text_dialect_test.rb +21 -0
  66. data/test/wlang/ruby_dialect_test.rb +100 -0
  67. data/test/wlang/ruby_expected.rb +3 -0
  68. data/test/wlang/ruby_template.wrb +3 -0
  69. data/test/wlang/ruleset_utils_test.rb +245 -0
  70. data/test/wlang/specification_examples_test.rb +52 -0
  71. data/test/wlang/test_utils.rb +25 -0
  72. data/test/wlang/wlang_test.rb +80 -0
  73. metadata +136 -0
@@ -0,0 +1,16 @@
1
+ <table class="symbols">
2
+ <tr>
3
+ <th class="name">name</th>
4
+ <th class="symbol">symbol</th>
5
+ <th class="meaning">meaning</th>
6
+ <th class="remark">remark</th>
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
+ }
16
+ </table>
data/lib/wlang.rb ADDED
@@ -0,0 +1,186 @@
1
+ require 'wlang/ruby_extensions'
2
+ require 'stringio'
3
+ require 'wlang/rule'
4
+ require 'wlang/rule_set'
5
+ require 'wlang/encoder_set'
6
+ require 'wlang/dialect'
7
+ require 'wlang/dialect_dsl'
8
+ require 'wlang/dialect_loader'
9
+ require 'wlang/parser'
10
+ require 'wlang/parser_context'
11
+ require 'wlang/intelligent_buffer'
12
+
13
+ #
14
+ # Main module of the _wlang_ code generator/template engine, providing a facade
15
+ # on _wlang_ tools. See also the Roadmap section of {README}[link://files/README.html]
16
+ # to enter the library.
17
+ #
18
+ module WLang
19
+
20
+ # Current version of WLang
21
+ VERSION = "0.8.4".freeze
22
+
23
+ # Reusable string for building dialect name based regexps
24
+ DIALECT_NAME_REGEXP_STR = "[-a-z]+"
25
+
26
+ #
27
+ # Regular expression for dialect names.
28
+ #
29
+ DIALECT_NAME_REGEXP = /^([-a-z]+)*$/
30
+
31
+ # Reusable string for building dialect name based regexps
32
+ QUALIFIED_DIALECT_NAME_REGEXP_STR = "[-a-z]+([\/][-a-z]+)*"
33
+
34
+ #
35
+ # Regular expression for dialect qualified names. Dialect qualified names are
36
+ # '/' seperated names, where a name is [-a-z]+.
37
+ # Examples: wlang/xhtml/uri, wlang/plain-text, ...
38
+ #
39
+ QUALIFIED_DIALECT_NAME_REGEXP = /^[-a-z]+([\/][-a-z]+)*$/
40
+
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
+ #
60
+ # Provides installed {file extension => dialect} mappings. File extensions
61
+ # (keys) contain the first dot (like .wtpl, .whtml, ...). Dialects (values) are
62
+ # qualified names, not Dialect instances.
63
+ #
64
+ FILE_EXTENSIONS = {}
65
+
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
+ #
74
+ # Main anonymous dialect. All installed dialects are children of this one,
75
+ # which is anonymous because it does not appear in qualified names.
76
+ #
77
+ @dialect = Dialect.new("", nil)
78
+
79
+ #
80
+ # Installs or query a dialect.
81
+ #
82
+ # <b>When called with a block</b>, this method installs a _wlang_ dialect under
83
+ # _name_ (which cannot be qualified). Extensions can be provided to let _wlang_
84
+ # automatically recognize files that are expressed in this dialect. The block
85
+ # is interpreted as code in the dialect DSL (domain specific language, see
86
+ # WLang::Dialect::DSL). Returns nil in this case.
87
+ #
88
+ # <b>When called without a block</b> this method returns a Dialect instance
89
+ # installed under name (which can be a qualified name). Extensions are ignored
90
+ # in this case. Returns nil if not found, a Dialect instance otherwise.
91
+ #
92
+ def self.dialect(name, *extensions, &block)
93
+ if block_given?
94
+ raise "Unsupported qualified names in dialect installation"\
95
+ unless name.index('/').nil?
96
+ Dialect::DSL.new(@dialect).dialect(name, *extensions, &block).build!
97
+ else
98
+ @dialect.dialect(name)
99
+ end
100
+ end
101
+
102
+ #
103
+ # Adds a data loader for file extensions.
104
+ #
105
+ 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
111
+ end
112
+
113
+ #
114
+ # Loads data from a given URI. If _extension_ is omitted, tries to infer it
115
+ # from the uri, otherwise use it directly. Returns loaded data.
116
+ #
117
+ 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
122
+ loader = DATA_EXTENSIONS[extension]
123
+ raise("No data loader for #{extension}") if loader.nil?
124
+ loader.call(uri)
125
+ end
126
+
127
+ # Infers a dialect from a file extension
128
+ def self.infer_dialect(uri)
129
+ WLang::FILE_EXTENSIONS[File.extname(uri)]
130
+ end
131
+
132
+ #
133
+ # Returns an encoder installed under a qualified name. Returns nil if not
134
+ # found.
135
+ #
136
+ def self.encoder(name)
137
+ @dialect.encoder(name)
138
+ end
139
+
140
+ #
141
+ # Instantiates a template written in some _wlang_ dialect, using a given _context_
142
+ # (providing instantiation data). Returns instantiatiation as a String. If you want
143
+ # to instantiate the template in a specific buffer (a file or console for example),
144
+ # use Template. _template_ is expected to be a String and _context_ a Hash. To
145
+ # know available dialects, see WLang::StandardDialects. <em>block_symbols</em>
146
+ # can be <tt>:braces</tt> ('{' and '}' pairs), <tt>:brackets</tt> ('[' and ']'
147
+ # pairs) or <tt>:parentheses</tt> ('(' and ')' pairs).
148
+ #
149
+ # Examples:
150
+ # WLang.instantiate "Hello ${who} !", {"who" => "Mr. Jones"}
151
+ # WLang.instantiate "SELECT * FROM people WHERE name='{name}'", {"who" => "Mr. O'Neil"}, "wlang/sql"
152
+ # WLang.instantiate "Hello $(who) !", {"who" => "Mr. Jones"}, "wlang/active-string", :parentheses
153
+ #
154
+ def self.instantiate(template, context=nil, dialect="wlang/active-string", block_symbols = :braces)
155
+ WLang::Template.new(template, dialect, context, block_symbols).instantiate
156
+ end
157
+
158
+ #
159
+ # 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_.
165
+ #
166
+ # Examples:
167
+ # 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)
183
+ end
184
+
185
+ end
186
+ require 'wlang/dialects/standard_dialects'
@@ -0,0 +1,19 @@
1
+ module WLang
2
+
3
+ #
4
+ # Provides an Object with all inherited instance methods removed.
5
+ #
6
+ class BasicObject
7
+
8
+ # Methods that we keep
9
+ KEPT_METHODS = ["__send__", "__id__", "instance_eval", "initialize", "object_id",
10
+ :__send__, :__id__, :instance_eval, :initialize, :object_id]
11
+
12
+ # Removes all methods that are not needed to the class
13
+ (instance_methods + private_instance_methods).each do |m|
14
+ undef_method(m) unless KEPT_METHODS.include?(m)
15
+ end
16
+
17
+ end # class BasicObject
18
+
19
+ end
@@ -0,0 +1,230 @@
1
+ require 'wlang/encoder_set'
2
+ require 'wlang/rule_set'
3
+ module WLang
4
+
5
+ #
6
+ # Implements the _dialect_ abstraction (see {README}[link://files/README.html]).
7
+ # A dialect instance is a aggregation of encoders and ruleset (through EncoderSet
8
+ # and RuleSet classes). A dialect is also a node in the dialect tree and has a
9
+ # qualified name through this tree. For example <tt>wlang/xhtml</tt> is the
10
+ # qualified name of a <tt>xhtml</tt> dialect which is a child dialect of
11
+ # <tt>wlang</tt>.
12
+ #
13
+ # Users are not intended to use this class directly. Use the Domain Specific
14
+ # Language instead (see WLang::Dialect::DSL).
15
+ #
16
+ # === For developers only
17
+ # In order to avoid having users to install all required gems of all dialects
18
+ # wlang implements a lazy load design pattern on the dialect tree, through the
19
+ # WLang::Dialect::DSL and WLang::Dialect::Loader classes. The former only creates
20
+ # Dialect instances as tree nodes (by chaining dialects through @parent) and
21
+ # installs mapping with file extensions. Rules and encoders are not initially
22
+ # installed (precisely: WLang::Dialect::DSL#require_ruby is simply ignored).
23
+ # When a given dialect is needed by wlang it is first built (through the build!
24
+ # method and the WLang::Dialect::Loader class).
25
+ #
26
+ # Standard dialect obtention methods (WLang#dialect as well as WLang::Dialect#dialect)
27
+ # ensure that returned dialects are built. If you obtain dialects another way,
28
+ # be sure that they are built before using them (is_built? and build! are your
29
+ # friends to achive that goal).
30
+ #
31
+ # Moreover, child dialects may require tools of their ancestors. The following
32
+ # invariant should always be respected: if a dialect is built, all its ancestors
33
+ # are built as well. This invariant is not enforced by the build! method because
34
+ # it is trivially respected by the way WLang::Dialect#dialect is implemented.
35
+ #
36
+ class Dialect
37
+
38
+ # Underlying ruleset
39
+ attr_reader :ruleset
40
+
41
+ # Underlying encoders
42
+ attr_reader :encoders
43
+
44
+ # Dialect name
45
+ attr_reader :name
46
+
47
+ # Parent dialect
48
+ attr_reader :parent
49
+
50
+ #
51
+ # Creates a dialect instance. _builder_ block is a chunk of code of the DSL
52
+ # that will be executed twice: once at construction time to create sub dialects
53
+ # nodes and install file extensions and once at building time to install ruleset
54
+ # and encoders.
55
+ #
56
+ def initialize(name, parent, &builder)
57
+ @name, @parent = name, parent
58
+ @builder, @built = builder, builder.nil?
59
+ @dialects = nil
60
+ @encoders = nil
61
+ @ruleset = nil
62
+ DSL.new(self).instance_eval(&builder) unless builder.nil?
63
+ end
64
+
65
+ ### Lazy load mechanism ######################################################
66
+
67
+ #
68
+ # Force the dialect to be built. Has no effect if it is already built. Invokes
69
+ # the DSL chunk of code through WLang::DSL::Loader otherwise.
70
+ #
71
+ def build!
72
+ unless is_built?
73
+ WLang::Dialect::Loader.new(self).instance_eval(&@builder)
74
+ @built = true
75
+ end
76
+ self
77
+ end
78
+
79
+ # Returns true if this dialect is already built, false otherwise.
80
+ def is_built?
81
+ return @built
82
+ end
83
+
84
+
85
+ ### Installation #############################################################
86
+
87
+ #
88
+ # Adds a child dialect under _name_. _name_ cannot be qualified and must be a
89
+ # valid dialect name according to the wlang specification (see WLang::DIALECT_NAME_REGEXP).
90
+ # _child_ must be a Dialect instance.
91
+ #
92
+ def add_child_dialect(name, child)
93
+ raise(ArgumentError, "Invalid dialect name") unless WLang::DIALECT_NAME_REGEXP =~ name
94
+ raise(ArgumentError, "Dialect expected") unless Dialect===child
95
+ @dialects = {} if @dialects.nil?
96
+ @dialects[name] = child
97
+ end
98
+
99
+ # See EncoderSet#add_encoder
100
+ def add_encoder(name, &block)
101
+ @encoders = EncoderSet.new if @encoders.nil?
102
+ @encoders.add_encoder(name, &block)
103
+ end
104
+
105
+ # See EncoderSet#add_encoders
106
+ def add_encoders(mod, pairs)
107
+ @encoders = EncoderSet.new if @encoders.nil?
108
+ @encoders.add_encoders(mod, pairs)
109
+ end
110
+
111
+ # See RuleSet::add_rule
112
+ def add_rule(name, &block)
113
+ @ruleset = RuleSet.new if @ruleset.nil?
114
+ @ruleset.add_rule(name, &block)
115
+ end
116
+
117
+ # See RuleSet::add_rules
118
+ def add_rules(mod, pairs)
119
+ @ruleset = RuleSet.new if @ruleset.nil?
120
+ @ruleset.add_rules(mod, pairs)
121
+ end
122
+
123
+ ### Query API ################################################################
124
+
125
+ # Returns qualified name of this dialect
126
+ def qualified_name
127
+ parentname = @parent.nil? ? "" : @parent.to_s
128
+ return ""==parentname ? @name : parentname + '/' + @name
129
+ end
130
+
131
+ #
132
+ # Finds a child dialect by name. _name_ can be a String denoting a qualified
133
+ # name as well as an Array of strings, resulting from a qualified name split.
134
+ # This method should always be invoked on built dialects, it always returns nil
135
+ # otherwise. When found, returned dialect is automatically built as well as all
136
+ # its ancestors. When not found, the method returns nil.
137
+ #
138
+ def dialect(name)
139
+ # implement argument conventions
140
+ if String===name
141
+ raise(ArgumentError, "Invalid dialect name #{name}") unless WLang::QUALIFIED_DIALECT_NAME_REGEXP =~ name
142
+ name = name.split('/')
143
+ elsif not(Array===name)
144
+ raise(ArgumentError,"Invalid dialect name #{name}")
145
+ end
146
+
147
+ # not built or no child at all
148
+ return nil if @dialects.nil?
149
+
150
+ # get first child name and find it
151
+ child_name = name[0]
152
+ child_dialect = @dialects[child_name]
153
+
154
+ if child_dialect.nil?
155
+ # unexisting, return nil
156
+ return nil
157
+ elsif name.length==1
158
+ # found and last of qualified name -> build it
159
+ return child_dialect.build!
160
+ else
161
+ # found but not last of qualified name -> build it and delegate
162
+ child_dialect.build!
163
+ return child_dialect.dialect(name[1..-1])
164
+ end
165
+ end
166
+
167
+ #
168
+ # Finds an encoder by name.
169
+ #
170
+ def encoder(name)
171
+ # implement argument conventions
172
+ if String===name
173
+ raise(ArgumentError, "Invalid encoder name #{name}") unless WLang::QUALIFIED_ENCODER_NAME_REGEXP =~ name
174
+ name = name.split('/')
175
+ elsif not(Array===name)
176
+ raise(ArgumentError,"Invalid encoder name #{name}")
177
+ end
178
+
179
+ # last name in qualified?
180
+ if name.length==1
181
+ # delegate to encoders
182
+ return nil if @encoders.nil?
183
+ return @encoders.get_encoder(name[0])
184
+ else
185
+ # find sub dialect, and delegate
186
+ child_name = name[0]
187
+ child_dialect = dialect(child_name)
188
+ if child_dialect.nil?
189
+ return nil
190
+ else
191
+ return child_dialect.encoder(name[1..-1])
192
+ end
193
+ end
194
+ end
195
+
196
+ # Finds a encoder in dialect tree
197
+ def find_encoder(name)
198
+ raise(ArgumentError, "Invalid (relative) encoder name #{name}") unless String===name
199
+ raise(ArgumentError, "Invalid (relative) encoder name #{name}") if name.include?("/")
200
+ return nil if @encoders.nil?
201
+ if @encoders.has_encoder?(name)
202
+ @encoders.get_encoder(name)
203
+ elsif @parent
204
+ @parent.find_encoder(name)
205
+ else
206
+ nil
207
+ end
208
+ end
209
+
210
+ # See RuleSet#pattern.
211
+ def pattern(block_symbols)
212
+ return RuleSet.new.pattern(block_symbols) if @ruleset.nil?
213
+ @ruleset.pattern(block_symbols)
214
+ end
215
+
216
+ ### Other utilities ##########################################################
217
+
218
+ # Factors a spacing friendly buffer for instantiation in this dialect
219
+ def factor_buffer
220
+ IntelligentBuffer.new
221
+ end
222
+
223
+ # Returns a string representation
224
+ def to_s
225
+ qualified_name
226
+ end
227
+
228
+ end # class Dialect
229
+
230
+ end #module WLang