caset 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.
- checksums.yaml +7 -0
- data/README.md +180 -0
- data/bin/cm +15 -0
- data/bin/cml +15 -0
- data/doc/MANUAL.md +100 -0
- data/doc/sample.csv +12 -0
- data/doc/sample.md +60 -0
- data/lib/caset/gen_bdd.rb +121 -0
- data/lib/caset/gen_caset.rb +41 -0
- data/lib/caset/gen_pytest.rb +211 -0
- data/lib/caset/gen_spec.rb +36 -0
- data/lib/caset/version.rb +3 -0
- data/lib/caset.rb +272 -0
- data/lib/casetdown/casetable.rb +121 -0
- data/lib/casetdown/casetcode.rb +345 -0
- data/lib/casetdown/casetdoc.rb +241 -0
- data/lib/casetdown/casetter.rb +141 -0
- data/lib/casetdown/cli.rb +129 -0
- data/lib/casetdown/live.rb +102 -0
- data/lib/casetdown/support/api_app.rb +127 -0
- data/lib/casetdown/support/endata.rb +197 -0
- data/lib/casetdown/support/simtab.rb +372 -0
- data/lib/casetdown/support/text_abstract.rb +124 -0
- data/lib/casetdown/support/tintext.rb +141 -0
- data/lib/casetdown.rb +48 -0
- metadata +79 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#coding:utf-8
|
|
2
|
+
require 'yaml'
|
|
3
|
+
|
|
4
|
+
=begin
|
|
5
|
+
INPUT: PYCODE.py + ABSTRACT.yml
|
|
6
|
+
OUTPUT: tests/PYTEST
|
|
7
|
+
=end
|
|
8
|
+
|
|
9
|
+
=begin # ABSTRACT TEMPLATE
|
|
10
|
+
--- # MODULE_NAME
|
|
11
|
+
|
|
12
|
+
# [basic header]
|
|
13
|
+
path: THE_FOLDER_PATH_OF_MODULE_SOURCE
|
|
14
|
+
module: MODULE_NAME
|
|
15
|
+
file:
|
|
16
|
+
FILENAME: # FILENAME INCLUDE ".py"
|
|
17
|
+
|
|
18
|
+
# [import]
|
|
19
|
+
import:
|
|
20
|
+
- REFMOD_NAME # => import REFMOD_NAME
|
|
21
|
+
- [REFMOD_NAME, SUB_REFMOD_NAME] # => from REFMOD_NAME import SUB_REFMOD_NAME
|
|
22
|
+
- [REFMOD_NAME, SUB_REFMOD_NAME, ALIAS_NAME] # => from REFMOD_NAME import SUB_REFMOD_NAME as ALIAS_NAME
|
|
23
|
+
|
|
24
|
+
# [external variables and functions]
|
|
25
|
+
varc:
|
|
26
|
+
- VAR_GLOBAL
|
|
27
|
+
- [VAR_GLOBAL, MODE]
|
|
28
|
+
- [VAR_GLOBAL, MODE, OG_VAR]
|
|
29
|
+
- [VAR_GLOBAL, MODE, OG_VAR, TYPE] # => [VAR_NAME|WRAPPER_NAME, null, null, TYPE|null]
|
|
30
|
+
func:
|
|
31
|
+
- [[EXTN_FUN_NAME, ARGV...], [REF_VAR_NAME|REF_FUN_NAME...], [RETN_VAL...]] # => [[MUST, OPTIONAL...], [OPTIONAL...], [OPTIONAL...]]
|
|
32
|
+
# no need to complete REF_STATEMENT and RETN_STATEMENT
|
|
33
|
+
|
|
34
|
+
# [two way to note vars and funs in class]
|
|
35
|
+
# one: only one class
|
|
36
|
+
CLASS:
|
|
37
|
+
BODY
|
|
38
|
+
# two: two or more classes
|
|
39
|
+
CLASSES:
|
|
40
|
+
- BODY
|
|
41
|
+
|
|
42
|
+
# [body of class]
|
|
43
|
+
CLASS:
|
|
44
|
+
name: CLASS_NAME
|
|
45
|
+
base: PARENT_CLASS_NAME
|
|
46
|
+
|
|
47
|
+
varc:
|
|
48
|
+
- CLASS_VAR
|
|
49
|
+
- [CLASS_VAR, MODE]
|
|
50
|
+
- [CLASS_VAR, MODE, OG_VAR]
|
|
51
|
+
- [CLASS_VAR, MODE, OG_VAR, TYPE] # => [VAR_NAME|WRAPPER_NAME, c|null, null, TYPE|null]
|
|
52
|
+
func:
|
|
53
|
+
- [[CLS_FUN_NAME]]
|
|
54
|
+
- [[CLS_FUN_NAME, ARGV...]]
|
|
55
|
+
- [[CLS_FUN_NAME, ARGV...], [REF_VAR_NAME|REF_FUN_NAME...]]
|
|
56
|
+
- [[CLS_FUN_NAME, ARGV...], [REF_VAR_NAME|REF_FUN_NAME...], [RETN_VAL...]] # => [[MUST, OPTIONAL...], [OPTIONAL...], [OPTIONAL...]]
|
|
57
|
+
# no need to complete REF_STATEMENT and RETN_STATEMENT
|
|
58
|
+
|
|
59
|
+
attr:
|
|
60
|
+
- INS_VAR
|
|
61
|
+
- [INS_VAR, MODE]
|
|
62
|
+
- [INS_VAR, MODE, OG_VAR]
|
|
63
|
+
- [INS_VAR, MODE, OG_VAR, TYPE] # => [VAR_NAME|WRAPPER_NAME, r|w|rw|i|null, ORIGINAL_VAR_NAME|null, TYPE|null]
|
|
64
|
+
fun:
|
|
65
|
+
- [[INS_FUN_NAME]]
|
|
66
|
+
- [[INS_FUN_NAME, ARGV...]]
|
|
67
|
+
- [[INS_FUN_NAME, ARGV...], [REF_VAR_NAME|REF_FUN_NAME...]]
|
|
68
|
+
- [[INS_FUN_NAME, ARGV...], [REF_VAR_NAME|REF_FUN_NAME...], [RETN_VAL...]] # => [[MUST, OPTIONAL...], [OPTIONAL...], [OPTIONAL...]]
|
|
69
|
+
# no need to complete REF_STATEMENT and RETN_STATEMENT
|
|
70
|
+
=end
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
=begin # EXAMPLE
|
|
74
|
+
--- # SERVICE_A
|
|
75
|
+
path: ~/workspace/dev/server/service_a
|
|
76
|
+
module: service_a
|
|
77
|
+
|
|
78
|
+
file:
|
|
79
|
+
service_a.py:
|
|
80
|
+
import:
|
|
81
|
+
- [__future__, absolute_import]
|
|
82
|
+
- sys
|
|
83
|
+
- [server.base_template, base_service, Service]
|
|
84
|
+
- [server.service_b, ServiceB]
|
|
85
|
+
|
|
86
|
+
classes: # or class and the value is only one item of hash
|
|
87
|
+
- name: ServiceA
|
|
88
|
+
base: Service
|
|
89
|
+
attr:
|
|
90
|
+
- [result, rw, dict, ResultA] # attr as r|w|rw methods
|
|
91
|
+
- [tmp, null, null]
|
|
92
|
+
fun:
|
|
93
|
+
- [[__init__],[tmp]]
|
|
94
|
+
- [[tmp_cal, tmp_val],[tmp, result], [result,size(result)]]
|
|
95
|
+
varc:
|
|
96
|
+
- a
|
|
97
|
+
- [b, null, null, str]
|
|
98
|
+
- c
|
|
99
|
+
func:
|
|
100
|
+
- [[tmp_zzZ, a,b,c]]
|
|
101
|
+
|
|
102
|
+
varc:
|
|
103
|
+
- d
|
|
104
|
+
- [e, null, null]
|
|
105
|
+
- [f, null, null, int]
|
|
106
|
+
func:
|
|
107
|
+
- [[tmp_ooO, d, e, f]]
|
|
108
|
+
|
|
109
|
+
=end
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
module Caset
|
|
113
|
+
module Pytest
|
|
114
|
+
module_function
|
|
115
|
+
|
|
116
|
+
def gen option
|
|
117
|
+
path, head, level = option[:path], option[:head], option[:level]
|
|
118
|
+
doc = YAML.load(File.read(path))
|
|
119
|
+
ohead = "#{doc['path']}/#{doc['module'] ? doc['module']+"/" : ''}"
|
|
120
|
+
pkgroute = head+ohead.split(head)[-1].gsub('/','.')
|
|
121
|
+
`mkdir -p tests/#{path.sub('.yml','')}`
|
|
122
|
+
|
|
123
|
+
doc['file'].each do|filename, abstract|
|
|
124
|
+
inner_fun_set = []
|
|
125
|
+
classes = (abstract['classes'] || [abstract['class']] || []).compact
|
|
126
|
+
classes.each do|klass|
|
|
127
|
+
inner_funcs,inner_funs = [], []
|
|
128
|
+
|
|
129
|
+
vars = klass['varc'] || []
|
|
130
|
+
vars.each do|var|
|
|
131
|
+
varname,varattr = var.instance_of?(Array) ? var[0..1] : [var,nil]
|
|
132
|
+
handle = level.include?(:strict) ? "raise Exception(\"No Implement\")" : ""
|
|
133
|
+
handle = level.include?(:fail) ? "assert False" : ""
|
|
134
|
+
tips = level.include?(:pending) ? "\"TODO: #{varname}#{varattr ? " :#{varattr}" : varattr}\"" : ""
|
|
135
|
+
adaptr = level.include?(:pending) ? " @pytest.mark.unfinished\n" : ""
|
|
136
|
+
inner_funcs << "#{adaptr} def test_#{varname}(self):\n #{handle}#{tips.empty? ? tips : "\n "+tips}\n"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
funcs = klass['func'] || []
|
|
140
|
+
funcs.each do|func|
|
|
141
|
+
sign = func[0]
|
|
142
|
+
next if sign[0]=='__init__'
|
|
143
|
+
handle = level.include?(:strict) ? "raise Exception(\"No Implement\")" : ""
|
|
144
|
+
handle = level.include?(:fail) ? "assert False" : ""
|
|
145
|
+
tips = level.include?(:pending) ? "\"TODO: #{sign[0]}(#{sign[1..-1].join(', ')})\"" : ""
|
|
146
|
+
adaptr = level.include?(:pending) ? " @pytest.mark.unfinished\n" : ""
|
|
147
|
+
inner_funcs << "#{adaptr} def test_#{sign[0]}(self):\n #{handle}#{tips.empty? ? tips : "\n "+tips}\n"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
vars = klass['attr'] || []
|
|
151
|
+
vars.each do|var|
|
|
152
|
+
varname,varattr = var.instance_of?(Array) ? var[0..1] : [var,nil]
|
|
153
|
+
handle = level.include?(:strict) ? "raise Exception(\"No Implement\")" : ""
|
|
154
|
+
handle = level.include?(:fail) ? "assert False" : ""
|
|
155
|
+
tips = level.include?(:pending) ? "\"TODO: #{varname}#{varattr ? " :#{varattr}" : varattr}\"" : ""
|
|
156
|
+
adaptr = level.include?(:pending) ? " @pytest.mark.unfinished\n" : ""
|
|
157
|
+
inner_funs << "#{adaptr} def test_#{varname}(self):\n #{handle}#{tips.empty? ? tips : "\n "+tips}\n"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
funs = klass['fun'] || []
|
|
161
|
+
funs.each do|fun|
|
|
162
|
+
sign = fun[0]
|
|
163
|
+
next if sign[0]=='__init__'
|
|
164
|
+
handle = level.include?(:strict) ? "raise Exception(\"No Implement\")" : ""
|
|
165
|
+
handle = level.include?(:fail) ? "assert False" : ""
|
|
166
|
+
tips = level.include?(:pending) ? "\"TODO: #{sign[0]}(#{sign[1..-1].join(', ')})\"" : ""
|
|
167
|
+
adaptr = level.include?(:pending) ? " @pytest.mark.unfinished\n" : ""
|
|
168
|
+
inner_funs << "#{adaptr} def test_#{sign[0]}(self):\n #{handle}#{tips.empty? ? tips : "\n "+tips}\n"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
unless inner_funcs.empty?
|
|
172
|
+
inner_funcs.unshift " def teardown_class(self):\n \"TODO: TEARDOWN CLASSMETHOD\"\n"
|
|
173
|
+
inner_funcs.unshift " def setup_class(self):\n \"TODO: SETUP CLASSMETHOD\"\n"
|
|
174
|
+
end
|
|
175
|
+
unless inner_funs.empty?
|
|
176
|
+
inner_funs.unshift " def teardown_method(self):\n \"TODO: TEARDOWN METHOD\"\n"
|
|
177
|
+
inner_funs.unshift " def setup_method(self):\n \"TODO: SETUP METHOD\"\n"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
inner_tmp = inner_funcs + inner_funs
|
|
181
|
+
inner_tmp.unshift "class #{klass['name']}Test():" unless inner_tmp.empty?
|
|
182
|
+
inner_fun_set += inner_tmp
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
outer_funcs = []
|
|
186
|
+
funcs = abstract['func'] || []
|
|
187
|
+
funcs.each do|func|
|
|
188
|
+
sign = func[0]
|
|
189
|
+
handle = level.include?(:strict) ? "raise Exception(\"No Implement\")" : ""
|
|
190
|
+
handle = level.include?(:fail) ? "assert False" : ""
|
|
191
|
+
tips = level.include?(:pending) ? "\"TODO: #{sign[0]}(#{sign[1..-1].join(', ')})\"" : ""
|
|
192
|
+
adaptr = level.include?(:pending) ? "@pytest.mark.unfinished\n" : ""
|
|
193
|
+
outer_funcs << "#{adaptr}def test_#{sign[0]}():\n #{handle}#{tips.empty? ? tips : "\n "+tips}\n"
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
unless outer_funcs.empty?
|
|
197
|
+
outer_funcs.unshift "def teardown_function():\n \"TODO: TEARDOWN FUNCTION\"\n"
|
|
198
|
+
outer_funcs.unshift "def setup_function():\n \"TODO: SETUP FUNCTION\"\n"
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
content = (inner_fun_set + outer_funcs)
|
|
202
|
+
unless content.empty?
|
|
203
|
+
content.unshift "# import sys\n# sys.path.append(\".\")\n# PACKAGE ROUTE: #{pkgroute}#{filename[0..-4]}\n"
|
|
204
|
+
content.unshift "#coding:utf-8\nimport pytest\n"
|
|
205
|
+
testpath = "tests/#{path.sub('.yml','')}/test_#{filename}"
|
|
206
|
+
File.write testpath, content.join("\n") unless File.exist?(testpath)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#coding:utf-8
|
|
2
|
+
|
|
3
|
+
# Usage:
|
|
4
|
+
# Spec.gen(ARGV,template)
|
|
5
|
+
|
|
6
|
+
module Spec
|
|
7
|
+
THE_SPEC_TEMPLATE = %Q{#coding:utf-8
|
|
8
|
+
$LOAD_PATH << "spec"
|
|
9
|
+
|
|
10
|
+
describe ((name)) do
|
|
11
|
+
context "..." do
|
|
12
|
+
before(:each) do
|
|
13
|
+
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "..." do
|
|
17
|
+
pending
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
after(:each) do
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end}
|
|
25
|
+
|
|
26
|
+
def self.gen names,template=THE_SPEC_TEMPLATE
|
|
27
|
+
Dir.mkdir("spec") unless File.exist?("spec")
|
|
28
|
+
["middleware","asset","support"].each do|subdir|
|
|
29
|
+
Dir.mkdir("spec/#{subdir}") unless File.exist?("spec/#{subdir}")
|
|
30
|
+
end
|
|
31
|
+
File.write("spec/spec_helper.rb","#coding:utf-8\n\n") unless File.exist?("spec/spec_helper.rb")
|
|
32
|
+
names.each do|name|
|
|
33
|
+
File.write("spec/#{name}_spec.rb",template.gsub("((name))",name)) unless File.exist?("spec/#{name}_spec.rb")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/caset.rb
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#coding:utf-8
|
|
2
|
+
require "caset/version"
|
|
3
|
+
require 'caset/gen_caset'
|
|
4
|
+
require 'caset/gen_bdd'
|
|
5
|
+
require 'caset/gen_spec'
|
|
6
|
+
require 'caset/gen_pytest'
|
|
7
|
+
|
|
8
|
+
module Caset
|
|
9
|
+
class Error < StandardError; end
|
|
10
|
+
|
|
11
|
+
#### 基础管道 ####
|
|
12
|
+
def self.construct
|
|
13
|
+
@__caset_register__ = { before_each: [], after_each: [], before_all: [], after_all: [], steps: {}, story: [] }
|
|
14
|
+
@__caset_report__ = []
|
|
15
|
+
@__caset_document__ = []
|
|
16
|
+
@__caset_indent__ = ""
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.register
|
|
20
|
+
@__caset_register__
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.report
|
|
24
|
+
@__caset_report__
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.document
|
|
28
|
+
@__caset_document__
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.indent
|
|
32
|
+
@__caset_indent__
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def msg context
|
|
36
|
+
if context.instance_of?(Array)
|
|
37
|
+
context.each do|pile|
|
|
38
|
+
Caset.report << Caset.indent+pile.to_s
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
Caset.report << Caset.indent+context.to_s
|
|
42
|
+
end
|
|
43
|
+
Caset.report[-1]
|
|
44
|
+
end
|
|
45
|
+
alias :记录 :msg
|
|
46
|
+
|
|
47
|
+
def mark context
|
|
48
|
+
if context.instance_of?(Array)
|
|
49
|
+
context.each do|pile|
|
|
50
|
+
Caset.document << Caset.indent+pile.to_s
|
|
51
|
+
end
|
|
52
|
+
else
|
|
53
|
+
Caset.document << Caset.indent+context.to_s
|
|
54
|
+
end
|
|
55
|
+
Caset.document[-1]
|
|
56
|
+
end
|
|
57
|
+
alias :标记 :mark
|
|
58
|
+
|
|
59
|
+
def pending reason=nil
|
|
60
|
+
mark "pending"+(reason ? ":#{reason}" : '')
|
|
61
|
+
msg "pending"+(reason ? ":#{reason}" : '')
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def 待定 原因=nil
|
|
65
|
+
mark "待定"+(原因 ? ":#{原因}" : '')
|
|
66
|
+
msg "待定"+(原因 ? ":#{原因}" : '')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def star context
|
|
70
|
+
mark "* #{context}"
|
|
71
|
+
end
|
|
72
|
+
alias :星 :star
|
|
73
|
+
|
|
74
|
+
def plus context
|
|
75
|
+
mark "+ #{context}"
|
|
76
|
+
end
|
|
77
|
+
alias :加 :plus
|
|
78
|
+
|
|
79
|
+
def minus context
|
|
80
|
+
mark "- #{context}"
|
|
81
|
+
end
|
|
82
|
+
alias :减 :minus
|
|
83
|
+
|
|
84
|
+
def Give context
|
|
85
|
+
mark "Give #{context}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def When context
|
|
89
|
+
mark "When #{context}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def Then context
|
|
93
|
+
mark "Then #{context}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def And context
|
|
97
|
+
mark " #{context}"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def 如果 上下文
|
|
101
|
+
mark "如果#{上下文}"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def 当 上下文
|
|
105
|
+
mark "当#{上下文}"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def 那么 上下文
|
|
109
|
+
mark "那么#{上下文}"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def 而且 上下文
|
|
113
|
+
mark "而且#{上下文}"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def self.indent= indent
|
|
117
|
+
@__caset_indent__ = indent
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
#### 设定结构 ####
|
|
121
|
+
def scenario description,options=[],&block
|
|
122
|
+
# registration
|
|
123
|
+
Caset.construct
|
|
124
|
+
unless options.include?(:bm_story) or options.include?(:bm_step)
|
|
125
|
+
block.call # all blocks regist
|
|
126
|
+
else
|
|
127
|
+
require 'benchmark'
|
|
128
|
+
end
|
|
129
|
+
Benchmark.bm{|x| @_bm_story_ = x;block.call } if options.include?(:bm_story)
|
|
130
|
+
Benchmark.bm{|x| @_bm_step_ = x;block.call } if options.include?(:bm_step)
|
|
131
|
+
|
|
132
|
+
# execution
|
|
133
|
+
msg "====#{description}===="
|
|
134
|
+
mark "====#{description}===="
|
|
135
|
+
|
|
136
|
+
Caset.indent = " " # for before-all
|
|
137
|
+
msg "== before all ==" unless Caset.register[:before_all].empty?
|
|
138
|
+
mark "preconfig:" unless Caset.register[:before_all].empty?
|
|
139
|
+
Caset.register[:before_all].each do|before_all_transaction|
|
|
140
|
+
Caset.indent = " " # for inner before-all
|
|
141
|
+
before_all_transaction.call
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
Caset.register[:story].each do|stories|
|
|
145
|
+
description,story = stories
|
|
146
|
+
run_case(description,story)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
Caset.indent = " " # for after-all
|
|
150
|
+
msg "== after all ==" unless Caset.register[:after_all].empty?
|
|
151
|
+
mark "postconfig:" unless Caset.register[:after_all].empty?
|
|
152
|
+
Caset.register[:after_all].each do|after_all_transaction|
|
|
153
|
+
Caset.indent = " " # for inner after-all
|
|
154
|
+
after_all_transaction.call
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
Caset.indent = "" # for scenario
|
|
158
|
+
msg "==== end scenario ===="
|
|
159
|
+
mark "==== end scenario ===="
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def before options=[:each],&block
|
|
163
|
+
if options.include?(:each)
|
|
164
|
+
Caset.register[:before_each] << block
|
|
165
|
+
elsif options.include?(:all)
|
|
166
|
+
Caset.register[:before_all] << block
|
|
167
|
+
else
|
|
168
|
+
# :off
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def after options=[:each],&block
|
|
173
|
+
if options.include?(:each)
|
|
174
|
+
Caset.register[:after_each] << block
|
|
175
|
+
elsif options.include?(:all)
|
|
176
|
+
Caset.register[:after_all] << block
|
|
177
|
+
else
|
|
178
|
+
# :off
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def usecase description,options=[],&block
|
|
183
|
+
unless options.include?(:off)
|
|
184
|
+
Caset.register[:story] << [description,block]
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
alias :ustory :usecase
|
|
188
|
+
|
|
189
|
+
def ustep id, description=nil,options=[], &block
|
|
190
|
+
unless options.include?(:off)
|
|
191
|
+
Caset.register[:step] ||= {}
|
|
192
|
+
Caset.register[:step][id] = block
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
#### 控制结构 ####
|
|
197
|
+
def run_step(id, description=nil, *args)
|
|
198
|
+
Caset.indent = " "
|
|
199
|
+
mark "#{id}.#{description}"
|
|
200
|
+
if Caset.register[:step][id]
|
|
201
|
+
msg "<#{id}>#{description}"
|
|
202
|
+
Caset.indent = " "
|
|
203
|
+
|
|
204
|
+
unless @_bm_step_
|
|
205
|
+
Caset.register[:step][id].call(*args)
|
|
206
|
+
else
|
|
207
|
+
@_bm_step_.report(id.to_s+"."+description.to_s){Caset.register[:step][id].call(*args)}
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
else
|
|
211
|
+
msg "<Warning>You are trying to call a step(#{id}) yet defined!"
|
|
212
|
+
end
|
|
213
|
+
Caset.indent = " "
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# steps = [step,...]
|
|
217
|
+
# step = {:id=>id, :desc=>description, :args=>args}
|
|
218
|
+
def run_steps(steps)
|
|
219
|
+
steps.each do|step|
|
|
220
|
+
run_step step[:id],step[:desc],*(step[:args]||[])
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def run_case description,block
|
|
225
|
+
Caset.indent = " " # for before-each
|
|
226
|
+
msg "== before_each ==" unless Caset.register[:before_each].empty?
|
|
227
|
+
mark "prepare:" unless Caset.register[:before_each].empty?
|
|
228
|
+
Caset.indent = " " # for inner before-each
|
|
229
|
+
Caset.register[:before_each].each do|before_each_transaction|
|
|
230
|
+
before_each_transaction.call
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
Caset.indent = " " # for ustory
|
|
234
|
+
msg "#{description}"
|
|
235
|
+
mark "[#{description}]"
|
|
236
|
+
Caset.indent = " " # for inner ustory
|
|
237
|
+
unless @_bm_story_
|
|
238
|
+
block.call
|
|
239
|
+
else
|
|
240
|
+
@_bm_story_.report(description){block.call}
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
Caset.indent = " " # for after-each
|
|
244
|
+
msg "== after each ==" unless Caset.register[:after_each].empty?
|
|
245
|
+
mark "posthandle:" unless Caset.register[:after_each].empty?
|
|
246
|
+
Caset.indent = " " # for inner after-each
|
|
247
|
+
Caset.register[:after_each].each do|after_each_transaction|
|
|
248
|
+
after_each_transaction.call
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
Caset.indent = " " # return to ustory level
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
class Numeric
|
|
256
|
+
def is title,left='(',right=')'
|
|
257
|
+
Caset.document
|
|
258
|
+
Caset.document << "#{Caset.indent}#{left}#{self}#{right}#{title}\n"
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def 是 标题,左='(',右=')';is 标题,左,右;end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
class String
|
|
265
|
+
def is title,left='(',right=')'
|
|
266
|
+
Caset.document
|
|
267
|
+
Caset.document << "#{Caset.indent}#{left}#{self}#{right}#{title}\n"
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def 是 标题,左='(',右=')';is 标题,左,右;end
|
|
271
|
+
end
|
|
272
|
+
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#coding:utf-8
|
|
2
|
+
require 'csv'
|
|
3
|
+
|
|
4
|
+
#------------------------------------------------------------------------------
|
|
5
|
+
# CSV 表格读写与渲染
|
|
6
|
+
#------------------------------------------------------------------------------
|
|
7
|
+
module CasetDown
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# 读取 CSV 文件并转换为二维表。
|
|
11
|
+
#
|
|
12
|
+
# 编码策略(对齐 SimTab.读取csv):优先按 UTF-8 解析(自动剥离 BOM),
|
|
13
|
+
# 内容不是合法 UTF-8 时回退按 GBK 转码,兼容历史报表。
|
|
14
|
+
# 支持表头解析(format == :csv 时解析表头)。
|
|
15
|
+
#
|
|
16
|
+
# @param path [String] CSV 文件路径
|
|
17
|
+
# @param format [Symbol] 解析模式,:raw 或 :csv(默认 :raw)
|
|
18
|
+
# @return [Object] 绑定表头的二维数组对象(含 绑定表头 方法)
|
|
19
|
+
def loadcsv path,format=:raw
|
|
20
|
+
raw = File.binread(path)
|
|
21
|
+
raw = raw.sub(/\A\xEF\xBB\xBF/n, '') # 剥离 UTF-8 BOM
|
|
22
|
+
csv = raw.dup.force_encoding('UTF-8')
|
|
23
|
+
csv = raw.dup.force_encoding('GBK').encode('UTF-8') unless csv.valid_encoding?
|
|
24
|
+
content = CSV.parse(csv, headers:(format==:csv))
|
|
25
|
+
table = content.绑定表头 content[0]
|
|
26
|
+
return table
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# 将二维表转换为 Markdown 表格字符串。
|
|
30
|
+
#
|
|
31
|
+
# 自动生成 |---|---| 分隔行。
|
|
32
|
+
#
|
|
33
|
+
# @param table [Array<Array>] 二维表数据
|
|
34
|
+
# @return [String] Markdown 格式表格字符串
|
|
35
|
+
# @example
|
|
36
|
+
# CasetDown.maketab([["a", "b"], ["1", "2"]])
|
|
37
|
+
# # => "|a|b|\n|---|---|\n|1|2|"
|
|
38
|
+
def maketab table
|
|
39
|
+
printab = table.map{|row|"|"+row.join("|")+"|"}
|
|
40
|
+
printab.insert 1, Array.new(table[0].size, '|---').join+"|"
|
|
41
|
+
return printab.join("\n")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# 渲染文档中的表格,支持 CSV 读写与表格替换回文档。
|
|
45
|
+
#
|
|
46
|
+
# 两种工作模式:
|
|
47
|
+
# 1. 将文档中的 table 写入 CSV 文件(:wtab)
|
|
48
|
+
# 2. 将代码块加载的 CSV 表格替换回文档(:rtab)
|
|
49
|
+
#
|
|
50
|
+
# @param cds [Hash] 解析结果
|
|
51
|
+
# @param option [Hash] 控制选项
|
|
52
|
+
# @option option [String] :stream 覆盖原始文档文本
|
|
53
|
+
# @option option [Array<Symbol>] :opt 渲染标志
|
|
54
|
+
# - :wtab 将文档中的 table 写入 CSV 文件
|
|
55
|
+
# - :rtab 将代码块加载的 CSV 替换回文档
|
|
56
|
+
# - :rendfile 保存为带时间戳的新文件
|
|
57
|
+
# @return [Hash] 渲染结果
|
|
58
|
+
# - :text [String] 处理后的文档文本
|
|
59
|
+
# - :data [String, nil] Ruby 脚本尾部数据(endata 格式)
|
|
60
|
+
# - :fact [String, nil] Erlang 脚本尾部数据(fact 函数子句)
|
|
61
|
+
# @note 表格名前需要从 ((TableName)) 格式段落提取
|
|
62
|
+
# @note 数据尾部使用局部变量累积:cml 会同一进程内反复调用本方法,
|
|
63
|
+
# 若沿用全局变量累积,重复渲染会使 __END__ 数据段与 erlang fact
|
|
64
|
+
# 子句叠加(同名 fact 子句 erlang 首个匹配优先,旧数据会遮蔽新数据)
|
|
65
|
+
def render_table cds, option={ stream: nil, opt: []} # empty, :wtab, :rtab, :rendfile
|
|
66
|
+
endata = {}
|
|
67
|
+
# write table to csv file
|
|
68
|
+
docs = CasetDown::Check.all(cds)
|
|
69
|
+
docs.each_with_index do|doc,index|
|
|
70
|
+
if doc[:name]=='table'
|
|
71
|
+
table_name = ( index-1 >= 0 && docs[index-1][:name]=='p' && docs[index-1][:text].strip[0..1]=='((' && docs[index-1][:text].strip[-2..-1]=='))' ) ? docs[index-1][:text][2..-3].strip : 'Anonymous'
|
|
72
|
+
table_body = [doc[:head]]+doc[:body]
|
|
73
|
+
endata["[table@endata ~]$ruby #{table_name}"] = table_body.to_s
|
|
74
|
+
CSV.open(table_name+".csv","w")do|writer|
|
|
75
|
+
table_body.each do|record|
|
|
76
|
+
writer << record
|
|
77
|
+
end
|
|
78
|
+
end if option[:opt].include?(:wtab)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# load table from csv file
|
|
83
|
+
result = {}
|
|
84
|
+
codtab = docs.select{|doc|doc[:name]=='code'}
|
|
85
|
+
codtab.each do|code|
|
|
86
|
+
interpreter, namespace, config, target, flow, segment, input, output, rows, cols = magic_head code
|
|
87
|
+
next unless output.to_s[-4..-1]=='.csv'
|
|
88
|
+
tab = loadcsv(output)
|
|
89
|
+
seltab = rows.inject([tab[0]]) do|seltab,row|
|
|
90
|
+
seltab += [tab[row]] if row.is_a?(Integer)
|
|
91
|
+
seltab += tab[row] if row.is_a?(Range)
|
|
92
|
+
seltab
|
|
93
|
+
end
|
|
94
|
+
seltab.绑定表头 seltab[0]
|
|
95
|
+
newtab = seltab.选择(*cols)
|
|
96
|
+
result[output] = maketab(newtab)
|
|
97
|
+
endata["[table@endata ~]$ruby #{output}"] = newtab.to_s
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
scr_tail, erl_tail = nil, ''
|
|
101
|
+
endata.each do|head, table|
|
|
102
|
+
(scr_tail ||= "__END__") << "\n#{head}\n#{table}\n"
|
|
103
|
+
erl_tail << "\n\n"+ToErlang.fact(head.split('$ruby ')[1], ToErlang.table(eval(table)))
|
|
104
|
+
end
|
|
105
|
+
origin_text = option[:stream] || cds[:doc].text
|
|
106
|
+
result.each do|output, newtab|
|
|
107
|
+
wrapped = "((#{output}))\n\n#{newtab}"
|
|
108
|
+
origins = CasetDown.search_output(origin_text, output)
|
|
109
|
+
origins.each do|origin|
|
|
110
|
+
origin_text = origin_text.gsub(origin, wrapped)
|
|
111
|
+
end
|
|
112
|
+
end if option[:opt].include?(:rtab)
|
|
113
|
+
|
|
114
|
+
if option[:opt].include?(:rendfile)
|
|
115
|
+
pazz = cds[:path].split('.')
|
|
116
|
+
pazz.insert(-2,Time.new.strftime("%Y%m%d%H%M%S"))
|
|
117
|
+
File.write pazz.join('.'), origin_text
|
|
118
|
+
end
|
|
119
|
+
return { text: origin_text, data: scr_tail, fact: (erl_tail.empty? ? nil : erl_tail)}
|
|
120
|
+
end
|
|
121
|
+
end
|