rux 1.0.0

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.
data/lib/rux.rb ADDED
@@ -0,0 +1,73 @@
1
+ require 'stringio'
2
+
3
+ # Racc uses LoadErrors for flow control and in certain ruby versions will
4
+ # print one along with its stack trace when required. To avoid receiving a
5
+ # long, arresting, and entirely irrelevant stack trace every time you invoke
6
+ # rux, temporarily redirect STDOUT here and throw away the output.
7
+ begin
8
+ old_stdout = $stdout
9
+ $stdout = StringIO.new
10
+ require 'racc/parser'
11
+ ensure
12
+ $stdout = old_stdout
13
+ end
14
+
15
+ require 'cgi'
16
+ require 'parser/current'
17
+ require 'unparser'
18
+
19
+ module Rux
20
+ autoload :AST, 'rux/ast'
21
+ autoload :Buffer, 'rux/buffer'
22
+ autoload :Component, 'rux/component'
23
+ autoload :DefaultTagBuilder, 'rux/default_tag_builder'
24
+ autoload :DefaultVisitor, 'rux/default_visitor'
25
+ autoload :File, 'rux/file'
26
+ autoload :Lex, 'rux/lex'
27
+ autoload :Lexer, 'rux/lexer'
28
+ autoload :Parser, 'rux/parser'
29
+ autoload :RubyLexer, 'rux/ruby_lexer'
30
+ autoload :RuxLexer, 'rux/rux_lexer'
31
+ autoload :Utils, 'rux/utils'
32
+ autoload :Visitor, 'rux/visitor'
33
+
34
+ class << self
35
+ attr_accessor :tag_builder, :buffer
36
+
37
+ def to_ruby(str, visitor: default_visitor, pretty: true)
38
+ ruby_code = visitor.visit(Parser.parse(str))
39
+ return ruby_code unless pretty
40
+
41
+ ::Unparser.unparse(
42
+ ::Parser::CurrentRuby.parse(ruby_code)
43
+ )
44
+ end
45
+
46
+ def default_visitor
47
+ @default_visitor ||= DefaultVisitor.new
48
+ end
49
+
50
+ def default_tag_builder
51
+ @default_tag_builder ||= DefaultTagBuilder.new
52
+ end
53
+
54
+ def default_buffer
55
+ @default_buffer ||= Buffer
56
+ end
57
+
58
+ def tag(tag_name, attributes = {}, &block)
59
+ tag_builder.call(tag_name, attributes, &block)
60
+ end
61
+
62
+ def create_buffer
63
+ buffer.new
64
+ end
65
+
66
+ def library_paths
67
+ @library_paths ||= []
68
+ end
69
+ end
70
+
71
+ self.tag_builder = self.default_tag_builder
72
+ self.buffer = self.default_buffer
73
+ end
data/lib/rux/ast.rb ADDED
@@ -0,0 +1,9 @@
1
+ module Rux
2
+ module AST
3
+ autoload :ListNode, 'rux/ast/list_node'
4
+ autoload :RubyNode, 'rux/ast/ruby_node'
5
+ autoload :StringNode, 'rux/ast/string_node'
6
+ autoload :TagNode, 'rux/ast/tag_node'
7
+ autoload :TextNode, 'rux/ast/text_node'
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ module Rux
2
+ module AST
3
+ class ListNode
4
+ attr_reader :children
5
+
6
+ def initialize(children)
7
+ @children = children
8
+ end
9
+
10
+ def accept(visitor)
11
+ visitor.visit_list(self)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Rux
2
+ module AST
3
+ class RubyNode
4
+ attr_reader :code
5
+
6
+ def initialize(code)
7
+ @code = code
8
+ end
9
+
10
+ def accept(visitor)
11
+ visitor.visit_ruby(self)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Rux
2
+ module AST
3
+ class StringNode
4
+ attr_reader :str
5
+
6
+ def initialize(str)
7
+ @str = str
8
+ end
9
+
10
+ def accept(visitor)
11
+ visitor.visit_string(self)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module Rux
2
+ module AST
3
+ class TagNode
4
+ attr_reader :name, :attrs, :children
5
+
6
+ def initialize(name, attrs)
7
+ @name = name
8
+ @attrs = attrs
9
+ @children = []
10
+ end
11
+
12
+ def accept(visitor)
13
+ visitor.visit_tag(self)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ require 'cgi'
2
+
3
+ module Rux
4
+ module AST
5
+ class TextNode
6
+ attr_reader :text
7
+
8
+ def initialize(text)
9
+ @text = text
10
+ end
11
+
12
+ def accept(visitor)
13
+ visitor.visit_text(self)
14
+ end
15
+ end
16
+ end
17
+ end
data/lib/rux/buffer.rb ADDED
@@ -0,0 +1,15 @@
1
+ module Rux
2
+ class Buffer
3
+ def initialize(init_str = '')
4
+ @string = init_str.dup
5
+ end
6
+
7
+ def <<(str)
8
+ @string << (str || '')
9
+ end
10
+
11
+ def to_s
12
+ @string
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ module Rux
2
+ class DefaultTagBuilder
3
+ def call(tag_name, attributes = {})
4
+ "<#{tag_name} #{serialize_attrs(attributes)}>" <<
5
+ (block_given? ? Array(yield) : []).join <<
6
+ "</#{tag_name}>"
7
+ end
8
+
9
+ private
10
+
11
+ def serialize_attrs(attributes)
12
+ ''.tap do |result|
13
+ attributes.each_pair.with_index do |(k, v), idx|
14
+ result << ' ' unless idx == 0
15
+ result << "#{k.to_s.gsub('-', '_')}=\"#{CGI.escape_html(v.to_s)}\""
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,67 @@
1
+ require 'cgi'
2
+
3
+ module Rux
4
+ class DefaultVisitor < Visitor
5
+ def visit_list(node)
6
+ node.children.map { |child| visit(child) }.join
7
+ end
8
+
9
+ def visit_ruby(node)
10
+ node.code
11
+ end
12
+
13
+ def visit_string(node)
14
+ node.str
15
+ end
16
+
17
+ def visit_tag(node)
18
+ ''.tap do |result|
19
+ block_arg = if (as = node.attrs['as'])
20
+ visit(as)
21
+ end
22
+
23
+ at = node.attrs.each_with_object([]) do |(k, v), ret|
24
+ next if k == 'as'
25
+ ret << Utils.attr_to_hash_elem(k, visit(v))
26
+ end
27
+
28
+ if node.name.start_with?(/[A-Z]/)
29
+ result << "render(#{node.name}.new"
30
+
31
+ unless node.attrs.empty?
32
+ result << "({ #{at.join(', ')} })"
33
+ end
34
+ else
35
+ result << "Rux.tag('#{node.name}'"
36
+
37
+ unless node.attrs.empty?
38
+ result << ", { #{at.join(', ')} }"
39
+ end
40
+ end
41
+
42
+ result << ')'
43
+
44
+ if node.children.size > 1
45
+ result << " { "
46
+ result << "|#{block_arg}| " if block_arg
47
+ result << "Rux.create_buffer.tap { |_rux_buf_| "
48
+
49
+ node.children.each do |child|
50
+ result << "_rux_buf_ << #{visit(child).strip};"
51
+ end
52
+
53
+ result << " }.to_s }"
54
+ elsif node.children.size == 1
55
+ result << ' { '
56
+ result << "|#{block_arg}| " if block_arg
57
+ result << visit(node.children.first).strip
58
+ result << ' }'
59
+ end
60
+ end
61
+ end
62
+
63
+ def visit_text(node)
64
+ "\"#{CGI.escape_html(node.text)}\""
65
+ end
66
+ end
67
+ end
data/lib/rux/file.rb ADDED
@@ -0,0 +1,27 @@
1
+ module Rux
2
+ class File
3
+ attr_reader :path
4
+
5
+ def initialize(path)
6
+ @path = path
7
+ end
8
+
9
+ def to_ruby(visitor: Rux.default_visitor, **kwargs)
10
+ Rux.to_ruby(contents, visitor: visitor, **kwargs)
11
+ end
12
+
13
+ def write(outfile = nil, **kwargs)
14
+ ::File.write(outfile || default_outfile, to_ruby(**kwargs))
15
+ end
16
+
17
+ def default_outfile
18
+ @outfile ||= "#{path.chomp('.rux')}.rb"
19
+ end
20
+
21
+ private
22
+
23
+ def contents
24
+ @contents ||= ::File.read(path)
25
+ end
26
+ end
27
+ end
data/lib/rux/lex.rb ADDED
@@ -0,0 +1,9 @@
1
+ module Rux
2
+ module Lex
3
+ autoload :CharsetPattern, 'rux/lex/patterns'
4
+ autoload :DefaultPattern, 'rux/lex/patterns'
5
+ autoload :NegatedCharsetPattern, 'rux/lex/patterns'
6
+ autoload :State, 'rux/lex/state'
7
+ autoload :Transition, 'rux/lex/transition'
8
+ end
9
+ end
@@ -0,0 +1,41 @@
1
+ module Rux
2
+ module Lex
3
+ class DefaultPattern
4
+ def matches?(_)
5
+ true
6
+ end
7
+ end
8
+
9
+ class CharsetPattern
10
+ def self.parse(str)
11
+ pairs = str.scan(/(.)-?(.)?/)
12
+
13
+ new(
14
+ pairs.flat_map do |pair|
15
+ if pair[1]
16
+ (pair[0]..pair[1]).to_a
17
+ else
18
+ [pair[0]]
19
+ end
20
+ end
21
+ )
22
+ end
23
+
24
+ attr_reader :chars
25
+
26
+ def initialize(chars)
27
+ @chars = Set.new(chars)
28
+ end
29
+
30
+ def matches?(char)
31
+ chars.include?(char)
32
+ end
33
+ end
34
+
35
+ class NegatedCharsetPattern < CharsetPattern
36
+ def matches?(char)
37
+ !chars.include?(char)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,33 @@
1
+ module Rux
2
+ module Lex
3
+ class State
4
+ def self.parse(state_str, transition_strs, inputs)
5
+ is_terminal = state_str.end_with?('*')
6
+ state_name = "tRUX_#{state_str.chomp('*').upcase}".to_sym
7
+
8
+ transitions = transition_strs.each_with_object([]).with_index do |(ts, ret), idx|
9
+ ret << Transition.parse(ts, inputs[idx]) if ts
10
+ end
11
+
12
+ new(state_name, is_terminal, transitions)
13
+ end
14
+
15
+ attr_reader :name, :is_terminal, :transitions
16
+
17
+ alias_method :terminal?, :is_terminal
18
+
19
+ def initialize(name, is_terminal, transitions)
20
+ @name = name
21
+ @is_terminal = is_terminal
22
+ @transitions = transitions
23
+ @cache = {}
24
+ end
25
+
26
+ def [](chr)
27
+ @cache[chr] ||= transitions.find do |trans|
28
+ trans.matches?(chr)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,39 @@
1
+ ,[<],[a-zA-Z0-9_-_---:-:],[>],[/],(space),[=],"[""]",['],"[^""]",[^'],[{],[}],(default)
2
+ start,tag_open_test,,,,,,,,,,literal_ruby_code_start,,literal_body
3
+ tag_open_test,,tag_open_start[0],,tag_close_start,,,,,,,,,
4
+ tag_open_start*,,tag_open_body,,,,,,,,,,,
5
+ tag_open_body,,tag_open_body,tag_open[0],tag_self_closing[0],tag_open[0],,,,,,,,
6
+ tag_open*,,,tag_open_end,,attribute_spaces_body,,,,,,,,
7
+ tag_open_end*,,,,,,,,,,,,,
8
+ tag_close_start*,,tag_close_body,,,,,,,,,,,
9
+ tag_close_body,,tag_close_body,tag_close[0],,tag_close[0],,,,,,,,
10
+ tag_close*,,,tag_close_end,tag_self_closing[0],tag_close_spaces_body,,,,,,,,
11
+ tag_close_spaces_body,,,tag_close_spaces[0],,tag_close_spaces_body,,,,,,,,
12
+ tag_close_spaces*,,,tag_close_end,,,,,,,,,,
13
+ tag_close_end*,,,,,,,,,,,,,
14
+ tag_self_closing*,,,,tag_self_closing_start,,,,,,,,,
15
+ tag_self_closing_start,,,tag_self_closing_end,,,,,,,,,,
16
+ tag_self_closing_end*,,,,,,,,,,,,,
17
+ ,,,,,,,,,,,,,
18
+ attribute_spaces_body,,attribute_spaces[0],attribute_spaces[0],attribute_spaces[0],attribute_spaces_body,,,,,,,,
19
+ attribute_spaces*,,attribute_name_body,tag_close_start,tag_self_closing_start,,,,,,,,,
20
+ attribute_name_body,,attribute_name_body,attribute_name[0],attribute_name[0],attribute_name[0],attribute_name[0],,,,,,,
21
+ attribute_name*,,,tag_open_end,tag_self_closing_start,attribute_equals_spaces_body,attribute_equals,,,,,,,
22
+ attribute_equals_spaces_body,,,attribute_equals_spaces[0],,attribute_equals_spaces_body,attribute_equals_spaces[0],,,,,,,attribute_equals_spaces[0]
23
+ attribute_equals_spaces*,,,tag_open_end,tag_self_closing_start,,attribute_equals,,,,,,,attribute_name_body
24
+ attribute_equals*,,attribute_uq_body,,,attribute_value_spaces_body[0],,attribute_dq_body,attribute_sq_body,,,attribute_value_ruby_code_start,,
25
+ attribute_value_spaces_body,,attribute_value_spaces[0],,,attribute_value_spaces_body,,attribute_value_spaces[0],attribute_value_spaces[0],,,attribute_value_spaces[0],,
26
+ attribute_value_spaces*,,attribute_uq_body,,,,,attribute_dq_body,attribute_sq_body,,,attribute_value_ruby_code_start,,
27
+ attribute_dq_body,,,,,,,attribute_value,,attribute_dq_body,,,,
28
+ attribute_sq_body,,,,,,,,attribute_value,,attribute_sq_body,,,
29
+ attribute_uq_body,,attribute_uq_body,,,attribute_value,,,,,,,,
30
+ attribute_value_ruby_code_start*,,,,,,,,,,,,,attribute_value_ruby_code
31
+ attribute_value_ruby_code*,,,,,,,,,,,,attribute_value_ruby_code_end,
32
+ attribute_value_ruby_code_end*,,attribute_name,tag_open_end,tag_self_closing_start,attribute_spaces_body,,,,,,,,
33
+ attribute_value*,,attribute_name,tag_open_end,tag_self_closing_start,attribute_spaces_body,,attribute_value_ending,attribute_value_ending,,,,,
34
+ ,,,,,,,,,,,,,
35
+ literal_body,literal[0],,,,,,,,,,literal[0],,literal_body
36
+ literal*,,,,,,,,,,,literal_ruby_code_start,,
37
+ literal_ruby_code_start*,,,,,,,,,,,,,literal_ruby_code[0]
38
+ literal_ruby_code*,,,,,,,,,,,,literal_ruby_code_end,
39
+ literal_ruby_code_end*,,,,,,,,,,,,,
@@ -0,0 +1,22 @@
1
+ module Rux
2
+ module Lex
3
+ class Transition
4
+ attr_reader :input, :to_state, :advance_count
5
+
6
+ def self.parse(str, input)
7
+ to_state, advance_count = str.match(/\A(\w+)\[?(-?\d+)?\]?/).captures
8
+ new(input, :"tRUX_#{to_state.upcase}", (advance_count || 1).to_i)
9
+ end
10
+
11
+ def initialize(input, to_state, advance_count)
12
+ @input = input
13
+ @to_state = to_state
14
+ @advance_count = advance_count
15
+ end
16
+
17
+ def matches?(chr)
18
+ input.matches?(chr)
19
+ end
20
+ end
21
+ end
22
+ end