arst 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ module ARST
2
+ module Generator
3
+
4
+ class Base
5
+
6
+ def self.generate(node, options={})
7
+ new(node).generate(options)
8
+ end
9
+
10
+ attr_reader :node
11
+
12
+ def initialize(node)
13
+ @node = node
14
+ end
15
+
16
+ def generate(options={})
17
+ parse_children(@node, options)
18
+ end
19
+
20
+ protected
21
+
22
+ def parse_children(node, options={})
23
+ raise NotImplementedError
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,11 @@
1
+ require 'arst/generator/base'
2
+
3
+ module ARST
4
+ module Generator
5
+
6
+ class C < Base
7
+
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,164 @@
1
+ require 'arst/generator/base'
2
+ require 'arst/helpers'
3
+
4
+ module ARST
5
+ module Generator
6
+
7
+ # TODO: This class is so dirty >=) CLEANN EEETTT
8
+ class Ruby < Base
9
+
10
+ def initialize(node)
11
+ super(node)
12
+
13
+ @current_output = nil
14
+ end
15
+
16
+ protected
17
+
18
+ # =-=-= Hooks =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
19
+
20
+ def default_options
21
+ {
22
+ depth: 0,
23
+ split_files: true,
24
+ indent_size: 2,
25
+ indent_char: ' ',
26
+ newline_size: 1,
27
+ newline_character: "\n"
28
+ }
29
+ end
30
+
31
+ def indent(options)
32
+ (options[:indent_char] * options[:indent_size]) * options[:depth]
33
+ end
34
+
35
+ def newline(options)
36
+ newline_char = options[:newline_size] == 0 ? '; ' : options[:newline_character]
37
+
38
+ options[:newline_character] * options[:newline_size]
39
+ end
40
+
41
+ # =-=-= Parsers =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
42
+
43
+ def parse_children(node, options={})
44
+ options = default_options.merge( options )
45
+
46
+ if options[:split_files]
47
+ @current_output = []
48
+ parse_children_as_multiple_files(node, options)
49
+ else
50
+ @current_output = [ { filename: filename_for_single_file(node), body: '' } ]
51
+
52
+ parse_children_as_single_file(node, options)
53
+ end
54
+
55
+ result = @current_output
56
+ @current_output = nil
57
+
58
+ result
59
+ end
60
+
61
+ def parse_children_as_single_file(node, options={})
62
+ node.children.each do |node|
63
+ @current_output[0][:body] << indent(options) # TODO: DRY this up as a helper method
64
+ @current_output[0][:body] << code_from_node(node) # TODO: DRY this up as a helper method
65
+ @current_output[0][:body] << newline(options) # TODO: DRY this up as a helper method
66
+ parse_children_as_single_file( node, options.merge(depth: options[:depth]+1) ) unless node.children.empty?
67
+ @current_output[0][:body] << "#{ indent(options) }end#{ newline(options) }" if [:module, :class].include?(node.type)
68
+ end
69
+ end
70
+
71
+ def parse_children_as_multiple_files(node, options={})
72
+ options[:ancestors] = node.ancestors # The main (first) node's ancestor list
73
+
74
+ unless [:root, :include, :extend].include?(node.type) # TODO: Hold container (class, module) and non-container (include, extend) in helper method or constant
75
+ body_code = parse_node_for_multiple_files(node, options)
76
+ body_requirements = require_statements_from_node(node)
77
+ body = "#{ body_requirements }#{ body_code }"
78
+
79
+ @current_output << { filename: filename_for_split_files(node), body: body }
80
+ end
81
+ node.children.each { |child| parse_children_as_multiple_files(child, options) }
82
+ end
83
+
84
+ def parse_node_for_multiple_files(node, options={})
85
+ output = ''
86
+ options[:current_ancestor_index] ||= 0
87
+ ancestor = options[:ancestors][ options[:current_ancestor_index] ]
88
+
89
+ output << indent(options) # TODO: DRY this up as a helper method
90
+ output << code_from_node(ancestor) # TODO: DRY this up as a helper method
91
+ output << newline(options) # TODO: DRY this up as a helper method
92
+
93
+ if options[:current_ancestor_index] == options[:ancestors].count - 1 # The first node
94
+ child_options = options.merge(depth: options[:depth]+1)
95
+
96
+ ancestor.children.each do |child|
97
+ if [:include, :extend].include?(child.type) # TODO: Hold container (class, module) and non-container (include, extend) in helper method or constant
98
+ output << indent(child_options) # TODO: DRY this up as a helper method
99
+ output << code_from_node(child) # TODO: DRY this up as a helper method
100
+ output << newline(child_options) # TODO: DRY this up as a helper method
101
+ end
102
+ end
103
+ else
104
+ child_options = options.merge(depth: options[:depth]+1, current_ancestor_index: options[:current_ancestor_index]+1)
105
+ output << parse_node_for_multiple_files( ancestor, child_options )
106
+ end
107
+
108
+ output << "#{ indent(options) }end#{ newline(options) }" if [:module, :class].include?(ancestor.type)
109
+
110
+ output
111
+ end
112
+
113
+ # =-=-= Helpers =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
114
+
115
+ def filename_for_single_file(node)
116
+ node.children.collect(&:human_name).join('_and_') + '.rb'
117
+ end
118
+
119
+ def filename_for_split_files(node)
120
+ node.ancestors.collect(&:human_name).join('/') + '.rb'
121
+ end
122
+
123
+ def code_from_node(node)
124
+ case node.type
125
+ when :module
126
+ "module #{ node.name }"
127
+ when :class
128
+ code_class = "class #{ node.name }"
129
+ code_subclass = " < #{ node.superclass }" if node.superclass?
130
+
131
+ "#{code_class}#{code_subclass}"
132
+ when :extend
133
+ "extend #{ node.name }"
134
+ when :include
135
+ "include #{ node.name }"
136
+ else
137
+ ""
138
+ # TODO: Raise ARST::Error::InvalidNodeType
139
+ end
140
+ end
141
+
142
+ def require_statements_from_node(node)
143
+ # TODO: Determine scope of subclass or included/extended module
144
+ nodes_with_requirements = node.children.find_all do |child|
145
+ [:include, :extend].include?(child.type) || child.type == :class && child.superclass?
146
+ end
147
+
148
+ nodes_with_requirements.unshift(node) if node.type == :class && node.superclass?
149
+
150
+ nodes_with_requirements.collect! do |child|
151
+ name = child.type == :class ? child.superclass : child.name
152
+ ancestry = name.include?('::') ? name.split('::') : child.ancestors.collect(&:name)
153
+ path = ancestry.collect { |name| Helpers.underscore(name) }.join('/')
154
+
155
+ "require '#{path}'"
156
+ end
157
+
158
+ nodes_with_requirements.empty? ? nil : nodes_with_requirements.join("\n") << "\n\n"
159
+ end
160
+
161
+ end
162
+
163
+ end
164
+ end
@@ -0,0 +1,13 @@
1
+ module ARST
2
+
3
+ module Helpers
4
+
5
+ def underscore(string)
6
+ string.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
7
+ end
8
+
9
+ extend self
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,25 @@
1
+ require 'arst/node/root'
2
+ require 'arst/node/module'
3
+ require 'arst/node/class'
4
+ require 'arst/node/include'
5
+ require 'arst/node/extend'
6
+
7
+ module ARST
8
+
9
+ module Node
10
+
11
+ # TODO: Rename to something more specific... from_raw_tree?
12
+ def self.from_options(options)
13
+ case options[:type]
14
+ when 'module' then Node::Module.new(options)
15
+ when 'class' then Node::Class.new(options)
16
+ when 'extend' then Node::Extend.new(options)
17
+ when 'include' then Node::Include.new(options)
18
+ else
19
+ # TODO: Raise ARST::Error::InvalidNodeType
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,39 @@
1
+ require 'arst/helpers'
2
+
3
+ module ARST
4
+ module Node
5
+
6
+ class Base
7
+
8
+ attr_reader :parent, :children
9
+
10
+ def initialize(options={})
11
+ # TODO: Validate keys
12
+ options = default_options.merge(options)
13
+
14
+ @parent, @children = options.values_at(:parent, :children)
15
+ @children.collect! { |child_options| Node.from_options( child_options.merge(parent: self) ) }
16
+ end
17
+
18
+ def ancestors
19
+ return [] if parent.nil?
20
+
21
+ ( parent.ancestors || [] ) + [self]
22
+ end
23
+
24
+ def type
25
+ @type ||= ARST::Helpers.underscore(self.class.to_s.split(/::/).last).to_sym
26
+ end
27
+
28
+ protected
29
+
30
+ def default_options
31
+ {
32
+ children: []
33
+ }
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,32 @@
1
+ require 'arst/node/base'
2
+ require 'arst/node/namable'
3
+
4
+ module ARST
5
+ module Node
6
+
7
+ class Class < Base
8
+
9
+ include Namable
10
+
11
+ attr_reader :superclass
12
+
13
+ def initialize(options={})
14
+ super(options)
15
+
16
+ # TODO: Validate keys
17
+ self.superclass = options[:superclass]
18
+ end
19
+
20
+ def superclass=(superclass)
21
+ # TODO: Sanitize `superclass` given
22
+ @superclass = superclass.to_s unless superclass.nil?
23
+ end
24
+
25
+ def superclass?
26
+ !superclass.nil?
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,14 @@
1
+ require 'arst/node/base'
2
+ require 'arst/node/namable'
3
+
4
+ module ARST
5
+ module Node
6
+
7
+ class Extend < Base
8
+
9
+ include Namable
10
+
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ require 'arst/node/base'
2
+ require 'arst/node/namable'
3
+
4
+ module ARST
5
+ module Node
6
+
7
+ class Include < Base
8
+
9
+ include Namable
10
+
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ require 'arst/node/base'
2
+ require 'arst/node/namable'
3
+
4
+ module ARST
5
+ module Node
6
+
7
+ class Module < Base
8
+
9
+ include Namable
10
+
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,26 @@
1
+ module ARST
2
+ module Node
3
+
4
+ module Namable
5
+
6
+ attr_reader :name
7
+
8
+ def initialize(options={})
9
+ super(options)
10
+
11
+ # TODO: Validate keys
12
+ self.name = options[:name]
13
+ end
14
+
15
+ def name=(name)
16
+ @name = name.to_s # TODO: Sanitize
17
+ end
18
+
19
+ def human_name
20
+ ARST::Helpers.underscore(name)
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ require 'arst/node/base'
2
+
3
+ module ARST
4
+ module Node
5
+
6
+ class Root < Base
7
+
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,64 @@
1
+ require 'parslet'
2
+ require 'arst/node'
3
+
4
+ module ARST
5
+
6
+ # The parser for ARST.
7
+ class Parser < Parslet::Parser
8
+
9
+ def self.parse(input, options={})
10
+ new.parse(input, options)
11
+ end
12
+
13
+ def parse(input, options={})
14
+ tree = super(input)
15
+
16
+ options[:raw] ? tree : Node::Root.new(tree)
17
+ end
18
+
19
+ def indent(depth)
20
+ str(' ' * depth)
21
+ end
22
+
23
+ rule(:space) { match('[\t\s]') }
24
+ rule(:spaces) { space.repeat(1) }
25
+ rule(:newline) { match('\n') }
26
+
27
+ rule :constant do
28
+ match('[A-Z]') >> ( match('[A-Za-z0-9]') | str('::') ).repeat(1) # TODO: Cannot end with '::'
29
+ end
30
+
31
+ rule :module_keyword do
32
+ str('module').as(:type) >> spaces >> constant.as(:name)
33
+ end
34
+
35
+ rule :class_keyword do
36
+ str('class').as(:type) >> spaces >> constant.as(:name) >>
37
+ ( spaces >> str('<') >> spaces >> constant.as(:superclass) ).maybe
38
+ end
39
+
40
+ rule :include_keyword do
41
+ str('include').as(:type) >> space >> constant.as(:name)
42
+ end
43
+
44
+ rule :extend_keyword do
45
+ str('extend').as(:type) >> space >> constant.as(:name)
46
+ end
47
+
48
+ def node(depth)
49
+ indent(depth) >>
50
+ (
51
+ ( class_keyword | module_keyword ) >> newline.maybe >>
52
+ dynamic { |soutce, context| node(depth+1).repeat(0) }.as(:children) |
53
+ ( include_keyword | extend_keyword ) >> newline.maybe |
54
+ newline
55
+ )
56
+ end
57
+
58
+ rule(:document) { node(0).repeat.as(:children) }
59
+
60
+ root :document
61
+
62
+ end
63
+
64
+ end