rmk-ruby 0.1.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/.gitignore +10 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/LICENSE +674 -0
- data/README.md +37 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/rmk +74 -0
- data/lib/rmk/build.rb +238 -0
- data/lib/rmk/rmk.rb +197 -0
- data/lib/rmk/rule.rb +30 -0
- data/lib/rmk/schedule.rb +15 -0
- data/lib/rmk/storage.rb +30 -0
- data/lib/rmk/vars.rb +70 -0
- data/lib/rmk/vdir.rb +502 -0
- data/lib/rmk/version.rb +3 -0
- data/lib/rmk/vfile.rb +84 -0
- data/lib/rmk.rb +4 -0
- data/rmk.gemspec +35 -0
- metadata +93 -0
data/lib/rmk/vars.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
class Rmk; end
|
2
|
+
|
3
|
+
class Rmk::Vars < Hash
|
4
|
+
# create vars
|
5
|
+
# @param rmk [Hash] rmk system reserved vars, query first so will not be override
|
6
|
+
# @param upstream [Rmk::Vars, nil] upstream vars for lookup var which current obj not include
|
7
|
+
def initialize(rmk, upstream) @rmk, @upstream = rmk, upstream end
|
8
|
+
attr_reader :rmk, :upstream
|
9
|
+
|
10
|
+
# create new downstream vars which will lookup first, when not found then lookup current obj
|
11
|
+
# @param clone_rmk [Boolean] dup a new rmk Hash or not, usually dup when need add reserved vars
|
12
|
+
def downstream_new(clone_rmk:false) Rmk::Vars.new clone_rmk ? @rmk.clone(freeze:false) : @rmk, self end
|
13
|
+
|
14
|
+
protected def fetch(name) super(name, nil) || @upstream&.fetch(name) end
|
15
|
+
|
16
|
+
def [](name) @rmk[name] || fetch(name) end
|
17
|
+
|
18
|
+
def []=(name, append = false, value)
|
19
|
+
value = interpolate_str value.to_s
|
20
|
+
super name, append ? self[name].to_s + value : value
|
21
|
+
end
|
22
|
+
|
23
|
+
protected def member?(name) super(name) || @upstream&.member?(name) end
|
24
|
+
|
25
|
+
def include?(name, inherit = true) inherit ? @rmk.include?(name) || member?(name) : super(name) end
|
26
|
+
|
27
|
+
alias_method :org_keys, :keys
|
28
|
+
|
29
|
+
protected def get_keys; @upstream ? org_keys + @upstream.get_keys : org_keys end
|
30
|
+
|
31
|
+
def keys(inherit = true) inherit ? @rmk.keys + get_keys : super() end
|
32
|
+
|
33
|
+
# only do #{\w+} interpolate
|
34
|
+
def preprocess_str(str) str.gsub(/\$((?:\$\$)*){(\w+)}/){"#{$1}#{self[$2]}"} end
|
35
|
+
|
36
|
+
# do all '$' prefix escape str interpolate
|
37
|
+
def unescape_str(str) str.gsub(/\$(?:([\$\s>&])|(\w+))/){$1 || self[$2]} end
|
38
|
+
|
39
|
+
# preprocess str, and then unescape the result
|
40
|
+
def interpolate_str(str) unescape_str preprocess_str str end
|
41
|
+
|
42
|
+
# preprocess str,and then split use white spaces, and then unescape each result, typically used for split file list
|
43
|
+
# @param str [String] str to split
|
44
|
+
# @return [Array<String>]
|
45
|
+
def split_str(str)
|
46
|
+
str = preprocess_str str
|
47
|
+
result = []
|
48
|
+
until str.empty?
|
49
|
+
head, _, str = str.partition /(?<!\$)(?:\$\$)*\K\s+/
|
50
|
+
result << unescape_str(head) unless head.empty?
|
51
|
+
end
|
52
|
+
result
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Rmk::MultiVarWriter
|
57
|
+
# create
|
58
|
+
# @param vars [Array<Rmk::Vars>] init Vars obj array
|
59
|
+
def initialize(*vars) @vars = vars end
|
60
|
+
|
61
|
+
# add vars obj to array
|
62
|
+
# @param vars [Rmk::Vars] vars obj
|
63
|
+
def <<(vars) @vars << vars end
|
64
|
+
|
65
|
+
# write var to all vars obj
|
66
|
+
# @param name [String]
|
67
|
+
# @param append [Boolean] is '+=' mode ?
|
68
|
+
# @param value [String, nil]
|
69
|
+
def []=(name, append = false, value) @vars.each{|var| var[name, append] = value} end
|
70
|
+
end
|
data/lib/rmk/vdir.rb
ADDED
@@ -0,0 +1,502 @@
|
|
1
|
+
require_relative 'rmk'
|
2
|
+
require_relative 'vfile'
|
3
|
+
require_relative 'rule'
|
4
|
+
require_relative 'build'
|
5
|
+
|
6
|
+
class Rmk::VOutDir < Hash
|
7
|
+
attr_reader :path, :vpath
|
8
|
+
|
9
|
+
def initialize(path:, vpath:nil)
|
10
|
+
super()
|
11
|
+
@path, @vpath = path, vpath
|
12
|
+
end
|
13
|
+
|
14
|
+
def derive_new(subname)
|
15
|
+
Rmk::VOutDir.new path:File.join(@path, subname, ''), vpath:@vpath && File.join(@vpath, subname, '')
|
16
|
+
end
|
17
|
+
|
18
|
+
def join_abs_path(path) File.join @path, path end
|
19
|
+
|
20
|
+
def join_virtual_path(path) @vpath && File.join(@vpath, path) end
|
21
|
+
|
22
|
+
def new_filepath(name)
|
23
|
+
{path:File.join(@path, name), vpath:@vpath && (@vpath.empty? ? name : File.join(@vpath, name))}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Rmk::VDir
|
28
|
+
attr_reader :rmk, :abs_src_path, :abs_out_path
|
29
|
+
attr_reader :srcfiles, :outfiles, :voutfiles, :builds
|
30
|
+
attr_reader :vars, :rules, :subdirs
|
31
|
+
attr_reader :defaultfile
|
32
|
+
|
33
|
+
# create virtual dir
|
34
|
+
# @param rmk [Rmk] Rmk obj
|
35
|
+
# @param parent [Rmk::VDir, nil] parent virtual dir, or nil for root
|
36
|
+
# @param path [String] path relative to parent, or empty for root
|
37
|
+
def initialize(rmk, parent, path = '')
|
38
|
+
@rmk = rmk
|
39
|
+
@parent = parent
|
40
|
+
@name = path
|
41
|
+
@defaultfile = @parent&.defaultfile
|
42
|
+
@rules = {}
|
43
|
+
@subdirs = {}
|
44
|
+
@srcfiles = {}
|
45
|
+
@builds = []
|
46
|
+
@collections = {}
|
47
|
+
@virtual_path = @parent&.join_virtual_path("#{@name}/")
|
48
|
+
Dir.mkdir @virtual_path if @virtual_path && !Dir.exist?(@virtual_path)
|
49
|
+
@abs_src_path = ::File.join @rmk.srcroot, @virtual_path.to_s, ''
|
50
|
+
@abs_out_path = ::File.join @rmk.outroot, @virtual_path.to_s, ''
|
51
|
+
@outfiles = Rmk::VOutDir.new path:@abs_out_path, vpath:@virtual_path || ''
|
52
|
+
@voutfiles = {''=>@outfiles}
|
53
|
+
@vars = Rmk::Vars.new @rmk.vars.clone(freeze:false), nil
|
54
|
+
@vars.rmk['vpath'] = @virtual_path&.[](0 .. -2) || '.'
|
55
|
+
@vars.rmk['srcpath'] = @abs_src_path[0 .. -2]
|
56
|
+
@vars.rmk['outpath'] = @abs_out_path[0 .. -2]
|
57
|
+
end
|
58
|
+
|
59
|
+
def vpath; @virtual_path end
|
60
|
+
|
61
|
+
def collections(name = nil) name ? @collections[name] ||= [] : @collections end
|
62
|
+
|
63
|
+
def include_subdir(path)
|
64
|
+
@subdirs[path] ||= Rmk::VDir.new @rmk, self, path
|
65
|
+
end
|
66
|
+
|
67
|
+
def join_abs_src_path(file) ::File.join @abs_src_path, file end
|
68
|
+
|
69
|
+
def join_abs_out_path(file) ::File.join @abs_out_path, file end
|
70
|
+
|
71
|
+
def join_virtual_path(file) @virtual_path ? ::File.join(@virtual_path, file) : file end
|
72
|
+
|
73
|
+
# join src file path relative to out root, or absolute src path when not relative src
|
74
|
+
def join_rto_src_path(file) @rmk.join_rto_src_path join_virtual_path(file) end
|
75
|
+
|
76
|
+
# split virtual path pattern to dir part and file match regex part
|
77
|
+
# @param pattern [String] virtual path, can include '*' to match any char at last no dir part
|
78
|
+
# @return [Array(String, <Regex, nil>, <String, nil>, <String, nil>)]
|
79
|
+
# when pattern include '*', return [dir part, file(or dir) match regex, post dir part, post file part]
|
80
|
+
# ;otherwise return [origin pattern, nil, nil, nil]
|
81
|
+
def split_vpath_pattern(pattern)
|
82
|
+
match = /^((?:[^\/*]+\/)*+)([^\/*]*+)(?:\*([^\/*]*+))?(?(3)(\/(?:[^\/*]+\/)*+[^\/*]++)?)$/.match pattern
|
83
|
+
raise "file syntax '#{pattern}' error" unless match
|
84
|
+
dir, prefix, postfix, postpath = *match[1..4]
|
85
|
+
regex = postfix && /#{Regexp.escape prefix}(.*)#{Regexp.escape postfix}$/
|
86
|
+
[regex ? dir : pattern, regex, postpath]
|
87
|
+
end
|
88
|
+
|
89
|
+
# find files which can be build's imput file
|
90
|
+
# @param pattern [String] virtual path to find src and out files which can include '*' to match any char at last no dir part
|
91
|
+
# ;or absolute path to find a src file which not in src tree
|
92
|
+
# @param ffile [Boolean] return FFile struct or not
|
93
|
+
# @return [Array<VFile, FFile>] return array of FFile when ffile, otherwise array of VFile
|
94
|
+
def find_inputfiles(pattern, ffile:false)
|
95
|
+
return @rmk.find_inputfiles pattern, ffile:ffile if pattern.match? /^[A-Z]:/
|
96
|
+
pattern = Rmk.normalize_path pattern
|
97
|
+
dir, regex, postpath = split_vpath_pattern pattern
|
98
|
+
return find_voutfiles_imp dir, regex, postpath, ffile:ffile if pattern.start_with? ':'
|
99
|
+
files = find_srcfiles_imp pattern, dir, regex, postpath, ffile:ffile
|
100
|
+
files.concat find_outfiles_imp dir, regex, postpath, ffile:ffile
|
101
|
+
files
|
102
|
+
end
|
103
|
+
|
104
|
+
# find srcfiles raw implement(assume all parms valid)
|
105
|
+
# @param pattern [String] virtual path, can include '*' to match any char at last no dir part
|
106
|
+
# @param ffile [Boolean] return FFile struct or not
|
107
|
+
# @return [Array<VFile, FFile>] return array of FFile when ffile, otherwise array of VFile
|
108
|
+
protected def find_srcfiles_imp(pattern, dir, regex, postpath, ffile:false)
|
109
|
+
return Dir[join_virtual_path(pattern), base: @rmk.srcroot].map! do |vp|
|
110
|
+
@rmk.add_src_file path:@rmk.join_abs_src_path(vp), vpath:vp
|
111
|
+
end unless ffile
|
112
|
+
return Dir[pattern, base:@abs_src_path].map! do |vn|
|
113
|
+
FFile.new @rmk.add_src_file(path:join_abs_src_path(vn), vpath:join_virtual_path(vn)), vn, nil
|
114
|
+
end unless regex
|
115
|
+
range = dir.size .. (postpath ? -1 - postpath.size : -1)
|
116
|
+
Dir[pattern, base:@abs_src_path].map! do |vn|
|
117
|
+
file = @rmk.add_src_file path:join_abs_src_path(vn), vpath:join_virtual_path(vn)
|
118
|
+
FFile.new file, vn, vn[range][regex, 1]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# find outfiles raw implement(assume all parms valid)
|
123
|
+
# @param path [String] virtual path, if regex, path must be dir(end with '/') or empty, otherwise contrary
|
124
|
+
# @param regex [Regexp, nil] if not nil, file match regexp, or dir match regexp when postpath not nil
|
125
|
+
# @param postpath [String, nil] path after dir match regexp
|
126
|
+
# @param ffile [Boolean] return FFile struct or not
|
127
|
+
# @return [Array<VFile, FFile>] return array of FFile when ffile, otherwise array of VFile
|
128
|
+
protected def find_outfiles_imp(path, regex, postpath, ffile:false)
|
129
|
+
files = []
|
130
|
+
unless regex
|
131
|
+
*spath, fn = *path.split('/')
|
132
|
+
dir = spath.inject(self){|obj, dn| obj&.subdirs&.[](dn)}
|
133
|
+
return files unless dir
|
134
|
+
dir.voutfiles.each_value{|ofs| files << (ffile ? FFile.new(ofs[fn], path) : ofs[fn]) if ofs.include? fn}
|
135
|
+
files.concat ffile ? dir.collections[fn].map{|f| FFile.new f} : dir.collections[fn] if dir.collections.include? fn
|
136
|
+
return files
|
137
|
+
end
|
138
|
+
dir = path.split('/').inject(self){|obj, dn| obj&.subdirs&.[](dn)}
|
139
|
+
return files unless dir
|
140
|
+
if postpath
|
141
|
+
*spath, fn = *postpath.delete_prefix('/').split('/')
|
142
|
+
dir.subdirs.each do |name, obj|
|
143
|
+
next unless name.match? regex
|
144
|
+
sdir = spath.inject(obj){|sobj, dn| sobj&.subdirs[dn]}
|
145
|
+
next unless sdir
|
146
|
+
vname, stem = path + name + postpath, name[regex, 1] if ffile
|
147
|
+
sdir.voutfiles.each_value{|ofs| files << (ffile ? FFile.new(ofs[fn], vname, stem) : ofs[fn]) if ofs.include? fn}
|
148
|
+
files.concat ffile ? sdir.collections[fn].map{|f| FFile.new f} : sdir.collections[fn] if sdir.collections.include? fn
|
149
|
+
end
|
150
|
+
else
|
151
|
+
dir.voutfiles.each_value do |ofs|
|
152
|
+
ofs.each {|k, v| files << (ffile ? FFile.new(v, path + k, k[regex, 1]) : v) if k.match? regex}
|
153
|
+
end
|
154
|
+
dir.collections.each do |k, v|
|
155
|
+
next unless k.match? regex
|
156
|
+
files.concat ffile ? v.map {|f| FFile.new f} : v
|
157
|
+
end
|
158
|
+
end
|
159
|
+
files
|
160
|
+
end
|
161
|
+
|
162
|
+
# find voutfiles
|
163
|
+
protected def find_voutfiles_imp(path, regex, postpath, ffile:false)
|
164
|
+
files = []
|
165
|
+
path.delete_prefix! ':'
|
166
|
+
unless regex
|
167
|
+
match = path.match /^(.*)\/([^\/]+)$/
|
168
|
+
vout, name = match[1], match[2]
|
169
|
+
raise "vout dir '#{vout}' not found" unless @voutfiles.include? vout
|
170
|
+
vout = @voutfiles[vout]
|
171
|
+
return files unless vout.include? name
|
172
|
+
files << (ffile ? FFile.new(vout[name], name) : vout[name])
|
173
|
+
return files
|
174
|
+
end
|
175
|
+
if postpath
|
176
|
+
match = postpath.match /^(.*)\/([^\/]+)$/
|
177
|
+
postpath, name = match[1], match[2]
|
178
|
+
range = path.size .. -1-postpath.size
|
179
|
+
@voutfiles.each do |vn, fs|
|
180
|
+
next unless vn.start_with?(path) && vn.end_with?(postpath) && vn[range].match?(regex) && fs.include?(name)
|
181
|
+
files << (ffile ? FFile.new(fs[name], name, vn[range][regex, 1]) : fs[name])
|
182
|
+
end
|
183
|
+
else
|
184
|
+
vout = path.delete_suffix '/'
|
185
|
+
raise "vout dir '#{vout}' not found" unless @voutfiles.include? vout
|
186
|
+
vout = @voutfiles[vout]
|
187
|
+
vout.each {|name, file| files << (ffile ? FFile.new(file, name, name[regex, 1]) : file) if name.match? regex}
|
188
|
+
end
|
189
|
+
files
|
190
|
+
end
|
191
|
+
|
192
|
+
# find files which must be build's output
|
193
|
+
# @param pattern [String] virtual path to find out files which can include '*' to match any char at last no dir part
|
194
|
+
# @return [Array<Hash>] return Array of file, and Regex when has '*' pattern
|
195
|
+
def find_outfiles(pattern)
|
196
|
+
return @rmk.find_outfiles pattern if pattern.match? /^[A-Z]:/i
|
197
|
+
return find_voutfiles_imp *split_vpath_pattern(pattern) if pattern.start_with? ':'
|
198
|
+
find_outfiles_imp *split_vpath_pattern(pattern)
|
199
|
+
end
|
200
|
+
|
201
|
+
# add a output file
|
202
|
+
# @param name file name, relative to this dir when not absolute path
|
203
|
+
# @return [VFile] virtual file object
|
204
|
+
def add_out_file(name)
|
205
|
+
if /^[A-Z]:/.match? name
|
206
|
+
@rmk.add_out_file path:name, vpath: name.start_with?(@rmk.outroot) && name[@rmk.outroot.size .. - 1]
|
207
|
+
elsif /^:(.*)\/([^\/]+)$/.match name
|
208
|
+
vout, name = $1, $2
|
209
|
+
raise "vout dir '#{vout}' not found" unless @voutfiles.include? vout
|
210
|
+
@voutfiles[vout][name] = @rmk.add_out_file **@voutfiles[vout].new_filepath(name)
|
211
|
+
else
|
212
|
+
@voutfiles[''][name] = @rmk.add_out_file path:join_abs_out_path(name), vpath:join_virtual_path(name)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
private def begin_define_nonvar(indent)
|
217
|
+
last = @state[-1]
|
218
|
+
case last[:type]
|
219
|
+
when :AcceptVar # rule or build context which can add var
|
220
|
+
@state.pop
|
221
|
+
last = @state[-1]
|
222
|
+
raise 'invalid indent' unless indent == last[:indent]
|
223
|
+
when :SubVar
|
224
|
+
@state.pop 2
|
225
|
+
last = @state[-1]
|
226
|
+
raise 'invalid indent' unless indent == last[:indent]
|
227
|
+
when :Condition # just after condition context
|
228
|
+
raise 'invalid indent' unless indent > last[:indent]
|
229
|
+
@state << {indent:indent, type:nil, condition:last[:condition], vars:last[:vars]}
|
230
|
+
last = @state[-1]
|
231
|
+
else # general context
|
232
|
+
raise 'invalid indent' unless indent == last[:indent]
|
233
|
+
end
|
234
|
+
last
|
235
|
+
end
|
236
|
+
|
237
|
+
private def end_last_define
|
238
|
+
last = @state[-1]
|
239
|
+
if last[:type] == :SubVar
|
240
|
+
@state.pop 2
|
241
|
+
elsif last[:type] == :AcceptVar
|
242
|
+
@state.pop
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def define_var(indent, name, append, value)
|
247
|
+
last = @state[-1]
|
248
|
+
case last[:type]
|
249
|
+
when :AcceptVar # rule or build context which can add var
|
250
|
+
raise 'invalid indent' if indent < last[:indent]
|
251
|
+
if indent > last[:indent]
|
252
|
+
@state << {indent:indent, type: :SubVar, condition:last[:condition], vars:last[:vars]}
|
253
|
+
last = @state[-1]
|
254
|
+
else
|
255
|
+
@state.pop
|
256
|
+
last = @state[-1]
|
257
|
+
raise 'invalid indent' unless indent == last[:indent]
|
258
|
+
end
|
259
|
+
when :Condition # just after condition context
|
260
|
+
raise 'invalid indent' unless indent > last[:indent]
|
261
|
+
@state << {indent:indent, type:nil, condition:last[:condition], vars:last[:vars]}
|
262
|
+
last = @state[-1]
|
263
|
+
else # general context
|
264
|
+
raise 'invalid indent' unless indent == last[:indent]
|
265
|
+
end
|
266
|
+
last[:vars][name, append] = value
|
267
|
+
end
|
268
|
+
|
269
|
+
def parse
|
270
|
+
raise "dir '#{@abs_src_path}' has been parsed" if @state
|
271
|
+
@state = []
|
272
|
+
file = join_abs_src_path 'default.rmk'
|
273
|
+
@defaultfile = file if ::File.exist? file
|
274
|
+
file = join_abs_out_path 'config.rmk'
|
275
|
+
parse_file file if ::File.exist? file
|
276
|
+
file = join_abs_src_path 'dir.rmk'
|
277
|
+
if ::File.exist? file
|
278
|
+
parse_file file
|
279
|
+
elsif @defaultfile
|
280
|
+
parse_file @defaultfile
|
281
|
+
else
|
282
|
+
raise "dir parse error: '#{file}' doesn't exist and can't finded any 'default.rmk'"
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def parse_file(file)
|
287
|
+
last_state, @state = @state, [{indent:0, type:nil, condition:nil, vars:@vars}]
|
288
|
+
lines = IO.readlines file
|
289
|
+
markid = lid = 0
|
290
|
+
while lid < lines.size
|
291
|
+
line, markid = '', lid
|
292
|
+
while lid < lines.size
|
293
|
+
break if lines[lid].sub!(/(?<!\$)(?:\$\$)*\K#.*$/, '')
|
294
|
+
break unless lines[lid].sub!(/(?<!\$)(?:\$\$)*\K\$\n/m, '')
|
295
|
+
line += lines[lid]
|
296
|
+
lid += 1
|
297
|
+
lines[lid]&.lstrip!
|
298
|
+
end
|
299
|
+
parse_line lid < lines.size ? line + lines[lid] : line, markid
|
300
|
+
lid += 1
|
301
|
+
end
|
302
|
+
@state = last_state
|
303
|
+
rescue
|
304
|
+
$!.set_backtrace $!.backtrace.push "#{file}:#{markid + 1}:vpath'#{@virtual_path}'"
|
305
|
+
raise
|
306
|
+
end
|
307
|
+
|
308
|
+
def parse_line(line, lid)
|
309
|
+
match = /^(?<indent> *|\t*)(?:(?<firstword>\w+)\s*(?<content>.+)?)?$/.match line
|
310
|
+
raise 'syntax error' unless match
|
311
|
+
indent, firstword, line = match[:indent].size, match[:firstword], match[:content]
|
312
|
+
return end_last_define unless firstword
|
313
|
+
state = @state[-1]
|
314
|
+
if !state[:condition].nil? && !state[:condition] # false state fast process
|
315
|
+
@state.pop if indent == state[:indent] && firstword == 'endif'
|
316
|
+
return
|
317
|
+
end
|
318
|
+
case firstword
|
319
|
+
when /^if(n)?(?:(eq)|def)$/
|
320
|
+
logicnot, logicmod = Regexp.last_match(1), Regexp.last_match(2)
|
321
|
+
state = begin_define_nonvar indent
|
322
|
+
parms = state[:vars].split_str line
|
323
|
+
if logicmod
|
324
|
+
raise 'must have two str' unless parms.size == 2
|
325
|
+
value = logicnot ? parms[0] != parms[1] : parms[0] == parms[1]
|
326
|
+
else
|
327
|
+
raise 'must have var name' if parms.empty?
|
328
|
+
value = logicnot ? !parms.any?{|vn| @vars.include? vn} : parms.all?{|vn| @vars.include? vn}
|
329
|
+
end
|
330
|
+
@state << {indent:indent, type: :Condition, condition:value, vars:state[:vars]}
|
331
|
+
when 'else', 'endif'
|
332
|
+
raise 'syntax error' if line
|
333
|
+
endmod = firstword != 'else'
|
334
|
+
while state
|
335
|
+
raise 'not if condition' if state[:condition].nil?
|
336
|
+
if state[:type]&.== :Condition
|
337
|
+
raise 'invalid indent' unless indent == state[:indent]
|
338
|
+
return endmod ? @state.pop : state[:condition] = false
|
339
|
+
end
|
340
|
+
@state.pop
|
341
|
+
state = @state[-1]
|
342
|
+
end
|
343
|
+
raise 'not found match if'
|
344
|
+
when 'rule'
|
345
|
+
match = /^(?<name>\w+)\s*(?:=\s*(?<command>.*))?$/.match line
|
346
|
+
raise 'rule name or command invalid' unless match
|
347
|
+
state = begin_define_nonvar indent
|
348
|
+
raise "rule '#{match[:name]}' has been defined" if @rules.include? match[:name]
|
349
|
+
rule = @rules[match[:name]] = Rmk::Rule.new match[:command]
|
350
|
+
@state << {indent:indent, type: :AcceptVar, condition:state[:condition], vars:rule.vars}
|
351
|
+
when /^exec$/
|
352
|
+
state = begin_define_nonvar indent
|
353
|
+
match = /^(?<rule>\w+)(?<each>\s+each)?:\s*(?<parms>.*)$/.match line
|
354
|
+
raise 'syntax error' unless match
|
355
|
+
raise "rule '#{match[:rule]}' undefined" unless @rules.include? match[:rule]
|
356
|
+
eachmode = match[:each]
|
357
|
+
parms = match[:parms].split /(?<!\$)(?:\$\$)*\K>>/, -1
|
358
|
+
raise "syntax error, use '>>' to separat input and output and collect" unless (1 .. 3) === parms.size
|
359
|
+
ioregex = /(?<!\$)(?:\$\$)*\K&/
|
360
|
+
iparms = parms[0].split ioregex
|
361
|
+
raise 'input field count error' unless (1..3) === iparms.size
|
362
|
+
if parms[1] && !parms[1].empty?
|
363
|
+
oparms = parms[1].lstrip.split ioregex
|
364
|
+
raise 'output field count error' unless (1..2) === oparms.size
|
365
|
+
else
|
366
|
+
raise 'syntax error: must give output field after >>' if parms[1] && !parms[2]
|
367
|
+
oparms = []
|
368
|
+
end
|
369
|
+
if parms[2]
|
370
|
+
parms[2] = state[:vars].split_str(parms[2].lstrip).map!{|name| collections name}
|
371
|
+
raise 'must give collection name' if parms[2].empty?
|
372
|
+
else
|
373
|
+
parms[2] = @rules[match[:rule]]['collection']&.lstrip
|
374
|
+
parms[2] = state[:vars].split_str(parms[2]).map!{|name| collections name} if parms[2]
|
375
|
+
end
|
376
|
+
iparms[0] = state[:vars].split_str iparms[0]
|
377
|
+
raise 'must have input file' if iparms[0].size == 0
|
378
|
+
if eachmode
|
379
|
+
vars = Rmk::MultiVarWriter.new
|
380
|
+
iparms[0].each do |fn|
|
381
|
+
files = find_inputfiles fn, ffile:true
|
382
|
+
files.each do |file|
|
383
|
+
build = Rmk::Build.new(self, @rules[match[:rule]], state[:vars], [file], iparms[1],
|
384
|
+
iparms[2], oparms[0], oparms[1], parms[2])
|
385
|
+
@builds << build
|
386
|
+
vars << build.vars
|
387
|
+
end
|
388
|
+
end
|
389
|
+
else
|
390
|
+
files = []
|
391
|
+
iparms[0].each {|fn| files.concat find_inputfiles fn}
|
392
|
+
build = Rmk::Build.new(self, @rules[match[:rule]], state[:vars], files, iparms[1], iparms[2],
|
393
|
+
oparms[0], oparms[1], parms[2])
|
394
|
+
@builds << build
|
395
|
+
vars = build.vars
|
396
|
+
end
|
397
|
+
@state << {indent:indent, type: :AcceptVar, condition:state[:condition], vars:vars}
|
398
|
+
when 'collect'
|
399
|
+
state = begin_define_nonvar indent
|
400
|
+
parms = line.split /(?<!\$)(?:\$\$)*\K>>/
|
401
|
+
raise "must have only one '>>' separator for separat input and output" unless parms.size == 2
|
402
|
+
collection = state[:vars].split_str(parms[1].lstrip).map!{|name| collections name}
|
403
|
+
raise 'must give collection name' if collection.empty?
|
404
|
+
state[:vars].split_str(parms[0]).each do |fn|
|
405
|
+
files = find_inputfiles fn
|
406
|
+
collection.each{|col| col.concat files}
|
407
|
+
end
|
408
|
+
when 'default'
|
409
|
+
state = begin_define_nonvar indent
|
410
|
+
parms = state[:vars].split_str line
|
411
|
+
raise "must have file name" if parms.empty?
|
412
|
+
parms.each do |parm|
|
413
|
+
files = find_outfiles parm
|
414
|
+
raise "pattern '#{parm}' not match any out file" if files.empty?
|
415
|
+
@rmk.add_default *files
|
416
|
+
end
|
417
|
+
when /^include(_dir)?(_force)?$/
|
418
|
+
dirmode, force = Regexp.last_match(1), Regexp.last_match(2)
|
419
|
+
state = begin_define_nonvar indent
|
420
|
+
parms = state[:vars].split_str line
|
421
|
+
raise dirmode ? "must have file name" : "must have dir name or matcher" if parms.empty?
|
422
|
+
if dirmode
|
423
|
+
threads = []
|
424
|
+
parms.each do |parm|
|
425
|
+
dirs = Dir[File.join @abs_src_path, parm, '']
|
426
|
+
if dirs.empty?
|
427
|
+
raise "subdir '#{parm}' doesn't exist" if force
|
428
|
+
else
|
429
|
+
dirs.each do |dir|
|
430
|
+
dir = include_subdir dir[@abs_src_path.size .. -2]
|
431
|
+
threads << Rmk::Schedule.new_thread!( &dir.method(:parse) )
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end
|
435
|
+
threads.each{|thr| thr.join}
|
436
|
+
else
|
437
|
+
parms.each do |parm|
|
438
|
+
if parm.match? /^[a-zA-Z]:/
|
439
|
+
if File.exist? parm
|
440
|
+
parse_file parm
|
441
|
+
else
|
442
|
+
raise "file '#{parm}' not exist" if force
|
443
|
+
end
|
444
|
+
else
|
445
|
+
files = find_outfiles parm
|
446
|
+
if files.empty?
|
447
|
+
file = join_abs_out_path parm
|
448
|
+
next parse_file file if File.exist? file
|
449
|
+
file = join_abs_src_path parm
|
450
|
+
next parse_file file if File.exist? file
|
451
|
+
raise "file '#{parm}' not exist" if force
|
452
|
+
else
|
453
|
+
files.each{|file| file.output_ref_build.parser_force_run!}
|
454
|
+
files.each{|file| parse_file file.vpath}
|
455
|
+
end
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
459
|
+
when 'vout'
|
460
|
+
state = begin_define_nonvar indent
|
461
|
+
match = line.match /^(\S+)\s*(?:=\s*(\S*)\s*)?$/
|
462
|
+
raise 'syntax error' unless match
|
463
|
+
if match[2]
|
464
|
+
path = state[:vars].interpolate_str match[2]
|
465
|
+
path << '/' unless path.end_with? '/'
|
466
|
+
if path.match? /^[a-z]:\//i
|
467
|
+
@voutfiles[match[1]] = Rmk::VOutDir.new path:path,
|
468
|
+
vpath:path.start_with?(@rmk.outroot) && path[@rmk.outroot.size..-1]
|
469
|
+
else
|
470
|
+
path = join_virtual_path path
|
471
|
+
@voutfiles[match[1]] = Rmk::VOutDir.new path:join_abs_out_path(path), vpath:path
|
472
|
+
end
|
473
|
+
Dir.mkdir path unless Dir.exist? path
|
474
|
+
@vars.rmk[match[1] + '_path'] = @voutfiles[match[1]].path[0 .. -2]
|
475
|
+
elsif @parent&.voutfiles&.include? match[1]
|
476
|
+
path = (@voutfiles[match[1]] = @parent.voutfiles[match[1]].derive_new @name).path
|
477
|
+
Dir.mkdir path unless Dir.exist? path
|
478
|
+
@vars.rmk[match[1] + '_path'] = @voutfiles[match[1]].path[0 .. -2]
|
479
|
+
else
|
480
|
+
raise 'no inheritable vout dir'
|
481
|
+
end
|
482
|
+
when 'inherit'
|
483
|
+
begin_define_nonvar indent
|
484
|
+
raise 'syntax error' if line
|
485
|
+
if @parent
|
486
|
+
@vars.merge! @parent.vars
|
487
|
+
@rules.merge! @parent.rules
|
488
|
+
end
|
489
|
+
when 'report'
|
490
|
+
state = begin_define_nonvar indent
|
491
|
+
parms = line.match /^((error)|warn|(info))\s+(.*)$/
|
492
|
+
raise 'syntax error' unless parms
|
493
|
+
line = "Rmk: #{parms[1]}: " + state[:vars].interpolate_str(parms[4])
|
494
|
+
parms[3] ? puts(line) : $stderr.puts(line)
|
495
|
+
exit 2 if parms[2]
|
496
|
+
else
|
497
|
+
match = /^(?:(?<append>\+=)|=)(?(<append>)|\s*)(?<value>.*)$/.match line
|
498
|
+
raise 'syntax error' unless match
|
499
|
+
define_var indent, firstword, match[:append], match[:value]
|
500
|
+
end
|
501
|
+
end
|
502
|
+
end
|
data/lib/rmk/version.rb
ADDED
data/lib/rmk/vfile.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require_relative 'rmk'
|
2
|
+
|
3
|
+
# virtual file which represent a real OS file
|
4
|
+
class Rmk::VFile
|
5
|
+
attr_reader :path, :vpath
|
6
|
+
attr_accessor :is_src
|
7
|
+
|
8
|
+
def self.generate_modified_id(path) File.mtime(path).to_i end
|
9
|
+
|
10
|
+
def src?; @is_src end
|
11
|
+
|
12
|
+
# builds which include this file as input file
|
13
|
+
def input_ref_builds; @input_ref_builds end
|
14
|
+
|
15
|
+
# builds which include this file as order-only file
|
16
|
+
def order_ref_builds; @order_ref_builds end
|
17
|
+
|
18
|
+
# build which include this file as output file
|
19
|
+
# @return [Rmk::Build]
|
20
|
+
attr_accessor :output_ref_build
|
21
|
+
|
22
|
+
# create VFile
|
23
|
+
# @param rmk [Rmk]
|
24
|
+
# @param path [String] file's absolute path, must be normalized
|
25
|
+
# @param vpath [String] file's virtual path
|
26
|
+
def initialize(rmk:, path:, vpath:nil, is_src:false)
|
27
|
+
@rmk, @path, @vpath, @is_src = rmk, path, vpath || nil, is_src
|
28
|
+
@input_ref_builds, @order_ref_builds = [], []
|
29
|
+
@output_ref_build = nil unless is_src
|
30
|
+
end
|
31
|
+
|
32
|
+
# get or set out file state
|
33
|
+
# return [:eixst, :create, true, nil]
|
34
|
+
attr_accessor :state
|
35
|
+
|
36
|
+
# generate file's modified id from current disk content
|
37
|
+
def generate_modified_id; Rmk::VFile.generate_modified_id @path end
|
38
|
+
|
39
|
+
# load last time modified id from system database
|
40
|
+
# @return [Object] last stored modified id or nil for no last stored id
|
41
|
+
def load_modified_id; @rmk.mid_storage[@path] end
|
42
|
+
|
43
|
+
# store modified id to system database for next time check
|
44
|
+
# @param mid [Object] modified id
|
45
|
+
# @return [Object] stored modified id
|
46
|
+
def store_modified_id(mid) @rmk.mid_storage[@path] = mid end
|
47
|
+
|
48
|
+
# change to out file
|
49
|
+
# @param outfile [Rmk::VFile] target file
|
50
|
+
# @return [self]
|
51
|
+
def change_to_out!(outfile)
|
52
|
+
raise "outfile '#{@path}' can't change to outfile" if src?
|
53
|
+
raise "outfile '#{@path}' can't change to srcfile" if outfile.src?
|
54
|
+
unless @path == outfile.path && @vpath == outfile.vpath
|
55
|
+
raise "srcfile '#{@path}' can't change to outfile '#{outfile.path}'"
|
56
|
+
end
|
57
|
+
@is_src = false
|
58
|
+
@input_ref_builds.concat outfile.input_ref_builds
|
59
|
+
@order_ref_builds.concat outfile.order_ref_builds
|
60
|
+
@output_ref_build = outfile.output_ref_build
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def updated!(modified)
|
65
|
+
input_ref_builds.each{|build| build.input_updated! modified}
|
66
|
+
order_ref_builds.each{|build| build.order_updated! modified}
|
67
|
+
end
|
68
|
+
|
69
|
+
# check build's to run as srcfile, means file must be exist and can't check more than one time
|
70
|
+
def check_for_build
|
71
|
+
lmid, cmid = load_modified_id, generate_modified_id
|
72
|
+
return updated! false if lmid == cmid
|
73
|
+
store_modified_id cmid
|
74
|
+
updated! true
|
75
|
+
end
|
76
|
+
|
77
|
+
# check outdated or not as srcfile at parse phaze(not build phaze)
|
78
|
+
def check_for_parse
|
79
|
+
load_modified_id != generate_modified_id
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Finded file struct
|
84
|
+
FFile = Struct.new :vfile, :vname, :stem
|
data/lib/rmk.rb
ADDED