luobo 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ = Luobo
2
+
3
+ Luobo is a simple, easy to extend code generator.
4
+
5
+ When you feed <tt>tuzi</tt> (bunny) with some luobo (carrot) script, it will produce sources base on your predefined rules.
6
+
7
+
@@ -0,0 +1,3 @@
1
+ require 'rspec/core/rake_task'
2
+ RSpec::Core::RakeTask.new('spec')
3
+
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ # vim syn=ruby
3
+
4
+ $: << 'lib'
5
+
6
+ require "luobo"
7
+
8
+ # TODO: optparser and build-in drivers.
9
+ file = ARGV[0]
10
+ out = STDOUT
11
+
12
+ Luobo::Base.new(file, out).process
@@ -0,0 +1,111 @@
1
+ DRIVER: ast
2
+
3
+ Raw line passed as it is.
4
+ AST: Raw line passed as it is.
5
+
6
+ ## SPLIT: abc df
7
+ AST: abc
8
+ AST: df
9
+
10
+ Raw line contains ->
11
+ fake block
12
+ AST: Raw line contains -> ->
13
+ AST: 2,fake block
14
+
15
+ ## SPEC: a spec blog ->
16
+ do that
17
+ do this
18
+ ## SPEC: nested spec ->
19
+ redo that
20
+ redo this
21
+ doing outside
22
+ doing further outside
23
+ AST: spec "a spec blog" do
24
+ AST: 2,do that
25
+ AST: 2,do this
26
+ AST: end
27
+ AST: 2,spec "nested spec" do
28
+ AST: 4,redo that
29
+ AST: 4,redo this
30
+ AST: 2,end
31
+ AST: 2,doing outside
32
+ AST: doing further outside
33
+
34
+ % =================
35
+ Give me a loop for <%= loop_name %>
36
+ SPEC: and has spec <%= loop_spec %> ->
37
+ specify a value as <%= value %>
38
+ % -----
39
+ % loop_name: looper
40
+ % loop_spec: spec a loop
41
+ % value: 89
42
+ % -----
43
+ % loop_name: looper again
44
+ % loop_spec: spec a loop again
45
+ % value: 899090
46
+ AST: Give me a loop for looper
47
+ AST: spec "and has spec spec a loop" do
48
+ AST: 2,specify a value as 89
49
+ AST: end
50
+ AST: Give me a loop for looper again
51
+ AST: spec "and has spec spec a loop again" do
52
+ AST: 2,specify a value as 899090
53
+ AST: end
54
+
55
+ Test a loop inside a block argument
56
+ AST: Test a loop inside a block argument
57
+
58
+ SPEC: loop inside ->
59
+ % ========================
60
+ key value <%= store %>
61
+ % -----
62
+ % store: factory
63
+ % -----
64
+ % store: firm
65
+ % ------
66
+ % store: person
67
+
68
+ AST: spec "loop inside" do
69
+ AST: 2, key value factory
70
+ AST: 2, key value firm
71
+ AST: 2, key value person
72
+ AST: end
73
+
74
+ Pure raw test in indent
75
+ Indent with 1 level
76
+ Indent with 2 levels
77
+
78
+ Indent with 2 levels again
79
+ Back!
80
+
81
+ AST: Pure raw test in indent
82
+ AST: 2,Indent with 1 level
83
+ AST: 4,Indent with 2 levels
84
+ AST: 4,Indent with 2 levels again
85
+ AST: Back!
86
+
87
+ UNKNOWN: unknown processor name treated as it is
88
+ AST: UNKNOWN: unknown processor name treated as it is
89
+
90
+ UNKBLOK: unknown with a block ->
91
+ This is quite strange!
92
+ Send back.
93
+ AST: UNKBLOK: unknown with a block -> ->
94
+ AST: 2,This is quite strange!
95
+ AST: Send back.
96
+
97
+ HASH: key_name: value_name
98
+ AST: key: key_name
99
+ AST: value: value_name
100
+
101
+ HASH: ->
102
+ blockkey: blockvalue
103
+ blockkey2: blockvalue2
104
+ blockkey3: blockvalue3
105
+
106
+ AST: key: blockkey
107
+ AST: value: blockvalue
108
+ AST: key: blockkey2
109
+ AST: value: blockvalue2
110
+ AST: key: blockkey3
111
+ AST: value: blockvalue3
@@ -0,0 +1,5 @@
1
+ require "yaml"
2
+
3
+ require "luobo/token"
4
+ require "luobo/driver"
5
+ require "luobo/base"
@@ -0,0 +1,211 @@
1
+ # encoding: utf-8
2
+
3
+ require "erubis"
4
+
5
+ module Luobo
6
+ ## entry class for the carrot module
7
+ class Base
8
+ attr_accessor :driver
9
+ def initialize file, output
10
+ @source_file = file
11
+
12
+ @output = output.is_a?(IO) ? output : File.open(output, "w")
13
+
14
+ # initialize:
15
+ @token_stack = Array.new
16
+
17
+ # initialize loop template and examples
18
+ self.reset_loop
19
+ end
20
+
21
+ # initialize the holders for a example based loop,
22
+ # or reset after a loop expansion
23
+ def reset_loop
24
+ @loop_start_ln = 0
25
+ @loop_template = nil
26
+ @loop_examples = Array.new
27
+ end
28
+
29
+ # extend a loop be examples
30
+ def expand_loop
31
+ raise "no examples found for loop start on line #{@loop_start_ln}" unless @loop_examples.size > 0
32
+ loop_n = 0
33
+ @loop_examples.each do |exa|
34
+ loop_n += 1
35
+ vars = YAML.load(exa)
36
+ # erubie the template
37
+ rslt = Erubis::Eruby.new(@loop_template).result(vars)
38
+ li = 0
39
+ rslt.split("\n").each do |line|
40
+ li += 1
41
+ self.process_line line, @loop_start_ln + li, loop_n
42
+ end
43
+ end
44
+
45
+ # clear up holders
46
+ self.reset_loop
47
+ end
48
+
49
+ def dump contents
50
+ @output.print contents if contents
51
+ end
52
+
53
+ # travel up through the token stack, close all token with
54
+ # indent level larger or equal to the parameter
55
+ def close_stack indent_level
56
+ source = ''
57
+ while @token_stack.size > 0 and @token_stack[-1].indent_level >= indent_level
58
+ if @token_stack.size > 1
59
+ @token_stack[-2].add_block_code @driver.convert(@token_stack[-1])
60
+ else # this is the last token in the stack
61
+ self.dump(@driver.convert(@token_stack[-1]))
62
+ end
63
+ @token_stack.pop
64
+ end
65
+ end
66
+
67
+ # add a new line to the existing token if it requires block codes
68
+ # or write the last token out before a new token
69
+ # this function invokes after a loop expansion.
70
+ def process_line line, ln, loop_n = 0
71
+ indent_level = 0
72
+ if /^(?<head_space_>\s+)/ =~ line
73
+ indent_level = head_space_.size
74
+ end
75
+
76
+ # try to close stack if applicable
77
+ self.close_stack indent_level
78
+
79
+ # starts a named_processor or starts a raw_processor
80
+ processor_name = '_raw'
81
+ line_code = line.gsub(/^\s*/,"")
82
+ block_code = nil
83
+ if matches = /#{regex_proc_line}/.match(line)
84
+ processor_name = matches["proc_name_"]
85
+ line_code = matches["line_code_"]
86
+ block_code = '' if line_code.gsub!(/#{regex_block_start}/, '')
87
+ end
88
+
89
+ @token_stack << Token.new(ln, line, indent_level, processor_name, line_code, block_code)
90
+
91
+ # unless it opens for block code close it soon,
92
+ # (dedicate the dump function to close_stack())
93
+ self.close_stack indent_level unless block_code
94
+ end
95
+
96
+ # add a new line to the example, separate examples to array
97
+ def add_example_line line
98
+ if /#{regex_new_example}/.match(line)
99
+ # start a new loop example with a placeholder nil
100
+ @loop_examples << nil
101
+ else
102
+ raise "you need use '#{regex_new_example}' to start a loop example" unless @loop_examples.size > 0
103
+ line.gsub!(/#{regex_example_head}/, '')
104
+ @loop_examples[-1] = @loop_examples[-1] ? @loop_examples[-1] + "\n" + line : line
105
+ end
106
+ end
107
+
108
+ # regex configurations
109
+ # match characters before a processor keyword
110
+ def regex_proc_head
111
+ "\s*(\#{2,}|\-{2,}|\/{2,})?\s*"
112
+ end
113
+
114
+ def regex_proc_name
115
+ "(?<proc_name_>[A-Z][A-Z0-9_]+)"
116
+ end
117
+
118
+ def regex_proc_end
119
+ "\s*\:?\s*"
120
+ end
121
+
122
+ def regex_proc_line
123
+ "^" + regex_proc_head + regex_proc_name + regex_proc_end + "(?<line_code_>.+)"
124
+ end
125
+
126
+ def regex_block_start
127
+ "\s*(?<block_start_>\-\>)?\s*$"
128
+ end
129
+
130
+ def regex_loop_line
131
+ "^" + regex_proc_head + "\%\s*\=\=\=\=+\s*"
132
+ end
133
+
134
+ def regex_new_example
135
+ "^" + regex_proc_head + "\%\s*\-\-\-\-+\s*"
136
+ end
137
+
138
+ def regex_example_head
139
+ "^" + regex_proc_head + "\%\s*"
140
+ end
141
+
142
+ def regex_example_line
143
+ "^" + regex_proc_head + "\%\s*(?<example_>.+)\s*"
144
+ end
145
+
146
+ # parse the file, whenever found a token, use driver to convert
147
+ # it and send to output
148
+ def process
149
+ in_loop = false
150
+ in_example = false
151
+
152
+ fh = File.open(@source_file, 'r:utf-8')
153
+ fh.each do |line|
154
+ line.chomp!
155
+ line.gsub!("\t", " ") # convert tab to 2 spaces
156
+
157
+ # ensure first line as driver definition
158
+ unless @driver
159
+ if matches = /^#{regex_proc_head}DRIVER#{regex_proc_end}(?<dname_>\w+)\s*$/.match(line)
160
+ @driver = Driver.create(matches["dname_"])
161
+ self.dump(@driver.setup)
162
+ next
163
+ else
164
+ raise "You must specify a driver on the first line"
165
+ end
166
+ end
167
+
168
+ # interrupt for loops
169
+ if matches = /#{regex_loop_line}/.match(line)
170
+ in_loop = true
171
+ @loop_start_ln = $.
172
+ next
173
+ end
174
+
175
+ # expand and end current loop
176
+ if in_example
177
+ unless /#{regex_example_head}/.match(line)
178
+ self.expand_loop
179
+ in_example = false
180
+ in_loop = false
181
+ end
182
+ end
183
+
184
+ # end a loop template and start a loop example
185
+ if in_loop
186
+ in_example = true if /#{regex_example_head}/.match(line)
187
+ end
188
+
189
+ # dispatch the current line to different holders
190
+ if in_example
191
+ self.add_example_line line
192
+ elsif in_loop
193
+ @loop_template = @loop_template ? @loop_template + "\n" + line : line
194
+ else
195
+ self.process_line line, $.
196
+ end
197
+ end
198
+ fh.close
199
+
200
+ # do not forget expand last loop if it reaches over EOF.
201
+ self.expand_loop if @loop_start_ln > 0
202
+
203
+ # close all remain stacks
204
+ self.close_stack 0
205
+
206
+ self.dump(@driver.exit)
207
+ @output.close
208
+ self
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,46 @@
1
+
2
+
3
+ module Luobo
4
+ class Driver
5
+
6
+ ## implements the factory pattern
7
+ @@drivers = {}
8
+
9
+ def self.create type
10
+ c = @@drivers[type.to_sym]
11
+ raise "No registered driver for type #{type.to_s}" unless c
12
+ c.new
13
+ end
14
+
15
+ def self.register_driver type
16
+ raise "Driver for #{type.to_s} already registed." if @@drivers[type.to_sym]
17
+ @@drivers[type.to_sym] = self
18
+ end
19
+
20
+ ## cross-drivers methods and callbacks
21
+ def convert token
22
+ pname = "do_" + token.processor_name.downcase
23
+ if self.respond_to?(pname)
24
+ self.send(pname.to_sym, token)
25
+ else
26
+ self.do__missing(token)
27
+ end
28
+ end
29
+
30
+ def exit; end
31
+ def setup; end
32
+
33
+ def indent token
34
+ " " * token.indent_level
35
+ end
36
+
37
+ def do__raw token
38
+ indent(token) + token.line_code.gsub(/^\s*/, "") + "\n"
39
+ end
40
+
41
+ def do__missing token
42
+ src = indent(token) + token.line
43
+ src += token.block_code + "\n" if token.block_code
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,28 @@
1
+ module Luobo
2
+ ## this class holds a block of carrot source code tokenized by the parser.
3
+ class Token
4
+ attr_accessor :ln, :line, :indent_level, :processor_name, :line_code, :block_code
5
+
6
+ def initialize ln, line, indent_level, processor_name, line_code, block_code
7
+ @ln, @line, @indent_level, @processor_name, @line_code, @block_code = ln, line, indent_level, processor_name, line_code, block_code
8
+ end
9
+
10
+ # add a line to current block args, separate each line with "\n"
11
+ def add_block_code line
12
+ line.chomp!
13
+ if self.block_code
14
+ self.block_code += "\n" + line
15
+ else
16
+ self.block_code = line
17
+ end
18
+ end
19
+
20
+ def block_args
21
+ YAML.load(block_code)
22
+ end
23
+
24
+ def line_args
25
+ YAML.load(line_code)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,4 @@
1
+ module Luobo
2
+ VERSION = '0.0.1'
3
+ end
4
+
@@ -0,0 +1,18 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
+ require 'luobo/version'
3
+
4
+ Gem::Specification.new 'luobo', Luobo::VERSION do |s|
5
+ s.description = "Luobo is a simple, easy to extend code generator."
6
+ s.summary = "Easy to extend code generator"
7
+ s.authors = ["Huang Wei"]
8
+ s.email = "huangw@pe-po.com"
9
+ s.homepage = "https://github.com/huangw/luobo-gem"
10
+ s.executables << "tuzi"
11
+ s.files = `git ls-files`.split("\n") - %w[.gitignore]
12
+ s.test_files = Dir.glob("{spec,test}/**/*.rb")
13
+ s.rdoc_options = %w[--line-numbers --inline-source --title Luobo --main README.rdoc --encoding=UTF-8]
14
+
15
+ s.add_development_dependency 'rspec', '~> 2.5'
16
+ s.add_dependency 'erubis'
17
+ end
18
+
@@ -0,0 +1,30 @@
1
+ require "spec_helper"
2
+
3
+ describe Luobo::Driver do
4
+ class TestDriver < Luobo::Driver
5
+ def do_test_met(tk)
6
+ "test method"
7
+ end
8
+ register_driver :test_driver
9
+ end
10
+
11
+ describe "create test driver" do
12
+ subject(:test_driver) do
13
+ Luobo::Driver.create(:test_driver)
14
+ end
15
+
16
+ it "is a test driver object" do
17
+ test_driver.is_a?(TestDriver).should be_true
18
+ end
19
+
20
+ its :class do
21
+ should eq(TestDriver)
22
+ end
23
+
24
+ it "inherit driver methods" do
25
+ tk = Luobo::Token.new(1, '', 1, 'test_met', '', '')
26
+ test_driver.convert(tk).should eq("test method")
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,78 @@
1
+ require "spec_helper"
2
+
3
+ describe Luobo::Base do
4
+ class OutputHolder < IO
5
+ attr_accessor :text
6
+ def initialize; @text = {} end
7
+ def print(str)
8
+ str.split("\n").each {|t| @text[t] = true}
9
+ end
10
+ def close; end
11
+ end
12
+
13
+ class AstDriver < Luobo::Driver
14
+ attr_accessor :asserts
15
+ def initialize
16
+ super()
17
+ @asserts = []
18
+ end
19
+
20
+ def do_ast token
21
+ str = ''
22
+ if /(?<ident_>\d+),\s*(?<rm_>.+)/ =~ token.line_code
23
+ str = " "*ident_.to_i + rm_
24
+ else
25
+ str = token.line_code
26
+ end
27
+ @asserts << str
28
+ nil
29
+ end
30
+
31
+ def do_split token
32
+ src = ""
33
+ token.line_code.split(/\s+/).each do |line|
34
+ src += line + "\n"
35
+ end
36
+ src
37
+ end
38
+
39
+ def do_spec token
40
+ src = indent(token) + "spec \"#{token.line_code}\" do"
41
+ src += token.block_code ? token.block_code : ""
42
+ src += "\n" + indent(token) + "end\n"
43
+ src
44
+ end
45
+
46
+ def do_hash token
47
+ hash = token.line_args if token.line_code.size > 0
48
+ if token.block_code.size > 0
49
+ hash = hash.is_a?(Hash) ? hash.merge(token.block_args) : token.block_args
50
+ end
51
+ src = ""
52
+ hash.each do |k,v|
53
+ src += indent(token) + "key: #{k}\n"
54
+ src += indent(token) + "value: #{v}\n"
55
+ end
56
+ src
57
+ end
58
+
59
+ register_driver :ast
60
+ end
61
+
62
+ describe "convert" do
63
+ oh = OutputHolder.new
64
+ base = Luobo::Base.new("example/parser.lub", oh).process
65
+
66
+ it "base should have a AstDriver" do
67
+ base.driver.is_a?(AstDriver)
68
+ p oh.text
69
+ end
70
+
71
+ base.driver.asserts.each do |line|
72
+ it "can parse #{line}" do
73
+ oh.text[line].should be_true
74
+ end
75
+ end
76
+ end
77
+
78
+ end
@@ -0,0 +1,7 @@
1
+ require 'rspec'
2
+ require 'luobo'
3
+
4
+ RSpec.configure do |config|
5
+ config.color_enabled = true
6
+ config.formatter = 'documentation'
7
+ end
@@ -0,0 +1,32 @@
1
+ require "spec_helper"
2
+
3
+ describe Luobo::Token do
4
+ subject (:token) do
5
+ Luobo::Token.new(1, "line", 4, "Echo", "same: hash", "['block', 'in', 'array']")
6
+ end
7
+
8
+ its :ln do
9
+ should eq(1)
10
+ end
11
+
12
+ its :line do
13
+ should eq("line")
14
+ end
15
+
16
+ its :indent_level do
17
+ should eq(4)
18
+ end
19
+
20
+ its :processor_name do
21
+ should eq("Echo")
22
+ end
23
+
24
+ its :line_args do
25
+ token.line_args["same"].should eq("hash")
26
+ end
27
+
28
+ its :block_args do
29
+ token.block_args[2].should eq("array")
30
+ end
31
+
32
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: luobo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Huang Wei
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2.5'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '2.5'
30
+ - !ruby/object:Gem::Dependency
31
+ name: erubis
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: Luobo is a simple, easy to extend code generator.
47
+ email: huangw@pe-po.com
48
+ executables:
49
+ - tuzi
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - README.rdoc
54
+ - Rakefile
55
+ - bin/tuzi
56
+ - example/parser.lub
57
+ - lib/luobo.rb
58
+ - lib/luobo/base.rb
59
+ - lib/luobo/driver.rb
60
+ - lib/luobo/token.rb
61
+ - lib/luobo/version.rb
62
+ - luobo.gemspec
63
+ - spec/driver_spec.rb
64
+ - spec/parser_spec.rb
65
+ - spec/spec_helper.rb
66
+ - spec/token_spec.rb
67
+ homepage: https://github.com/huangw/luobo-gem
68
+ licenses: []
69
+ post_install_message:
70
+ rdoc_options:
71
+ - --line-numbers
72
+ - --inline-source
73
+ - --title
74
+ - Luobo
75
+ - --main
76
+ - README.rdoc
77
+ - --encoding=UTF-8
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project:
94
+ rubygems_version: 1.8.24
95
+ signing_key:
96
+ specification_version: 3
97
+ summary: Easy to extend code generator
98
+ test_files:
99
+ - spec/driver_spec.rb
100
+ - spec/parser_spec.rb
101
+ - spec/spec_helper.rb
102
+ - spec/token_spec.rb