lib/wlang.rb in wlang-0.10.2 vs lib/wlang.rb in wlang-2.0.0.beta
- old
+ new
@@ -1,407 +1,34 @@
-require 'wlang/loader'
-require 'wlang/version'
-require 'wlang/ext/string'
-require 'stringio'
-require 'wlang/rule'
-require 'wlang/rule_set'
-require 'wlang/encoder_set'
-require 'wlang/dialect'
-require 'wlang/dialect_dsl'
-require 'wlang/dialect_loader'
-require 'wlang/hosted_language'
-require 'wlang/hash_scope'
-require 'wlang/parser'
-require 'wlang/parser_state'
-require 'wlang/intelligent_buffer'
-
+require "wlang/version"
+require "wlang/loader"
#
-# Main module of the _wlang_ code generator/template engine, providing a facade
-# on _wlang_ tools. See also the Roadmap section of {README}[link://files/README.html]
-# to enter the library.
+# WLang is a powerful code generation and templating engine
#
module WLang
-
- ######################################################################## About files and extensions
-
- # Regular expression for file extensions
- FILE_EXTENSION_REGEXP = /^\.[a-zA-Z0-9]+$/
-
- # Checks that _ext_ is a valid file extension or raises an ArgumentError
- def self.check_file_extension(ext)
- raise ArgumentError, "Invalid file extension #{ext} (/^\.[a-zA-Z-0-9]+$/ expected)", caller\
- unless FILE_EXTENSION_REGEXP =~ ext
- end
-
- # Raises an ArgumentError unless file is a real readable file
- def self.check_readable_file(file)
- raise ArgumentError, "File #{file} is not readable or not a file"\
- unless File.exists?(file) and File.file?(file) and File.readable?(file)
- end
-
- ######################################################################## About dialects
-
- # Reusable string for building dialect name based regexps
- DIALECT_NAME_REGEXP_STR = "[-a-z]+"
-
- # Regular expression for dialect names.
- DIALECT_NAME_REGEXP = /^([-a-z]+)*$/
-
- # Reusable string for building dialect name based regexps
- QUALIFIED_DIALECT_NAME_REGEXP_STR = "[-a-z]+([\/][-a-z]+)*"
-
- # Regular expression for dialect qualified names. Dialect qualified names are
- # '/' seperated names, where a name is [-a-z]+.
- #
- # Examples: wlang/xhtml/uri, wlang/plain-text, ...
- QUALIFIED_DIALECT_NAME_REGEXP = /^[-a-z]+([\/][-a-z]+)*$/
- # Checks that _name_ is a valid qualified dialect name or raises an ArgumentError
- def self.check_qualified_dialect_name(name)
- raise ArgumentError, "Invalid dialect qualified name '#{name}' (/^[-a-z]+([\/][-a-z]+)*$/ expected)", caller\
- unless QUALIFIED_DIALECT_NAME_REGEXP =~ name
- end
+ # These are allows block symbols
+ SYMBOLS = "!^%\"$&'*+?@~#,-./:;=<>|_".chars.to_a
- #
- # Provides installed {file extension => dialect} mappings. File extensions
- # (keys) contain the first dot (like .wtpl, .whtml, ...). Dialects (values) are
- # qualified names, not Dialect instances.
- #
- FILE_EXTENSIONS = {}
+ # Template braces
+ BRACES = ['{', '}']
- #
- # Main anonymous dialect. All installed dialects are children of this one,
- # which is anonymous because it does not appear in qualified names.
+ # Defines an anonymous dialect on the fly.
#
- @dialect = Dialect.new("", nil)
-
- # Returns the root of the dialect tree
- def self.dialect_tree
- @dialect
- end
-
- #
- # Maps a file extension to a dialect qualified name.
- #
# Example:
#
- # # We create an 'example' dialect
- # WLang::dialect('example') do
- # # see WLang::dialect about creating a dialect
+ # d = WLang::dialect do
+ # tag('$') do |buf,fn| buf << evaluate(fn) end
+ # ...
# end
+ # d.render("Hello ${who}!", :who => "world")
+ # # => "Hello world!"
#
- # # We map .wex file extensions to our new dialect
- # WLang::file_extension_map('.wex', 'example')
- #
- # This method raises an ArgumentError if the extension or dialect qualified
- # name is not valid.
- #
- def self.file_extension_map(extension, dialect_qname)
- check_file_extension(extension)
- check_qualified_dialect_name(dialect_qname)
- WLang::FILE_EXTENSIONS[extension] = dialect_qname
+ def dialect(superdialect = WLang::Dialect, &defn)
+ Class.new(superdialect, &defn)
end
+ module_function :dialect
- #
- # Infers a dialect from a file extension. Returns nil if no dialect is currently
- # mapped to the given extension (see file_extension_map)
- #
- # This method never raises errors.
- #
- def self.infer_dialect(uri)
- WLang::FILE_EXTENSIONS[File.extname(uri)]
- end
-
- #
- # Ensures, installs or query a dialect.
- #
- # <b>When name is a Dialect</b>, returns it immediately. This helper is provided
- # for methods that accept both qualified dialect name and dialect instance
- # arguments. Calling <code>WLang::dialect(arg)</code> ensures that the result will
- # be a Dialect instance in all cases (if the arg is valid).
- #
- # Example:
- #
- # # This methods does something with a wlang dialect. _dialect_ argument may
- # # be a Dialect instance or a qualified dialect name.
- # def my_method(dialect = 'wlang/active-string')
- # # ensures the Dialect instance or raises an ArgumentError if the dialect
- # # qualified name is invalid (returns nil otherwise !)
- # dialect = WLang::dialect(dialect)
- # end
- #
- # <b>When called with a block</b>, this method installs a _wlang_ dialect under
- # _name_ (which cannot be qualified). Extensions can be provided to let _wlang_
- # automatically recognize files that are expressed in this dialect. The block
- # is interpreted as code in the dialect DSL (domain specific language, see
- # WLang::Dialect::DSL). Returns nil in this case.
- #
- # Example:
- #
- # # New dialect with 'my_dialect' qualified name and automatically installed
- # # to recognize '.wmyd' file extensions
- # WLang::dialect("my_dialect", '.wmyd') do
- # # see WLang::Dialect::DSL for this part of the code
- # end
- #
- # <b>When called without a block</b> this method returns a Dialect instance
- # installed under name (which can be a qualified name). Extensions are ignored
- # in this case. Returns nil if not found, a Dialect instance otherwise.
- #
- # Example:
- #
- # # Lookup for the 'wlang/xhtml' dialect
- # wxhtml = WLang::dialect('wlang/xhtml')
- #
- # This method raises an ArgumentError if
- # * _name_ is not a valid dialect qualified name
- # * any of the file extension in _extensions_ is invalid
- #
- def self.dialect(name, *extensions, &block)
- # first case, already a dialect
- return name if Dialect===name
-
- # other cases, argument validations
- check_qualified_dialect_name(name)
- extensions.each {|ext| check_file_extension(ext)}
-
- if block_given?
- # first case, dialect installation
- raise "Unsupported qualified names in dialect installation"\
- unless name.index('/').nil?
- Dialect::DSL.new(@dialect).dialect(name, *extensions, &block)
- else
- # second case, dialect lookup
- @dialect.dialect(name)
- end
- end
-
- ######################################################################## About encoders
-
- # Reusable string for building encoder name based regexps
- ENCODER_NAME_REGEXP_STR = "[-a-z]+"
-
- # Regular expression for encoder names.
- ENCODER_NAME_REGEXP = /^([-a-z]+)*$/
-
- # Reusable string for building qualified encoder name based regexps
- QUALIFIED_ENCODER_NAME_REGEXP_STR = "[-a-z]+([\/][-a-z]+)*"
-
- # Regular expression for encoder qualified names. Encoder qualified names are
- # '/' seperated names, where a name is [-a-z]+.
- #
- # Examples: xhtml/entities-encoding, sql/single-quoting, ...
- QUALIFIED_ENCODER_NAME_REGEXP = /^([-a-z]+)([\/][-a-z]+)*$/
-
- # Checks that _name_ is a valid qualified encoder name or raises an ArgumentError
- def self.check_qualified_encoder_name(name)
- raise ArgumentError, "Invalid encoder qualified name #{name} (/^[-a-z]+([\/][-a-z]+)*$/ expected)", caller\
- unless QUALIFIED_ENCODER_NAME_REGEXP =~ name
- end
-
- #
- # Returns an encoder installed under a qualified name. Returns nil if not
- # found. If name is already an Encoder instance, returns it immediately.
- #
- # Example:
- #
- # encoder = WLang::encoder('xhtml/entities-encoding')
- # encoder.encode('something that needs html entities escaping')
- #
- # This method raises an ArgumentError if _name_ is not a valid encoder qualified
- # name.
- #
- def self.encoder(name)
- check_qualified_encoder_name(name)
- @dialect.encoder(name)
- end
-
- #
- # Shortcut for
- #
- # WLang::encoder(encoder_qname).encode(source, options)
- #
- # This method raises an ArgumentError
- # * if _source_ is not a String
- # * if the encoder qualified name is invalid
- #
- # It raises a WLang::Error if the encoder cannot be found
- #
- def self.encode(source, encoder_qname, options = {})
- raise ArgumentError, "String expected for source" unless String===source
- check_qualified_encoder_name(encoder_qname)
- encoder = WLang::encoder(encoder_qname)
- raise WLang::Error, "Unable to find encoder #{encoder_qname}" if encoder.nil?
- encoder.encode(source, options)
- end
-
- ######################################################################## About data loading
-
- #
- # Provides installed {file extension => data loader} mapping. File extensions
- # (keys) contain the first dot (like .wtpl, .whtml, ...). Data loades are
- # Proc instances that take a single |uri| argument.
- #
- DATA_EXTENSIONS = {}
-
- #
- # Adds a data loader for file extensions. A data loader is a block of arity 1,
- # taking a file as parameter and returning data decoded from the file.
- #
- # Example:
- #
- # # We have some MyXMLDataLoader class that is able to create a ruby object
- # # from things expressed .xml files
- # WLang::data_loader('.xml') {|file|
- # MyXMLDataLaoder.parse_file(file)
- # }
- #
- # # Later in a template (see the buffering ruleset that gives you <<={...})
- # <<={resources.xml as resources}
- # <html>
- # *{resources as r}{
- # ...
- # }
- # </html>
- #
- # This method raises an ArgumentError if
- # * no block is given or if the block is not of arity 1
- # * any of the file extensions in _exts_ is invalid
- #
- def self.data_loader(*exts, &block)
- raise(ArgumentError, "WLang::data_loader expects a block") unless block_given?
- raise(ArgumentError, "WLang::data_loader expects a block of arity 1") unless block.arity==1
- exts.each {|ext| check_file_extension(ext) }
- exts.each {|ext| DATA_EXTENSIONS[ext] = block}
- end
-
- #
- # Loads data from a given URI. If _extension_ is omitted, tries to infer it
- # from the uri, otherwise use it directly. Returns loaded data.
- #
- # This method raises a WLang::Error if no data loader is installed for the found
- # extension. It raises an ArgumentError if the file extension is invalid.
- #
- def self.load_data(uri, extension=nil)
- check_file_extension(extension = extension.nil? ? File.extname(uri) : extension)
- loader = DATA_EXTENSIONS[extension]
- raise ::WLang::Error, "No data loader for #{extension}" if loader.nil?
- loader.call(uri)
- end
-
- ######################################################################## About templates and instantiations
-
- #
- # Factors a template instance for a given string source, dialect (default to
- # 'wlang/active-string') and block symbols (default to :braces)
- #
- # Example:
- #
- # # The template source code must be interpreted as wlang/xhtml
- # template = WLang::template('<p>Hello ${who}!</p>', 'wlang/xhtml')
- # str = template.instantiate(:hello => 'world')
- #
- # # We may also use other block symbols...
- # template = WLang::template('<p>Hello $(who)!</p>', 'wlang/xhtml', :parentheses)
- # str = template.instantiate(:hello => 'world')
- #
- # This method raises an ArgumentError if
- # * _source_ is not a String
- # * _dialect_ is not a valid dialect qualified name or Dialect instance
- # * _block_symbols_ is not in [:braces, :brackets, :parentheses]
- #
- def self.template(source, dialect = 'wlang/active-string', block_symbols = :braces)
- raise ArgumentError, "String expected for source" unless String===source
- raise ArgumentError, "Invalid symbols for block #{block_symbols}"\
- unless ::WLang::Template::BLOCK_SYMBOLS.keys.include?(block_symbols)
- template = Template.new(source, WLang::dialect(dialect), block_symbols)
- end
-
- #
- # Factors a template instance for a given file, optional dialect (if nil is
- # passed, the dialect is infered from the extension) and block symbols
- # (default to :braces)
- #
- # Example:
- #
- # # the file index.wtpl is a wlang source code in 'wlang/xhtml' dialect
- # # (automatically infered from file extension)
- # template = WLang::template('index.wtpl')
- # puts template.instantiate(:who => 'world') # puts 'Hello world!'
- #
- # This method raises an ArgumentError
- # * if _file_ does not exists, is not a file or is not readable
- # * if _dialect_ is not a valid qualified dialect name, Dialect instance, or nil
- # * _block_symbols_ is not in [:braces, :brackets, :parentheses]
- #
- # It raises a WLang::Error
- # * if no dialect can be infered from the file extension (if _dialect_ was nil)
- #
- def self.file_template(file, dialect = nil, block_symbols = :braces)
- check_readable_file(file)
-
- # Check the dialect
- dialect = self.infer_dialect(file) if dialect.nil?
- raise WLang::Error, "No known dialect for file extension '#{File.extname(file)}'\n"\
- "Known extensions are: " << WLang::FILE_EXTENSIONS.keys.join(", ") if dialect.nil?
-
- # Build the template now
- template = template(File.read(file), dialect, block_symbols)
- template.source_file = file
- template
- end
-
- #
- # Instantiates a template written in some _wlang_ dialect, using a given _context_
- # (providing instantiation data). Returns instantiatiation as a String. If you want
- # to instantiate the template in a specific buffer (a file or console for example),
- # use Template. _template_ is expected to be a String and _context_ a Hash. To
- # know available dialects, see WLang::StandardDialects. <em>block_symbols</em>
- # can be <tt>:braces</tt> ('{' and '}' pairs), <tt>:brackets</tt> ('[' and ']'
- # pairs) or <tt>:parentheses</tt> ('(' and ')' pairs).
- #
- # Examples:
- # WLang.instantiate "Hello ${who} !", {"who" => "Mr. Jones"}
- # WLang.instantiate "SELECT * FROM people WHERE name='{name}'", {"who" => "Mr. O'Neil"}, "wlang/sql"
- # WLang.instantiate "Hello $(who) !", {"who" => "Mr. Jones"}, "wlang/active-string", :parentheses
- #
- # This method raises an ArgumentError if
- # * _source_ is not a String
- # * _context_ is not nil or a Hash
- # * _dialect_ is not a valid dialect qualified name or Dialect instance
- # * _block_symbols_ is not in [:braces, :brackets, :parentheses]
- #
- # It raises a WLang::Error
- # * something goes wrong during instantiation (see WLang::Error and subclasses)
- #
- def self.instantiate(source, context = {}, dialect="wlang/active-string", block_symbols = :braces)
- raise ArgumentError, "Hash expected for context argument" unless (context.nil? or Hash===context)
- template(source, dialect, block_symbols).instantiate(context || {}).to_s
- end
-
- #
- # Instantiates a file written in some _wlang_ dialect, using a given _context_
- # (providing instantiation data). If _dialect_ is nil, tries to infer it from the file
- # extension; otherwise _dialect_ is expected to be a qualified dialect name or a Dialect
- # instance. See instantiate about <tt>block_symbols</tt>.
- #
- # Examples:
- # Wlang.file_instantiate "template.wtpl", {"who" => "Mr. Jones"}
- # Wlang.file_instantiate "template.xxx", {"who" => "Mr. Jones"}, "wlang/xhtml"
- #
- # This method raises an ArgumentError if
- # * _file_ is not a readable file
- # * _context_ is not nil or a Hash
- # * _dialect_ is not a valid dialect qualified name, Dialect instance or nil
- # * _block_symbols_ is not in [:braces, :brackets, :parentheses]
- #
- # It raises a WLang::Error
- # * if no dialect can be infered from the file extension (if _dialect_ was nil)
- # * something goes wrong during instantiation (see WLang::Error and subclasses)
- #
- def self.file_instantiate(file, context = nil, dialect = nil, block_symbols = :braces)
- raise ArgumentError, "Hash expected for context argument" unless (context.nil? or Hash===context)
- file_template(file, dialect, block_symbols).instantiate(context || {}).to_s
- end
-
-end
-require 'wlang/dialects/standard_dialects'
+end # module WLang
+require 'wlang/compiler'
+require 'wlang/template'
+require 'wlang/dialect'
+require 'wlang/scope'