quik 0.1.1 → 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.
@@ -1,33 +1,33 @@
1
- # encoding: utf-8
2
-
3
- module Quik
4
-
5
- class Opts
6
-
7
- def merge_gli_options!( options = {} )
8
- @test = true if options[:test] == true
9
- @verbose = true if options[:verbose] == true
10
- end
11
-
12
-
13
- def verbose=(boolean) # add: alias for debug ??
14
- @verbose = boolean
15
- end
16
-
17
- def verbose?
18
- return false if @verbose.nil? # default verbose/debug flag is false
19
- @verbose == true
20
- end
21
-
22
- def test=(boolean)
23
- @test = boolean
24
- end
25
-
26
- def test?
27
- return false if @test.nil? # default test/dry-run flag is false
28
- @test == true
29
- end
30
-
31
- end # class Opts
32
-
33
- end # module Quik
1
+ # encoding: utf-8
2
+
3
+ module Quik
4
+
5
+ class Opts
6
+
7
+ def merge_gli_options!( options = {} )
8
+ @test = true if options[:test] == true
9
+ @verbose = true if options[:verbose] == true
10
+ end
11
+
12
+
13
+ def verbose=(boolean) # add: alias for debug ??
14
+ @verbose = boolean
15
+ end
16
+
17
+ def verbose?
18
+ return false if @verbose.nil? # default verbose/debug flag is false
19
+ @verbose == true
20
+ end
21
+
22
+ def test=(boolean)
23
+ @test = boolean
24
+ end
25
+
26
+ def test?
27
+ return false if @test.nil? # default test/dry-run flag is false
28
+ @test == true
29
+ end
30
+
31
+ end # class Opts
32
+
33
+ end # module Quik
@@ -0,0 +1,148 @@
1
+ # encoding: utf-8
2
+
3
+ ### todo: move to textutils for (re)use - why? why not??
4
+ ### todo add some references to alternative color gems
5
+ ##
6
+ ## e.g. https://github.com/fazibear/colorize
7
+ ## http://github.com/ssoroka/ansi
8
+ ## http://flori.github.com/term-ansicolor/
9
+ ## http://github.com/sickill/rainbow
10
+ ## https://github.com/janlelis/paint
11
+
12
+
13
+ ## move to core_ext.rb - why? why not??
14
+
15
+ ###
16
+ ## fix: use css style
17
+ ## e.g. color( :red )
18
+ ## color( :blue )
19
+ ## background_color( :red )
20
+ ## font_style( :bold ) -- why? why not??
21
+
22
+ class String
23
+
24
+ def self.use_colors?
25
+ @@use_color ||= true
26
+ ## todo/fix: check for windows on load (set to false;otherwise true)
27
+ @@use_color
28
+ end
29
+
30
+ def self.use_colors=(value)
31
+ @@use_color = value
32
+ end
33
+
34
+
35
+ def red() colorize(31); end
36
+ def green() colorize(32); end
37
+ def yellow() colorize(33); end ## note: more brown-ish (if not used w/ bold/bright mode) ??
38
+ def blue() colorize(34); end
39
+ def magenta() colorize(35); end ## pink ??
40
+ def cyan() colorize(36); end ## light blue ??
41
+
42
+ def bold() colorize(1); end ## just use 0 clear for now; instead of BOLD_OFF (22) code; use bright as an alias??
43
+
44
+ private
45
+ def colorize(code)
46
+ if self.class.use_colors?
47
+ "\e[#{code}m#{self}\e[0m"
48
+ else
49
+ self
50
+ end
51
+ end
52
+
53
+
54
+ ## todo - add modes e.g. bold/underscore/etc.
55
+ =begin
56
+ class String
57
+ def black; "\e[30m#{self}\e[0m" end
58
+ def red; "\e[31m#{self}\e[0m" end
59
+ def green; "\e[32m#{self}\e[0m" end
60
+ def brown; "\e[33m#{self}\e[0m" end
61
+ def blue; "\e[34m#{self}\e[0m" end
62
+ def magenta; "\e[35m#{self}\e[0m" end
63
+ def cyan; "\e[36m#{self}\e[0m" end
64
+ def gray; "\e[37m#{self}\e[0m" end
65
+
66
+ def on_black; "\e[40m#{self}\e[0m" end
67
+ def on_red; "\e[41m#{self}\e[0m" end
68
+ def on_green; "\e[42m#{self}\e[0m" end
69
+ def on_brown; "\e[43m#{self}\e[0m" end
70
+ def on_blue; "\e[44m#{self}\e[0m" end
71
+ def on_magenta; "\e[45m#{self}\e[0m" end
72
+ def on_cyan; "\e[46m#{self}\e[0m" end
73
+ def on_gray; "\e[47m#{self}\e[0m" end
74
+
75
+ def bold; "\e[1m#{self}\e[21m" end
76
+ def italic; "\e[3m#{self}\e[23m" end
77
+ def underline; "\e[4m#{self}\e[24m" end
78
+ def blink; "\e[5m#{self}\e[25m" end
79
+ def reverse_color; "\e[7m#{self}\e[27m" end
80
+
81
+ use Ansi constants - why? why not? e.g.:
82
+ //foreground color
83
+ public static final String BLACK_TEXT() { return "\033[30m";}
84
+ public static final String RED_TEXT() { return "\033[31m";}
85
+ public static final String GREEN_TEXT() { return "\033[32m";}
86
+ public static final String BROWN_TEXT() { return "\033[33m";}
87
+ public static final String BLUE_TEXT() { return "\033[34m";}
88
+ public static final String MAGENTA_TEXT() { return "\033[35m";}
89
+ public static final String CYAN_TEXT() { return "\033[36m";}
90
+ public static final String GRAY_TEXT() { return "\033[37m";}
91
+
92
+ //background color
93
+ public static final String BLACK_BACK() { return "\033[40m";}
94
+ public static final String RED_BACK() { return "\033[41m";}
95
+ public static final String GREEN_BACK() { return "\033[42m";}
96
+ public static final String BROWN_BACK() { return "\033[43m";}
97
+ public static final String BLUE_BACK() { return "\033[44m";}
98
+ public static final String MAGENTA_BACK() { return "\033[45m";}
99
+ public static final String CYAN_BACK() { return "\033[46m";}
100
+ public static final String WHITE_BACK() { return "\033[47m";}
101
+
102
+ //ANSI control chars
103
+ public static final String RESET_COLORS() { return "\033[0m";}
104
+ public static final String BOLD_ON() { return "\033[1m";}
105
+ public static final String BLINK_ON() { return "\033[5m";}
106
+ public static final String REVERSE_ON() { return "\033[7m";}
107
+ public static final String BOLD_OFF() { return "\033[22m";}
108
+ public static final String BLINK_OFF() { return "\033[25m";}
109
+ public static final String REVERSE_OFF() { return "\033[27m";}
110
+ end
111
+
112
+ Code Effect
113
+ 0 Turn off all attributes
114
+ 1 Set bright mode
115
+ 4 Set underline mode
116
+ 5 Set blink mode
117
+ 7 Exchange foreground and background colors
118
+ 8 Hide text (foreground color would be the same as background)
119
+ 30 Black text
120
+ 31 Red text
121
+ 32 Green text
122
+ 33 Yellow text
123
+ 34 Blue text
124
+ 35 Magenta text
125
+ 36 Cyan text
126
+ 37 White text
127
+ 39 Default text color
128
+ 40 Black background
129
+ 41 Red background
130
+ 42 Green background
131
+ 43 Yellow background
132
+ 44 Blue background
133
+ 45 Magenta background
134
+ 46 Cyan background
135
+ 47 White background
136
+ 49 Default background color
137
+
138
+ note:
139
+ puts "\e[31m" # set format (red foreground)
140
+ puts "\e[0" # clear format
141
+ puts "green-#{"red".red}-green".green # will be green-red-normal, because of \e[0
142
+ e.g. for now colors can NOT get nested
143
+ plus if you bold/italic/etc. use it before the color e.g.
144
+ bold.red etc.
145
+ =end
146
+
147
+
148
+ end # class String
@@ -1,60 +1,60 @@
1
- #encoding: utf-8
2
-
3
- module Quik
4
-
5
- ##
6
- # used for config block
7
- # lets you access props (even nested) that don't yet exist
8
- # and all props get stored in a hash
9
- #
10
- # e.g
11
- # c = OpenConfig.new
12
- # c.title = 'title'
13
- # c.author.name = 'name'
14
-
15
- # c.quik.last_updated = Time.now
16
- # c.quik.title = 'title'
17
- # c.quik.name = 'name'
18
- # c.quik.theme = 'theme'
19
-
20
- class OpenConfig
21
-
22
- def initialize
23
- @h = {}
24
- end
25
-
26
- def to_h
27
- h = {}
28
- @h.each do |k,v|
29
- if v.is_a? OpenConfig
30
- h[ k ] = v.to_h
31
- else
32
- h[ k ] = v ## just pass along as is
33
- end
34
- end
35
- h
36
- end
37
-
38
- def method_missing( m, *args, &block)
39
- if m.to_s =~ /^(.*)=$/ ## setter
40
- puts "config lookup (setter) >#{m}< #{m.class.name}, #{args.inspect}"
41
- key = m[0..-2].to_s ## cut off trailing =
42
- @h[ key ] = args[0].to_s # note: assume first arg is value for setter
43
- # note: for now all values are strings (always use to_s)
44
- else ## assume getter
45
- ## fix: add check for args?? must be 0 for getters??
46
- ## use else super to delegate non-getters??
47
- puts "config lookup (getter) >#{m}< #{m.class.name}"
48
- key = m.to_s
49
- value = @h[ key ]
50
- if value.nil?
51
- puts " config add (nested) hash"
52
- value = @h[ key ] = OpenConfig.new
53
- end
54
- value
55
- end
56
- end # method_missing
57
-
58
- end # class OpenConfig
59
-
60
- end # module Quik
1
+ #encoding: utf-8
2
+
3
+ module Quik
4
+
5
+ ##
6
+ # used for config block
7
+ # lets you access props (even nested) that don't yet exist
8
+ # and all props get stored in a hash
9
+ #
10
+ # e.g
11
+ # c = OpenConfig.new
12
+ # c.title = 'title'
13
+ # c.author.name = 'name'
14
+
15
+ # c.quik.last_updated = Time.now
16
+ # c.quik.title = 'title'
17
+ # c.quik.name = 'name'
18
+ # c.quik.theme = 'theme'
19
+
20
+ class OpenConfig
21
+
22
+ def initialize
23
+ @h = {}
24
+ end
25
+
26
+ def to_h
27
+ h = {}
28
+ @h.each do |k,v|
29
+ if v.is_a? OpenConfig
30
+ h[ k ] = v.to_h
31
+ else
32
+ h[ k ] = v ## just pass along as is
33
+ end
34
+ end
35
+ h
36
+ end
37
+
38
+ def method_missing( m, *args, &block)
39
+ if m.to_s =~ /^(.*)=$/ ## setter
40
+ puts "config lookup (setter) >#{m}< #{m.class.name}, #{args.inspect}"
41
+ key = m[0..-2].to_s ## cut off trailing =
42
+ @h[ key ] = args[0].to_s # note: assume first arg is value for setter
43
+ # note: for now all values are strings (always use to_s)
44
+ else ## assume getter
45
+ ## fix: add check for args?? must be 0 for getters??
46
+ ## use else super to delegate non-getters??
47
+ puts "config lookup (getter) >#{m}< #{m.class.name}"
48
+ key = m.to_s
49
+ value = @h[ key ]
50
+ if value.nil?
51
+ puts " config add (nested) hash"
52
+ value = @h[ key ] = OpenConfig.new
53
+ end
54
+ value
55
+ end
56
+ end # method_missing
57
+
58
+ end # class OpenConfig
59
+
60
+ end # module Quik
@@ -1,202 +1,207 @@
1
- # encoding: utf-8
2
-
3
- module Quik
4
-
5
- class Merger
6
-
7
-
8
-
9
- def find_files( root_dir )
10
- tree = []
11
-
12
- files = Dir.entries( root_dir )
13
- files = files.sort
14
- puts "#{root_dir}:"
15
- pp files
16
-
17
- files.each do |file|
18
- if File.directory?( "#{root_dir}/#{file}" )
19
- ## note: skip directory if it starts with dot e.g. . or .. or .git etc.
20
- if file.start_with?( '.' )
21
- puts "skipping directory >#{file}< (#{root_dir})"
22
- next
23
- end
24
- subtree = find_files( "#{root_dir}/#{file}" )
25
- tree << [ file, subtree ]
26
- else
27
- ## just a "regular" file
28
- tree << file
29
- end
30
- end
31
- tree
32
- end
33
-
34
-
35
- def merge_filenames( root_dir, hash, opts={} ) ## flags e.g. noop, verbose, etc. see FileUtils
36
- tree = find_files( root_dir )
37
- pp tree
38
-
39
- flags = { ## always use verbose mode for now
40
- verbose: true
41
- }
42
-
43
- ## todo/check: move opts={} to initialize e.g. Merger.new( opts={}) why? why not??
44
-
45
- ## check for noop e.g. test mode/dry run
46
- flags[ :noop ] = true if opts[:test] || opts[:dry_run] || opts[:noop]
47
-
48
- puts "walk tree:"
49
- merge_filenames_worker_step1( root_dir, hash, tree, flags )
50
- merge_filenames_worker_step2( root_dir, hash, tree, flags )
51
- end
52
-
53
- def merge_filenames_worker_step1( root_dir, hash, tree, flags )
54
- ## note: step 1 move all files
55
- ## step 2 move all dirs
56
-
57
- tree.each do |node|
58
- if node.is_a? Array ## assume it's a directory
59
- merge_filenames_worker_step1( "#{root_dir}/#{node[0]}", hash, node[1], flags )
60
- else ## assume it's a file
61
- old_name = node
62
- new_name = merge_path( old_name, hash )
63
- if old_name == new_name
64
- puts " keep file #{node} (#{root_dir})"
65
- else
66
- puts " *** move file #{old_name} => #{new_name} (#{root_dir})"
67
- src_path = "#{root_dir}/#{old_name}"
68
- dest_path = "#{root_dir}/#{new_name}"
69
- FileUtils.mv( src_path, dest_path, flags )
70
- end
71
- end
72
- end
73
- end
74
-
75
- def merge_filenames_worker_step2( root_dir, hash, tree, flags )
76
- ## note: step 1 move all files
77
- ## step 2 move all dirs
78
-
79
- tree.each do |node|
80
- if node.is_a? Array ## assume it's a directory
81
- merge_filenames_worker_step2( "#{root_dir}/#{node[0]}", hash, node[1], flags )
82
- old_name = node[0]
83
- new_name = merge_path( old_name, hash )
84
- if old_name == new_name
85
- puts " keep dir #{node[0]} (#{root_dir})"
86
- else
87
- puts " *** move dir #{old_name} => #{new_name} (#{root_dir})"
88
- src_path = "#{root_dir}/#{old_name}"
89
- dest_path = "#{root_dir}/#{new_name}"
90
- FileUtils.mv( src_path, dest_path, flags )
91
- end
92
- end
93
- end
94
- end
95
-
96
-
97
-
98
- def merge_files( root_dir, hash, opts={} )
99
- ## note: rescan files (after renames)
100
- tree = find_files( root_dir )
101
- pp tree
102
-
103
- puts "walk tree:"
104
- merge_files_worker( root_dir, '', hash, tree, opts )
105
- end
106
-
107
- def merge_files_worker( root_dir, relative_dir, hash, tree, opts )
108
- tree.each do |node|
109
- if node.is_a? Array ## assume it's a directory
110
- if relative_dir.empty? # e.g. just add w/o leading slash e.g. 'lib' and not '/lib'
111
- new_relative_dir = node[0].dup # note: create a new string; just in case
112
- else
113
- new_relative_dir = "#{relative_dir}/#{node[0]}"
114
- end
115
- merge_files_worker( root_dir, new_relative_dir, hash, node[1], opts )
116
- else ## assume it's a file
117
- if relative_dir.empty?
118
- relative_path = node
119
- else
120
- relative_path = "#{relative_dir}/#{node}"
121
- end
122
-
123
- src_path = "#{root_dir}/#{relative_path}"
124
-
125
- ## note: for now assume always assume text files/utf8
126
- ## fix: change to File.read_utf8 ??
127
- old_text = File.read( src_path )
128
- new_text = merge_text( old_text, hash )
129
-
130
- if opts[:o]
131
- dest_root = opts[:o]
132
- dest_path = "#{dest_root}/#{relative_path}"
133
- ## make sure dest_path exists
134
- dest_dir = File.dirname( dest_path )
135
- FileUtils.mkdir_p( dest_dir ) unless Dir.exists?( dest_dir )
136
- else
137
- dest_root = root_dir
138
- dest_path = "#{dest_root}/#{relative_path}"
139
- end
140
-
141
- if old_text == new_text
142
- if opts[:o] ## for testing copy file 1:1
143
- puts " copy file 1:1 #{node} (#{relative_dir}) in (#{dest_root})"
144
- FileUtils.cp( src_path, dest_path, verbose: true )
145
- else
146
- puts " skip file #{node} (#{relative_dir})"
147
- end
148
- else
149
- puts " *** update file #{node} (#{relative_dir}) in (#{dest_root})"
150
- File.open( dest_path, 'w' ) do |f|
151
- f.write new_text
152
- end
153
- end
154
- end
155
- end
156
- end
157
-
158
-
159
-
160
- def merge_path( path, hash )
161
- ## e.g. allow
162
- ## __filename__ or
163
- ## $filename$ for now
164
- path.gsub( /(__|\$)([a-z]+)\1/i ) do |_|
165
- key = $2.to_s
166
- value = hash[ key ]
167
- puts " [path] replacing #{key} w/ >#{value}< in (#{path})"
168
- value
169
- end
170
- end
171
-
172
- def merge_text( text, hash )
173
- ## e.g. allow
174
- ## $filename$ for now only in text
175
- ## note: must include leading and trailing word boundry (/B)
176
- ## e.g. hello$test$ will not match only "free-standing $test
177
- ## or in quote e.g. "$test$"
178
- ## e.g. no letters or digits allowed before or after $ to match
179
-
180
- ## pp text
181
-
182
- text.gsub( /\B\$([a-z]+)\$\B/i ) do |_|
183
- key = $1.to_s
184
- value = hash[ key ]
185
- puts " [text] replacing #{key} w/ >#{value}<"
186
- value
187
- end
188
- end
189
-
190
-
191
-
192
- def merge( root_dir, hash, opts={} )
193
- puts " merge #{root_dir}, #{hash.inspect}"
194
-
195
- merge_filenames( root_dir, hash, opts )
196
- merge_files( root_dir, hash, opts )
197
- end
198
-
199
- end # class Merger
200
-
201
- end # module Quik
202
-
1
+ # encoding: utf-8
2
+
3
+ module Quik
4
+
5
+ class Merger
6
+
7
+
8
+
9
+ def find_files( root_dir )
10
+ tree = []
11
+
12
+ files = Dir.entries( root_dir )
13
+ files = files.sort
14
+ puts "#{root_dir}:"
15
+ pp files
16
+
17
+ files.each do |file|
18
+ if File.directory?( "#{root_dir}/#{file}" )
19
+ ## note: skip directory if it starts with dot e.g. . or .. or .git etc.
20
+ if file.start_with?( '.' )
21
+ puts "skipping directory >#{file}< (#{root_dir})"
22
+ next
23
+ end
24
+ subtree = find_files( "#{root_dir}/#{file}" )
25
+ tree << [ file, subtree ]
26
+ else
27
+ ## just a "regular" file
28
+ tree << file
29
+ end
30
+ end
31
+ tree
32
+ end
33
+
34
+
35
+ def merge_filenames( root_dir, hash, opts={} ) ## flags e.g. noop, verbose, etc. see FileUtils
36
+ tree = find_files( root_dir )
37
+ pp tree
38
+
39
+ flags = { ## always use verbose mode for now
40
+ verbose: true
41
+ }
42
+
43
+ ## todo/check: move opts={} to initialize e.g. Merger.new( opts={}) why? why not??
44
+
45
+ ## check for noop e.g. test mode/dry run
46
+ flags[ :noop ] = true if opts[:test] || opts[:dry_run] || opts[:noop]
47
+
48
+ puts "walk tree:"
49
+ merge_filenames_worker_step1( root_dir, hash, tree, flags )
50
+ merge_filenames_worker_step2( root_dir, hash, tree, flags )
51
+ end
52
+
53
+ def merge_filenames_worker_step1( root_dir, hash, tree, flags )
54
+ ## note: step 1 move all files
55
+ ## step 2 move all dirs
56
+
57
+ tree.each do |node|
58
+ if node.is_a? Array ## assume it's a directory
59
+ merge_filenames_worker_step1( "#{root_dir}/#{node[0]}", hash, node[1], flags )
60
+ else ## assume it's a file
61
+ old_name = node
62
+ new_name = merge_path( old_name, hash )
63
+ if old_name == new_name
64
+ puts " keep file #{node} (#{root_dir})"
65
+ else
66
+ puts " *** move file #{old_name} => #{new_name} (#{root_dir})"
67
+ src_path = "#{root_dir}/#{old_name}"
68
+ dest_path = "#{root_dir}/#{new_name}"
69
+ ## note: make sure subpath exists (e.g. replacement might include new (sub)dirs too)
70
+ FileUtils.mkdir_p( File.dirname( dest_path ), flags ) unless Dir.exist?( File.dirname( dest_path ))
71
+ FileUtils.mv( src_path, dest_path, flags )
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ def merge_filenames_worker_step2( root_dir, hash, tree, flags )
78
+ ## note: step 1 move all files
79
+ ## step 2 move all dirs
80
+
81
+ tree.each do |node|
82
+ if node.is_a? Array ## assume it's a directory
83
+ merge_filenames_worker_step2( "#{root_dir}/#{node[0]}", hash, node[1], flags )
84
+ old_name = node[0]
85
+ new_name = merge_path( old_name, hash )
86
+ if old_name == new_name
87
+ puts " keep dir #{node[0]} (#{root_dir})"
88
+ else
89
+ puts " *** move dir #{old_name} => #{new_name} (#{root_dir})"
90
+ src_path = "#{root_dir}/#{old_name}"
91
+ dest_path = "#{root_dir}/#{new_name}"
92
+ ## note: make sure subpath exists (e.g. replacement might include new (sub)dirs too)
93
+ FileUtils.mkdir_p( File.dirname( dest_path ), flags ) unless Dir.exist?( File.dirname( dest_path ))
94
+ FileUtils.mv( src_path, dest_path, flags )
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+
101
+
102
+ def merge_files( root_dir, hash, opts={} )
103
+ ## note: rescan files (after renames)
104
+ tree = find_files( root_dir )
105
+ pp tree
106
+
107
+ puts "walk tree:"
108
+ merge_files_worker( root_dir, '', hash, tree, opts )
109
+ end
110
+
111
+ def merge_files_worker( root_dir, relative_dir, hash, tree, opts )
112
+ tree.each do |node|
113
+ if node.is_a? Array ## assume it's a directory
114
+ if relative_dir.empty? # e.g. just add w/o leading slash e.g. 'lib' and not '/lib'
115
+ new_relative_dir = node[0].dup # note: create a new string; just in case
116
+ else
117
+ new_relative_dir = "#{relative_dir}/#{node[0]}"
118
+ end
119
+ merge_files_worker( root_dir, new_relative_dir, hash, node[1], opts )
120
+ else ## assume it's a file
121
+ if relative_dir.empty?
122
+ relative_path = node
123
+ else
124
+ relative_path = "#{relative_dir}/#{node}"
125
+ end
126
+
127
+ src_path = "#{root_dir}/#{relative_path}"
128
+
129
+ ## note: for now assume always assume text files/utf8
130
+ old_text = File.open( src_path, 'r:utf-8' ) { |f| f.read }
131
+ new_text = merge_text( old_text, hash )
132
+
133
+ if opts[:o]
134
+ dest_root = opts[:o]
135
+ dest_path = "#{dest_root}/#{relative_path}"
136
+ ## make sure dest_path exists
137
+ dest_dir = File.dirname( dest_path )
138
+ FileUtils.mkdir_p( dest_dir ) unless Dir.exist?( dest_dir )
139
+ else
140
+ dest_root = root_dir
141
+ dest_path = "#{dest_root}/#{relative_path}"
142
+ end
143
+
144
+ if old_text == new_text
145
+ if opts[:o] ## for testing copy file 1:1
146
+ puts " copy file 1:1 #{node} (#{relative_dir}) in (#{dest_root})"
147
+ FileUtils.cp( src_path, dest_path, verbose: true )
148
+ else
149
+ puts " skip file #{node} (#{relative_dir})"
150
+ end
151
+ else
152
+ puts " *** update file #{node} (#{relative_dir}) in (#{dest_root})"
153
+ File.open( dest_path, 'w:utf-8' ) do |f|
154
+ f.write( new_text )
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+
162
+
163
+ def merge_path( path, hash )
164
+ ## e.g. allow
165
+ ## __filename__ or
166
+ ## $filename$ for now
167
+ ## note: allow underline too e.g $file_name$ etc.
168
+ path.gsub( /(__|\$)([a-z_]+)\1/i ) do |_|
169
+ key = $2.to_s
170
+ value = hash[ key ]
171
+ puts " [path] replacing #{key} w/ >#{value}< in (#{path})"
172
+ value
173
+ end
174
+ end
175
+
176
+ def merge_text( text, hash )
177
+ ## e.g. allow
178
+ ## $filename$ for now only in text
179
+ ## note: must include leading and trailing word boundry (/B)
180
+ ## e.g. hello$test$ will not match only "free-standing $test
181
+ ## or in quote e.g. "$test$"
182
+ ## e.g. no letters or digits allowed before or after $ to match
183
+ ## note: allow underline too e.g. $test_klass$ etc.
184
+
185
+ ## pp text
186
+
187
+ text.gsub( /\B\$([a-z_]+)\$\B/i ) do |_|
188
+ key = $1.to_s
189
+ value = hash[ key ]
190
+ puts " [text] replacing #{key} w/ >#{value}<"
191
+ value
192
+ end
193
+ end
194
+
195
+
196
+
197
+ def merge( root_dir, hash, opts={} )
198
+ puts " merge #{root_dir}, #{hash.inspect}"
199
+
200
+ merge_filenames( root_dir, hash, opts )
201
+ merge_files( root_dir, hash, opts )
202
+ end
203
+
204
+ end # class Merger
205
+
206
+ end # module Quik
207
+