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.
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
@@ -0,0 +1,3 @@
1
+ class Rmk
2
+ VERSION = "0.1.0"
3
+ end
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
@@ -0,0 +1,4 @@
1
+ require_relative 'rmk/rmk'
2
+ require_relative 'rmk/vfile'
3
+ require_relative 'rmk/build'
4
+ require_relative 'rmk/vdir'