luobo 0.0.1

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.
@@ -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