quik 0.3.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/{HISTORY.md → CHANGELOG.md} +3 -3
- data/LICENSE.md +116 -0
- data/Manifest.txt +30 -29
- data/README.md +133 -131
- data/Rakefile +33 -34
- data/bin/qk +5 -5
- data/bin/quik +5 -5
- data/lib/quik.rb +55 -44
- data/lib/quik/builder.rb +96 -96
- data/lib/quik/catalog.rb +73 -56
- data/lib/quik/cli/main.rb +213 -179
- data/lib/quik/cli/opts.rb +33 -33
- data/lib/quik/colors.rb +148 -147
- data/lib/quik/config.rb +60 -60
- data/lib/quik/merger.rb +207 -208
- data/lib/quik/package.rb +116 -115
- data/lib/quik/version.rb +24 -24
- data/lib/quik/wizard.rb +88 -88
- data/test/data/gem-starter-template/Manifest.txt +9 -9
- data/test/data/gem-starter-template/README.md +22 -22
- data/test/data/gem-starter-template/lib/__filename__/version.rb +12 -12
- data/test/helper.rb +18 -22
- data/test/test_colors.rb +39 -39
- data/test/test_config.rb +35 -35
- data/test/test_merger.rb +35 -35
- data/test/test_package.rb +22 -22
- data/test/test_wizard.rb +50 -50
- metadata +26 -38
- data/.gemtest +0 -0
data/lib/quik/colors.rb
CHANGED
@@ -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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
##
|
17
|
-
##
|
18
|
-
##
|
19
|
-
##
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
def
|
36
|
-
def
|
37
|
-
def
|
38
|
-
def
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
def
|
58
|
-
def
|
59
|
-
def
|
60
|
-
def
|
61
|
-
def
|
62
|
-
def
|
63
|
-
def
|
64
|
-
|
65
|
-
|
66
|
-
def
|
67
|
-
def
|
68
|
-
def
|
69
|
-
def
|
70
|
-
def
|
71
|
-
def
|
72
|
-
def
|
73
|
-
|
74
|
-
|
75
|
-
def
|
76
|
-
def
|
77
|
-
def
|
78
|
-
def
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
public static final String
|
84
|
-
public static final String
|
85
|
-
public static final String
|
86
|
-
public static final String
|
87
|
-
public static final String
|
88
|
-
public static final String
|
89
|
-
public static final String
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
public static final String
|
94
|
-
public static final String
|
95
|
-
public static final String
|
96
|
-
public static final String
|
97
|
-
public static final String
|
98
|
-
public static final String
|
99
|
-
public static final String
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
public static final String
|
104
|
-
public static final String
|
105
|
-
public static final String
|
106
|
-
public static final String
|
107
|
-
public static final String
|
108
|
-
public static final String
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
puts "\e[
|
140
|
-
puts "
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
data/lib/quik/config.rb
CHANGED
@@ -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
|
data/lib/quik/merger.rb
CHANGED
@@ -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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
dest_path
|
137
|
-
|
138
|
-
dest_dir
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
##
|
166
|
-
##
|
167
|
-
## $
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
##
|
179
|
-
##
|
180
|
-
##
|
181
|
-
##
|
182
|
-
##
|
183
|
-
## e.g.
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
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
|
+
|