quik 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,147 +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
-
11
-
12
- ## move to core_ext.rb - why? why not??
13
-
14
- ###
15
- ## fix: use css style
16
- ## e.g. color( :red )
17
- ## color( :blue )
18
- ## background_color( :red )
19
- ## font_style( :bold ) -- why? why not??
20
-
21
- class String
22
-
23
- def self.use_colors?
24
- @@use_color ||= true
25
- ## todo/fix: check for windows on load (set to false;otherwise true)
26
- @@use_color
27
- end
28
-
29
- def self.use_colors=(value)
30
- @@use_color = value
31
- end
32
-
33
-
34
- def red() colorize(31); end
35
- def green() colorize(32); end
36
- def yellow() colorize(33); end ## note: more brown-ish (if not used w/ bold/bright mode) ??
37
- def blue() colorize(34); end
38
- def magenta() colorize(35); end ## pink ??
39
- def cyan() colorize(36); end ## light blue ??
40
-
41
- def bold() colorize(1); end ## just use 0 clear for now; instead of BOLD_OFF (22) code; use bright as an alias??
42
-
43
- private
44
- def colorize(code)
45
- if self.class.use_colors?
46
- "\e[#{code}m#{self}\e[0m"
47
- else
48
- self
49
- end
50
- end
51
-
52
-
53
- ## todo - add modes e.g. bold/underscore/etc.
54
- =begin
55
- class String
56
- def black; "\e[30m#{self}\e[0m" end
57
- def red; "\e[31m#{self}\e[0m" end
58
- def green; "\e[32m#{self}\e[0m" end
59
- def brown; "\e[33m#{self}\e[0m" end
60
- def blue; "\e[34m#{self}\e[0m" end
61
- def magenta; "\e[35m#{self}\e[0m" end
62
- def cyan; "\e[36m#{self}\e[0m" end
63
- def gray; "\e[37m#{self}\e[0m" end
64
-
65
- def on_black; "\e[40m#{self}\e[0m" end
66
- def on_red; "\e[41m#{self}\e[0m" end
67
- def on_green; "\e[42m#{self}\e[0m" end
68
- def on_brown; "\e[43m#{self}\e[0m" end
69
- def on_blue; "\e[44m#{self}\e[0m" end
70
- def on_magenta; "\e[45m#{self}\e[0m" end
71
- def on_cyan; "\e[46m#{self}\e[0m" end
72
- def on_gray; "\e[47m#{self}\e[0m" end
73
-
74
- def bold; "\e[1m#{self}\e[21m" end
75
- def italic; "\e[3m#{self}\e[23m" end
76
- def underline; "\e[4m#{self}\e[24m" end
77
- def blink; "\e[5m#{self}\e[25m" end
78
- def reverse_color; "\e[7m#{self}\e[27m" end
79
-
80
- use Ansi constants - why? why not? e.g.:
81
- //foreground color
82
- public static final String BLACK_TEXT() { return "\033[30m";}
83
- public static final String RED_TEXT() { return "\033[31m";}
84
- public static final String GREEN_TEXT() { return "\033[32m";}
85
- public static final String BROWN_TEXT() { return "\033[33m";}
86
- public static final String BLUE_TEXT() { return "\033[34m";}
87
- public static final String MAGENTA_TEXT() { return "\033[35m";}
88
- public static final String CYAN_TEXT() { return "\033[36m";}
89
- public static final String GRAY_TEXT() { return "\033[37m";}
90
-
91
- //background color
92
- public static final String BLACK_BACK() { return "\033[40m";}
93
- public static final String RED_BACK() { return "\033[41m";}
94
- public static final String GREEN_BACK() { return "\033[42m";}
95
- public static final String BROWN_BACK() { return "\033[43m";}
96
- public static final String BLUE_BACK() { return "\033[44m";}
97
- public static final String MAGENTA_BACK() { return "\033[45m";}
98
- public static final String CYAN_BACK() { return "\033[46m";}
99
- public static final String WHITE_BACK() { return "\033[47m";}
100
-
101
- //ANSI control chars
102
- public static final String RESET_COLORS() { return "\033[0m";}
103
- public static final String BOLD_ON() { return "\033[1m";}
104
- public static final String BLINK_ON() { return "\033[5m";}
105
- public static final String REVERSE_ON() { return "\033[7m";}
106
- public static final String BOLD_OFF() { return "\033[22m";}
107
- public static final String BLINK_OFF() { return "\033[25m";}
108
- public static final String REVERSE_OFF() { return "\033[27m";}
109
- end
110
-
111
- Code Effect
112
- 0 Turn off all attributes
113
- 1 Set bright mode
114
- 4 Set underline mode
115
- 5 Set blink mode
116
- 7 Exchange foreground and background colors
117
- 8 Hide text (foreground color would be the same as background)
118
- 30 Black text
119
- 31 Red text
120
- 32 Green text
121
- 33 Yellow text
122
- 34 Blue text
123
- 35 Magenta text
124
- 36 Cyan text
125
- 37 White text
126
- 39 Default text color
127
- 40 Black background
128
- 41 Red background
129
- 42 Green background
130
- 43 Yellow background
131
- 44 Blue background
132
- 45 Magenta background
133
- 46 Cyan background
134
- 47 White background
135
- 49 Default background color
136
-
137
- note:
138
- puts "\e[31m" # set format (red foreground)
139
- puts "\e[0" # clear format
140
- puts "green-#{"red".red}-green".green # will be green-red-normal, because of \e[0
141
- e.g. for now colors can NOT get nested
142
- plus if you bold/italic/etc. use it before the color e.g.
143
- bold.red etc.
144
- =end
145
-
146
-
147
- end # class String
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,208 +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
- ## 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
- ## fix: change to File.read_utf8 ??
131
- old_text = File.read( src_path )
132
- new_text = merge_text( old_text, hash )
133
-
134
- if opts[:o]
135
- dest_root = opts[:o]
136
- dest_path = "#{dest_root}/#{relative_path}"
137
- ## make sure dest_path exists
138
- dest_dir = File.dirname( dest_path )
139
- FileUtils.mkdir_p( dest_dir ) unless Dir.exist?( dest_dir )
140
- else
141
- dest_root = root_dir
142
- dest_path = "#{dest_root}/#{relative_path}"
143
- end
144
-
145
- if old_text == new_text
146
- if opts[:o] ## for testing copy file 1:1
147
- puts " copy file 1:1 #{node} (#{relative_dir}) in (#{dest_root})"
148
- FileUtils.cp( src_path, dest_path, verbose: true )
149
- else
150
- puts " skip file #{node} (#{relative_dir})"
151
- end
152
- else
153
- puts " *** update file #{node} (#{relative_dir}) in (#{dest_root})"
154
- File.open( dest_path, 'w' ) do |f|
155
- f.write new_text
156
- end
157
- end
158
- end
159
- end
160
- end
161
-
162
-
163
-
164
- def merge_path( path, hash )
165
- ## e.g. allow
166
- ## __filename__ or
167
- ## $filename$ for now
168
- ## note: allow underline too e.g $file_name$ etc.
169
- path.gsub( /(__|\$)([a-z_]+)\1/i ) do |_|
170
- key = $2.to_s
171
- value = hash[ key ]
172
- puts " [path] replacing #{key} w/ >#{value}< in (#{path})"
173
- value
174
- end
175
- end
176
-
177
- def merge_text( text, hash )
178
- ## e.g. allow
179
- ## $filename$ for now only in text
180
- ## note: must include leading and trailing word boundry (/B)
181
- ## e.g. hello$test$ will not match only "free-standing $test
182
- ## or in quote e.g. "$test$"
183
- ## e.g. no letters or digits allowed before or after $ to match
184
- ## note: allow underline too e.g. $test_klass$ etc.
185
-
186
- ## pp text
187
-
188
- text.gsub( /\B\$([a-z_]+)\$\B/i ) do |_|
189
- key = $1.to_s
190
- value = hash[ key ]
191
- puts " [text] replacing #{key} w/ >#{value}<"
192
- value
193
- end
194
- end
195
-
196
-
197
-
198
- def merge( root_dir, hash, opts={} )
199
- puts " merge #{root_dir}, #{hash.inspect}"
200
-
201
- merge_filenames( root_dir, hash, opts )
202
- merge_files( root_dir, hash, opts )
203
- end
204
-
205
- end # class Merger
206
-
207
- end # module Quik
208
-
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
+