ezml 0.1.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.
@@ -0,0 +1,46 @@
1
+ module EZML
2
+
3
+ class Escapable < Temple::Filter
4
+ def initialize(*)
5
+ super
6
+ @escape_code = "::EZML::Helpers.html_escape((%s))"
7
+ @escaper = eval("proc {|v| #{@escape_code % 'v'} }")
8
+ @once_escape_code = "::EZML::Helpers.escape_once((%s))"
9
+ @once_escaper = eval("proc {|v| #{@once_escape_code % 'v'} }")
10
+ @escape = false
11
+ end
12
+
13
+ def on_escape(flag, exp)
14
+ old = @escape
15
+ @escape = flag
16
+ compile(exp)
17
+ ensure
18
+ @escape = old
19
+ end
20
+
21
+ def on_static(value)
22
+ [:static,
23
+ if @escape == :once
24
+ @once_escaper[value]
25
+ elsif @escape
26
+ @escaper[value]
27
+ else
28
+ value
29
+ end
30
+ ]
31
+ end
32
+
33
+ def on_dynamic(value)
34
+ [:dynamic,
35
+ if @escape == :once
36
+ @once_escape_code % value
37
+ elsif @escape
38
+ @escape_code % value
39
+ else
40
+ "(#{value}).to_s"
41
+ end
42
+ ]
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,348 @@
1
+ # frozen_string_literal: true
2
+ require 'optparse'
3
+ require 'rbconfig'
4
+ require 'pp'
5
+
6
+ module EZML
7
+ # This module handles the various EZML executables (`ezml` and `ezml-convert`).
8
+ module Exec
9
+ # An abstract class that encapsulates the executable code for all three executables.
10
+ class Generic
11
+ # @param args [Array<String>] The command-line arguments
12
+ def initialize(args)
13
+ @args = args
14
+ @options = {:for_engine => {}}
15
+ end
16
+
17
+ # Parses the command-line arguments and runs the executable.
18
+ # Calls `Kernel#exit` at the end, so it never returns.
19
+ #
20
+ # @see #parse
21
+ def parse!
22
+ begin
23
+ parse
24
+ rescue Exception => e
25
+ raise e if @options[:trace] || e.is_a?(SystemExit)
26
+
27
+ $stderr.print "#{e.class}: " unless e.class == RuntimeError
28
+ $stderr.puts "#{e.message}"
29
+ $stderr.puts " Use --trace for backtrace."
30
+ exit 1
31
+ end
32
+ exit 0
33
+ end
34
+
35
+ # Parses the command-line arguments and runs the executable.
36
+ # This does not handle exceptions or exit the program.
37
+ #
38
+ # @see #parse!
39
+ def parse
40
+ @opts = OptionParser.new(&method(:set_opts))
41
+ @opts.parse!(@args)
42
+
43
+ process_result
44
+
45
+ @options
46
+ end
47
+
48
+ # @return [String] A description of the executable
49
+ def to_s
50
+ @opts.to_s
51
+ end
52
+
53
+ protected
54
+
55
+ # Finds the line of the source template
56
+ # on which an exception was raised.
57
+ #
58
+ # @param exception [Exception] The exception
59
+ # @return [String] The line number
60
+ def get_line(exception)
61
+ # SyntaxErrors have weird line reporting
62
+ # when there's trailing whitespace,
63
+ # which there is for EZML documents.
64
+ return (exception.message.scan(/:(\d+)/).first || ["??"]).first if exception.is_a?(::SyntaxError)
65
+ (exception.backtrace[0].scan(/:(\d+)/).first || ["??"]).first
66
+ end
67
+
68
+ # Tells optparse how to parse the arguments
69
+ # available for all executables.
70
+ #
71
+ # This is meant to be overridden by subclasses
72
+ # so they can add their own options.
73
+ #
74
+ # @param opts [OptionParser]
75
+ def set_opts(opts)
76
+ opts.on('-s', '--stdin', :NONE, 'Read input from standard input instead of an input file') do
77
+ @options[:input] = $stdin
78
+ end
79
+
80
+ opts.on('--trace', :NONE, 'Show a full traceback on error') do
81
+ @options[:trace] = true
82
+ end
83
+
84
+ opts.on('--unix-newlines', 'Use Unix-style newlines in written files.') do
85
+ # Note that this is the preferred way to check for Windows, since
86
+ # JRuby and Rubinius also run there.
87
+ if RbConfig::CONFIG['host_os'] =~ /mswin|windows|mingw/i
88
+ @options[:unix_newlines] = true
89
+ end
90
+ end
91
+
92
+ opts.on_tail("-?", "-h", "--help", "Show this message") do
93
+ puts opts
94
+ exit
95
+ end
96
+
97
+ opts.on_tail("-v", "--version", "Print version") do
98
+ puts("EZML #{::EZML::VERSION}")
99
+ exit
100
+ end
101
+ end
102
+
103
+ # Processes the options set by the command-line arguments.
104
+ # In particular, sets `@options[:input]` and `@options[:output]`
105
+ # to appropriate IO streams.
106
+ #
107
+ # This is meant to be overridden by subclasses
108
+ # so they can run their respective programs.
109
+ def process_result
110
+ input, output = @options[:input], @options[:output]
111
+ args = @args.dup
112
+ input ||=
113
+ begin
114
+ filename = args.shift
115
+ @options[:filename] = filename
116
+ open_file(filename) || $stdin
117
+ end
118
+ output ||= open_file(args.shift, 'w') || $stdout
119
+
120
+ @options[:input], @options[:output] = input, output
121
+ end
122
+
123
+ COLORS = { :red => 31, :green => 32, :yellow => 33 }
124
+
125
+ # Prints a status message about performing the given action,
126
+ # colored using the given color (via terminal escapes) if possible.
127
+ #
128
+ # @param name [#to_s] A short name for the action being performed.
129
+ # Shouldn't be longer than 11 characters.
130
+ # @param color [Symbol] The name of the color to use for this action.
131
+ # Can be `:red`, `:green`, or `:yellow`.
132
+ def puts_action(name, color, arg)
133
+ return if @options[:for_engine][:quiet]
134
+ printf color(color, "%11s %s\n"), name, arg
135
+ end
136
+
137
+ # Same as `Kernel.puts`, but doesn't print anything if the `--quiet` option is set.
138
+ #
139
+ # @param args [Array] Passed on to `Kernel.puts`
140
+ def puts(*args)
141
+ return if @options[:for_engine][:quiet]
142
+ Kernel.puts(*args)
143
+ end
144
+
145
+ # Wraps the given string in terminal escapes
146
+ # causing it to have the given color.
147
+ # If terminal esapes aren't supported on this platform,
148
+ # just returns the string instead.
149
+ #
150
+ # @param color [Symbol] The name of the color to use.
151
+ # Can be `:red`, `:green`, or `:yellow`.
152
+ # @param str [String] The string to wrap in the given color.
153
+ # @return [String] The wrapped string.
154
+ def color(color, str)
155
+ raise "[BUG] Unrecognized color #{color}" unless COLORS[color]
156
+
157
+ # Almost any real Unix terminal will support color,
158
+ # so we just filter for Windows terms (which don't set TERM)
159
+ # and not-real terminals, which aren't ttys.
160
+ return str if ENV["TERM"].nil? || ENV["TERM"].empty? || !STDOUT.tty?
161
+ return "\e[#{COLORS[color]}m#{str}\e[0m"
162
+ end
163
+
164
+ private
165
+
166
+ def open_file(filename, flag = 'r')
167
+ return if filename.nil?
168
+ flag = 'wb' if @options[:unix_newlines] && flag == 'w'
169
+ File.open(filename, flag)
170
+ end
171
+
172
+ def handle_load_error(err)
173
+ dep = err.message[/^no such file to load -- (.*)/, 1]
174
+ raise err if @options[:trace] || dep.nil? || dep.empty?
175
+ $stderr.puts <<MESSAGE
176
+ Required dependency #{dep} not found!
177
+ Run "gem install #{dep}" to get it.
178
+ Use --trace for backtrace.
179
+ MESSAGE
180
+ exit 1
181
+ end
182
+ end
183
+
184
+ # The `ezml` executable.
185
+ class EZML < Generic
186
+ # @param args [Array<String>] The command-line arguments
187
+ def initialize(args)
188
+ super
189
+ @options[:for_engine] = {}
190
+ @options[:requires] = []
191
+ @options[:load_paths] = []
192
+ end
193
+
194
+ # Tells optparse how to parse the arguments.
195
+ #
196
+ # @param opts [OptionParser]
197
+ def set_opts(opts)
198
+ super
199
+
200
+ opts.banner = <<END
201
+ Usage: ezml [options] [INPUT] [OUTPUT]
202
+
203
+ Description:
204
+ Converts EZML files to HTML.
205
+
206
+ Options:
207
+ END
208
+
209
+ opts.on('-c', '--check', "Just check syntax, don't evaluate.") do
210
+ require 'stringio'
211
+ @options[:check_syntax] = true
212
+ @options[:output] = StringIO.new
213
+ end
214
+
215
+ opts.on('-f', '--format NAME',
216
+ 'Output format. Can be html5 (default), xhtml, or html4.') do |name|
217
+ @options[:for_engine][:format] = name.to_sym
218
+ end
219
+
220
+ opts.on('-e', '--escape-html',
221
+ 'Escape HTML characters (like ampersands and angle brackets) by default.') do
222
+ @options[:for_engine][:escape_html] = true
223
+ end
224
+
225
+ opts.on('--no-escape-attrs',
226
+ "Don't escape HTML characters (like ampersands and angle brackets) in attributes.") do
227
+ @options[:for_engine][:escape_attrs] = false
228
+ end
229
+
230
+ opts.on('-q', '--double-quote-attributes',
231
+ 'Set attribute wrapper to double-quotes (default is single).') do
232
+ @options[:for_engine][:attr_wrapper] = '"'
233
+ end
234
+
235
+ opts.on('--remove-whitespace',
236
+ 'Remove whitespace surrounding and within tags') do
237
+ @options[:for_engine][:remove_whitespace] = true
238
+ end
239
+
240
+ opts.on('--cdata',
241
+ 'Always add CDATA sections to javascript and css blocks.') do
242
+ @options[:for_engine][:cdata] = true
243
+ end
244
+
245
+ opts.on('--autoclose LIST',
246
+ 'Comma separated list of elements to be automatically self-closed.') do |list|
247
+ @options[:for_engine][:autoclose] = list.split(',')
248
+ end
249
+
250
+ opts.on('--suppress-eval',
251
+ 'Don\'t evaluate Ruby scripts.') do
252
+ @options[:for_engine][:suppress_eval] = true
253
+ end
254
+
255
+ opts.on('-r', '--require FILE', "Same as 'ruby -r'.") do |file|
256
+ @options[:requires] << file
257
+ end
258
+
259
+ opts.on('-I', '--load-path PATH', "Same as 'ruby -I'.") do |path|
260
+ @options[:load_paths] << path
261
+ end
262
+
263
+ opts.on('-E ex[:in]', 'Specify the default external and internal character encodings.') do |encoding|
264
+ external, internal = encoding.split(':')
265
+ Encoding.default_external = external if external && !external.empty?
266
+ Encoding.default_internal = internal if internal && !internal.empty?
267
+ end
268
+
269
+ opts.on('-d', '--debug', "Print out the precompiled Ruby source, and show syntax errors in the Ruby code.") do
270
+ @options[:debug] = true
271
+ end
272
+
273
+ opts.on('-p', '--parse', "Print out EZML parse tree.") do
274
+ @options[:parse] = true
275
+ end
276
+
277
+ end
278
+
279
+ # Processes the options set by the command-line arguments,
280
+ # and runs the EZML compiler appropriately.
281
+ def process_result
282
+ super
283
+ @options[:for_engine][:filename] = @options[:filename]
284
+ input = @options[:input]
285
+ output = @options[:output]
286
+
287
+ template = input.read()
288
+ input.close() if input.is_a? File
289
+
290
+ @options[:load_paths].each {|p| $LOAD_PATH << p}
291
+ @options[:requires].each {|f| require f}
292
+
293
+ begin
294
+
295
+ if @options[:parse]
296
+ parser = ::EZML::Parser.new(::EZML::Options.new(@options))
297
+ pp parser.call(template)
298
+ return
299
+ end
300
+
301
+ engine = ::EZML::Engine.new(template, @options[:for_engine])
302
+
303
+ if @options[:check_syntax]
304
+ error = validate_ruby(engine.precompiled)
305
+ if error
306
+ puts error.message.split("\n").first
307
+ exit 1
308
+ end
309
+ puts "Syntax OK"
310
+ return
311
+ end
312
+
313
+ if @options[:debug]
314
+ puts engine.precompiled
315
+ error = validate_ruby(engine.precompiled)
316
+ if error
317
+ puts '=' * 100
318
+ puts error.message.split("\n")[0]
319
+ exit 1
320
+ end
321
+ return
322
+ end
323
+
324
+ result = engine.to_html
325
+ rescue Exception => e
326
+ raise e if @options[:trace]
327
+
328
+ case e
329
+ when ::EZML::SyntaxError; raise "Syntax error on line #{get_line e}: #{e.message}"
330
+ when ::EZML::Error; raise "EZML error on line #{get_line e}: #{e.message}"
331
+ else raise "Exception on line #{get_line e}: #{e.message}"
332
+ end
333
+ end
334
+
335
+ output.write(result)
336
+ output.close() if output.is_a? File
337
+ end
338
+
339
+ def validate_ruby(code)
340
+ begin
341
+ eval("BEGIN {return nil}; #{code}", binding, @options[:filename] || "")
342
+ rescue ::SyntaxError # Not to be confused with EZML::SyntaxError
343
+ $!
344
+ end
345
+ end
346
+ end
347
+ end
348
+ end
@@ -0,0 +1,249 @@
1
+ require "tilt"
2
+
3
+ module EZML
4
+ module Filters
5
+
6
+ extend self
7
+
8
+ attr_reader :defined
9
+ @defined = {}
10
+
11
+ def register_tilt_filter(name, options = {})
12
+ if constants.map(&:to_s).include?(name.to_s)
13
+ raise "#{name} filter already defined"
14
+ end
15
+
16
+ filter = const_set(name, Module.new)
17
+ filter.extend const_get(options[:extend] || "Plain")
18
+ filter.extend TiltFilter
19
+ filter.extend PrecompiledTiltFilter if options.has_key? :precompiled
20
+
21
+ if options.has_key? :template_class
22
+ filter.template_class = options[:template_class]
23
+ else
24
+ filter.tilt_extension = options.fetch(:extension) { name.downcase }
25
+ end
26
+
27
+ # All ":coffeescript" as alias for ":coffee", etc.
28
+ if options.has_key?(:alias)
29
+ [options[:alias]].flatten.each {|x| Filters.defined[x.to_s] = filter}
30
+ end
31
+ filter
32
+ end
33
+
34
+ def remove_filter(name)
35
+ defined.delete name.to_s.downcase
36
+ if constants.map(&:to_s).include?(name.to_s)
37
+ remove_const name.to_sym
38
+ end
39
+ end
40
+
41
+ module Base
42
+
43
+ def self.included(base)
44
+ Filters.defined[base.name.split("::").last.downcase] = base
45
+ base.extend(base)
46
+ end
47
+
48
+ def render(text)
49
+ raise Error.new("#{self.inspect}#render not defined!")
50
+ end
51
+
52
+ def render_with_options(text, options)
53
+ render(text)
54
+ end
55
+
56
+ def internal_compile(*args)
57
+ compile(*args)
58
+ end
59
+
60
+ def compile(compiler, text)
61
+ filter = self
62
+ compiler.instance_eval do
63
+ if contains_interpolation?(text)
64
+ return if options[:suppress_eval]
65
+
66
+ text = unescape_interpolation(text, options[:escape_html]).gsub(/(\\+)n/) do |s|
67
+ escapes = $1.size
68
+ next s if escapes % 2 == 0
69
+ "#{'\\' * (escapes - 1)}\n"
70
+ end
71
+ text = %[\n#{text.sub(/\n"\Z/, "\\n\"")}]
72
+ push_script <<RUBY.rstrip, :escape_html => false
73
+ find_and_preserve(#{filter.inspect}.render_with_options(#{text}, _ezmlout.options))
74
+ RUBY
75
+ return
76
+ end
77
+
78
+ rendered = EZML::Helpers::find_and_preserve(filter.render_with_options(text.to_s, compiler.options), compiler.options[:preserve])
79
+ push_text("#{rendered.rstrip}\n")
80
+ end
81
+ end
82
+ end
83
+
84
+ module Plain
85
+ include Base
86
+
87
+ def render(text); text; end
88
+ end
89
+
90
+ module Javascript
91
+ include Base
92
+
93
+ # @see Base#render_with_options
94
+ def render_with_options(text, options)
95
+ indent = options[:cdata] ? ' ' : ' ' # 4 or 2 spaces
96
+ if options[:format] == :html5
97
+ type = ''
98
+ else
99
+ type = " type=#{options[:attr_wrapper]}text/javascript#{options[:attr_wrapper]}"
100
+ end
101
+
102
+ text = text.rstrip
103
+ text.gsub!("\n", "\n#{indent}")
104
+
105
+ %!<script#{type}>\n#{" //<![CDATA[\n" if options[:cdata]}#{indent}#{text}\n#{" //]]>\n" if options[:cdata]}</script>!
106
+ end
107
+ end
108
+
109
+ module Css
110
+ include Base
111
+
112
+ # @see Base#render_with_options
113
+ def render_with_options(text, options)
114
+ indent = options[:cdata] ? ' ' : ' ' # 4 or 2 spaces
115
+ if options[:format] == :html5
116
+ type = ''
117
+ else
118
+ type = " type=#{options[:attr_wrapper]}text/css#{options[:attr_wrapper]}"
119
+ end
120
+
121
+ text = text.rstrip
122
+ text.gsub!("\n", "\n#{indent}")
123
+
124
+ %(<style#{type}>\n#{" /*<![CDATA[*/\n" if options[:cdata]}#{indent}#{text}\n#{" /*]]>*/\n" if options[:cdata]}</style>)
125
+ end
126
+ end
127
+
128
+ module Cdata
129
+ include Base
130
+
131
+ # @see Base#render
132
+ def render(text)
133
+ "<![CDATA[#{"\n#{text.rstrip}".gsub("\n", "\n ")}\n]]>"
134
+ end
135
+ end
136
+
137
+ module Escaped
138
+ include Base
139
+
140
+ # @see Base#render
141
+ def render(text)
142
+ EZML::Helpers.html_escape text
143
+ end
144
+ end
145
+
146
+ module Ruby
147
+ include Base
148
+ require 'stringio'
149
+
150
+ # @see Base#compile
151
+ def compile(compiler, text)
152
+ return if compiler.options[:suppress_eval]
153
+ compiler.instance_eval do
154
+ push_silent "#{<<-FIRST.tr("\n", ';')}#{text}#{<<-LAST.tr("\n", ';')}"
155
+ begin
156
+ ezml_io = StringIO.new(_ezmlout.buffer, 'a')
157
+ FIRST
158
+ ensure
159
+ ezml_io.close
160
+ ezml_io = nil
161
+ end
162
+ LAST
163
+ end
164
+ end
165
+ end
166
+
167
+ module Preserve
168
+ include Base
169
+
170
+ # @see Base#render
171
+ def render(text)
172
+ EZML::Helpers.preserve text
173
+ end
174
+ end
175
+
176
+ module TiltFilter
177
+ extend self
178
+ attr_accessor :tilt_extension, :options
179
+ attr_writer :template_class
180
+
181
+ def template_class
182
+ (@template_class if defined? @template_class) or begin
183
+ @template_class = Tilt["t.#{tilt_extension}"] or
184
+ raise Error.new(Error.message(:cant_run_filter, tilt_extension))
185
+ rescue LoadError => e
186
+ dep = e.message.split('--').last.strip
187
+ raise Error.new(Error.message(:gem_install_filter_deps, tilt_extension, dep))
188
+ end
189
+ end
190
+
191
+ def self.extended(base)
192
+ base.options = {}
193
+ base.instance_eval %Q{
194
+ include Base
195
+
196
+ def render_with_options(text, compiler_options)
197
+ text = template_class.new(nil, 1, options) {text}.render
198
+ super(text, compiler_options)
199
+ end
200
+ }
201
+ end
202
+ end
203
+
204
+ module PrecompiledTiltFilter
205
+ def precompiled(text)
206
+ template_class.new(nil, 1, options) { text }.send(:precompiled, {}).first
207
+ end
208
+
209
+ def compile(compiler, text)
210
+ return if compiler.options[:suppress_eval]
211
+ compiler.send(:push_script, precompiled(text))
212
+ end
213
+ end
214
+
215
+ # @!parse module Sass; end
216
+ register_tilt_filter "Sass", :extend => "Css"
217
+
218
+ # @!parse module Scss; end
219
+ register_tilt_filter "Scss", :extend => "Css"
220
+
221
+ # @!parse module Less; end
222
+ register_tilt_filter "Less", :extend => "Css"
223
+
224
+ # @!parse module Markdown; end
225
+ register_tilt_filter "Markdown"
226
+
227
+ # @!parse module Erb; end
228
+ register_tilt_filter "Erb", :precompiled => true
229
+
230
+ # @!parse module Coffee; end
231
+ register_tilt_filter "Coffee", :alias => "coffeescript", :extend => "Javascript"
232
+
233
+ module Erb
234
+ class << self
235
+ def precompiled(text)
236
+ #workaround for https://github.com/rtomayko/tilt/pull/183
237
+ require 'erubis' if (defined?(::Erubis) && !defined?(::Erubis::Eruby))
238
+ super.sub(/^#coding:.*?\n/, '')
239
+ end
240
+ end
241
+ end
242
+ end
243
+ end
244
+
245
+ begin
246
+ #{}require_relative "filters/maruku"
247
+ #{}require_relative "filters/textile"
248
+ rescue LoadError
249
+ end