rux 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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