app_stack 1.3.4 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Gemfile +6 -5
- data/Gemfile.lock +17 -5
- data/README.md +13 -1
- data/app_stack.gemspec +6 -5
- data/lib/app_stack/app.rb +86 -0
- data/lib/app_stack/cli_options.rb +42 -36
- data/lib/app_stack/compare_list.rb +195 -0
- data/lib/app_stack/configuration.rb +119 -0
- data/lib/app_stack/operator.rb +60 -0
- data/lib/app_stack/version.rb +1 -1
- data/lib/app_stack.rb +34 -6
- data/spec/app_stack_spec.rb +17 -0
- data/spec/cli_options_spec.rb +20 -0
- data/spec/compare_list_spec.rb +18 -0
- data/spec/configuration_spec.rb +95 -0
- data/spec/examples/la-assets-helper/app_stack.yml +14 -0
- data/spec/examples/la-assets-helper/lib/la_assets.rb +319 -0
- data/spec/examples/la-import_app/Trivia.erb +2 -0
- data/spec/examples/la-import_app/app_stack.yml +83 -0
- data/spec/examples/la-mongoid/app_stack.yml +49 -0
- data/spec/examples/la-mongoid/config/boot.rb +15 -0
- data/spec/examples/la-mongoid/config/database.rb +12 -0
- data/spec/examples/la-mongoid/config/mongoid.yml +22 -0
- data/spec/examples/la-mongoid/config/mongoid.yml.erb +22 -0
- data/spec/examples/la-sinatra/Gemfile.erb +11 -0
- data/spec/examples/la-sinatra/app_stack.yml +16 -0
- data/spec/examples/no-stacked/some_list.rb +4 -0
- data/spec/examples/sample_config/app_stack.yml +82 -0
- data/spec/examples/sample_config/hash_export.yml +4 -0
- data/spec/load_stack_spec.rb +13 -0
- data/spec/spec_helper.rb +7 -2
- data/tags +87 -34
- metadata +66 -87
- data/.app_stack.yml +0 -14
- data/bin/config_assets +0 -3
- data/lib/app_stack/copy_list_builder.rb +0 -147
- data/lib/app_stack/local_files_parser.rb +0 -95
- data/lib/app_stack/stack_app.rb +0 -119
- data/spec/config_spec.rb +0 -82
- data/spec/fixtures/config_sample/sample.yml +0 -19
- data/spec/fixtures/sample_files/module_1/Gemfile.erb +0 -1
- data/spec/fixtures/sample_files/module_1/app_stack.yml +0 -10
- data/spec/fixtures/sample_files/module_1/config/boot_sample.rb +0 -0
- data/spec/fixtures/sample_files/module_1/doc/excluding.txt +0 -1
- data/spec/fixtures/sample_files/module_1/doc/including.txt +0 -0
- data/spec/fixtures/sample_files/module_1/lib/mixins/b_lib.rb +0 -0
- data/spec/fixtures/sample_files/module_1/lib/mixins/s_lib.rb +0 -0
- data/spec/fixtures/sample_files/module_1/lib/samples/a_lib.rb +0 -0
- data/spec/fixtures/sample_files/module_1/spec/config_spec.rb +0 -0
- data/spec/fixtures/sample_files/module_2/Gemfile +0 -0
- data/spec/fixtures/sample_files/module_2/Gemfile.liquid +0 -1
- data/spec/fixtures/sample_files/module_2/app/lib/sequence.rb +0 -0
- data/spec/fixtures/sample_files/module_2/app_stack.yml +0 -3
- data/spec/fixtures/sample_files/module_3/app_stack.yml +0 -3
- data/spec/fixtures/sample_files/module_3/bin/del +0 -0
- data/spec/fixtures/sample_files/module_3/bin/rm +0 -0
- data/spec/fixtures/sample_files/module_3/config/application.yml +0 -0
- data/spec/fixtures/sample_files/module_3/config/database_development.yml +0 -0
- data/spec/fixtures/sample_files/module_3/config/database_production.yml +0 -0
- data/spec/fixtures/sample_files/module_3/lib/libfile_1.rb +0 -0
- data/spec/fixtures/sample_files/module_3/lib/libfile_2.rb +0 -0
- data/spec/fixtures/sample_files/my_app/app_stack.yml +0 -15
- data/spec/fixtures/sample_files/my_app/doc/excluding.txt +0 -1
- data/spec/fixtures/sample_files/my_app/local.conf +0 -0
- data/spec/fixtures/sample_files/my_app/local.conf.erb +0 -1
- data/spec/fixtures/sample_files/my_app/spec/config_spec.rb +0 -0
- data/spec/fixtures/sample_files/my_app_back/app_stack.yml +0 -15
- data/spec/fixtures/sample_files/my_app_back/doc/excluding.txt +0 -1
- data/spec/fixtures/sample_files/my_app_back/local.conf +0 -0
- data/spec/fixtures/sample_files/my_app_back/local.conf.erb +0 -1
- data/spec/fixtures/sample_files/my_app_back/spec/config_spec.rb +0 -0
- data/spec/merger_spec.rb +0 -43
- data/spec/stackup_spec.rb +0 -18
- /data/{file.erb → spec/examples/find_conf_file/both/.app_stack.yml} +0 -0
- /data/spec/{fixtures/sample_files/module_1/Gemfile → examples/find_conf_file/both/app_stack.yml} +0 -0
- /data/spec/{fixtures/sample_files/module_1/Rakefile → examples/find_conf_file/with_dot/.app_stack.yml} +0 -0
- /data/spec/{fixtures/config_sample → examples/sample_config}/wrong_key.yml +0 -0
- /data/spec/{fixtures/config_sample → examples/sample_config}/wrong_type.yml +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YzE2OTk2NTU4ZWJkMTc2ZDA2NmE4OWE4NGUwMjljY2RiNGQ4NTk1Mw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MTUwYjA2OTYxYjk2NWEyNzk2YWExZmRiYTA0YTU0ZjViYTI0MTEzYw==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YzFmOThkNzg5YmFiMTdlNzJhODU1ZTczM2JkN2U3NDlkYzQzYmFhMTZiNjdi
|
10
|
+
YTFmZTVjYzM3MjMzZmVmYjJjNGZjMGQ4YzQwODQwYzE1MDk0M2FjYWZlNTgw
|
11
|
+
YThjM2QxYzhhZTcyNTY5MTMyMTVmZjA0NWI1ZTY3MTEwODEzOTU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZWE1OWMzNDZiZTk3ZjdhYzBhMDU5MmFmZGYwYTNjM2Q2ZWYzZDVjOGVlZWQ5
|
14
|
+
ODM0YzVhNWQ3ODMzMzU4NzhjZWRhZThkMDhkMWI3NDQzYWQ2NjAyM2RjYWQ5
|
15
|
+
YTI3YWZiMjBjYTc4ODE4NTEwOTQ2ZGVmOWFhZWI3ZDg1YWUwZTc=
|
data/Gemfile
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
source 'http://ruby.taobao.org'
|
2
2
|
|
3
3
|
gem 'diffy'
|
4
|
-
gem 'tilt'
|
5
|
-
gem 'term-ansicolor'
|
6
|
-
gem 'activesupport'
|
4
|
+
gem 'tilt'
|
5
|
+
gem 'term-ansicolor'
|
6
|
+
gem 'activesupport'
|
7
7
|
|
8
8
|
group :development, :test do
|
9
|
-
gem 'rspec'
|
10
|
-
gem '
|
9
|
+
gem 'rspec'
|
10
|
+
gem 'rubocop'
|
11
|
+
gem 'simplecov'
|
11
12
|
end
|
data/Gemfile.lock
CHANGED
@@ -7,12 +7,18 @@ GEM
|
|
7
7
|
multi_json (~> 1.3)
|
8
8
|
thread_safe (~> 0.1)
|
9
9
|
tzinfo (~> 0.3.37)
|
10
|
+
ast (1.1.0)
|
10
11
|
atomic (1.1.13)
|
11
12
|
diff-lcs (1.2.4)
|
12
13
|
diffy (3.0.1)
|
13
14
|
i18n (0.6.5)
|
14
15
|
minitest (4.7.5)
|
15
16
|
multi_json (1.8.0)
|
17
|
+
parser (2.0.0)
|
18
|
+
ast (~> 1.1)
|
19
|
+
slop (~> 3.4, >= 3.4.5)
|
20
|
+
powerpack (0.0.9)
|
21
|
+
rainbow (1.1.4)
|
16
22
|
rspec (2.14.1)
|
17
23
|
rspec-core (~> 2.14.0)
|
18
24
|
rspec-expectations (~> 2.14.0)
|
@@ -21,10 +27,15 @@ GEM
|
|
21
27
|
rspec-expectations (2.14.3)
|
22
28
|
diff-lcs (>= 1.1.3, < 2.0)
|
23
29
|
rspec-mocks (2.14.3)
|
30
|
+
rubocop (0.14.1)
|
31
|
+
parser (~> 2.0)
|
32
|
+
powerpack (~> 0.0.6)
|
33
|
+
rainbow (>= 1.1.4)
|
24
34
|
simplecov (0.7.1)
|
25
35
|
multi_json (~> 1.0)
|
26
36
|
simplecov-html (~> 0.7.1)
|
27
37
|
simplecov-html (0.7.1)
|
38
|
+
slop (3.4.6)
|
28
39
|
term-ansicolor (1.2.2)
|
29
40
|
tins (~> 0.8)
|
30
41
|
thread_safe (0.1.2)
|
@@ -37,9 +48,10 @@ PLATFORMS
|
|
37
48
|
ruby
|
38
49
|
|
39
50
|
DEPENDENCIES
|
40
|
-
activesupport
|
51
|
+
activesupport
|
41
52
|
diffy
|
42
|
-
rspec
|
43
|
-
|
44
|
-
|
45
|
-
|
53
|
+
rspec
|
54
|
+
rubocop
|
55
|
+
simplecov
|
56
|
+
term-ansicolor
|
57
|
+
tilt
|
data/README.md
CHANGED
@@ -4,6 +4,17 @@
|
|
4
4
|
|
5
5
|
Import source files from a chain of application modules.
|
6
6
|
|
7
|
+
## Parking list for new version (1.4)
|
8
|
+
|
9
|
+
- Optional `.app_stack.yml` for stacked modules
|
10
|
+
- Better detection for yaml file error
|
11
|
+
- Better message with short file name
|
12
|
+
- Render files only if contents really changed
|
13
|
+
- <del datetime="2013-11-11T10:05:01 +0800">*new file only* mode</del>
|
14
|
+
- <del datetime="2013-11-11T10:05:15 +0800">Better simulate, new file only, force mode</del>
|
15
|
+
- Recursive stackup (?)
|
16
|
+
- Separate sync/import group instead of one single `stackapps`
|
17
|
+
|
7
18
|
## Synposis
|
8
19
|
|
9
20
|
Put a configuration file `.app_stack.yml` in your application
|
@@ -13,8 +24,9 @@ directory, in format like:
|
|
13
24
|
|
14
25
|
The directory contains other modules to import.
|
15
26
|
|
16
|
-
|
27
|
+
import:
|
17
28
|
- module-1
|
29
|
+
sync:
|
18
30
|
- module-2: [defaults, tests]
|
19
31
|
- module-3:
|
20
32
|
- defaults
|
data/app_stack.gemspec
CHANGED
@@ -12,11 +12,12 @@ Gem::Specification.new 'app_stack', AppStack::VERSION do |s|
|
|
12
12
|
s.license = 'MIT'
|
13
13
|
s.test_files = Dir.glob('{spec,test}/**/*.rb')
|
14
14
|
|
15
|
-
s.add_dependency 'tilt'
|
16
|
-
s.add_dependency 'term-ansicolor'
|
17
|
-
s.add_dependency 'activesupport'
|
15
|
+
s.add_dependency 'tilt'
|
16
|
+
s.add_dependency 'term-ansicolor'
|
17
|
+
s.add_dependency 'activesupport'
|
18
18
|
s.add_dependency 'diffy'
|
19
|
-
s.add_development_dependency 'rspec'
|
20
|
-
s.add_development_dependency '
|
19
|
+
s.add_development_dependency 'rspec'
|
20
|
+
s.add_development_dependency 'rubocop'
|
21
|
+
s.add_development_dependency 'simplecov'
|
21
22
|
end
|
22
23
|
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module AppStack
|
4
|
+
# represent a single stacked application
|
5
|
+
class App
|
6
|
+
attr_accessor :rel_path
|
7
|
+
attr_reader :config, :conf_file, :app_stacks, :export_groups,
|
8
|
+
:sync_list, :import_list, :render_list,
|
9
|
+
:compare_list, :copy_list, :diff_list, :attrs
|
10
|
+
|
11
|
+
include AppStack::ConfigParser
|
12
|
+
|
13
|
+
def initialize(filename = nil, app_dir = nil)
|
14
|
+
@conf_file, @app_dir = filename, app_dir
|
15
|
+
@config = load_config
|
16
|
+
info 'loaded configuration: ', @config
|
17
|
+
|
18
|
+
@app_stacks = {}
|
19
|
+
@compare_list = []
|
20
|
+
@attrs = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
# rubocop:disable MethodLength
|
24
|
+
def load_stack!
|
25
|
+
# load_stack.each ...
|
26
|
+
validate_config!
|
27
|
+
|
28
|
+
# parse all stacks
|
29
|
+
@import_list.merge(@sync_list).merge(@render_list).keys.each do |app|
|
30
|
+
app_dir = File.expand_path(app, File.join(directory, config.stack_dir))
|
31
|
+
# rubocop:disable LineLength
|
32
|
+
fail ParseError, "Error on #{@conf_file}, stack app #{app} not found in #{app_dir}" unless File.directory?(app_dir)
|
33
|
+
# rubocop:enable LineLength
|
34
|
+
@app_stacks[app] = App.new(AppStack.find_conf_file(app_dir), app_dir)
|
35
|
+
@app_stacks[app].rel_path = File.join(directory, config.stack_dir, app)
|
36
|
+
@app_stacks[app].parse_export_groups!
|
37
|
+
@attrs.merge! @app_stacks[app].config.attrs
|
38
|
+
|
39
|
+
# info "load app #{app.bold} as", @app_stacks[app]
|
40
|
+
info "load app #{app.bold}"
|
41
|
+
end
|
42
|
+
|
43
|
+
@attrs.merge! config.attrs
|
44
|
+
info 'merged attrs', attrs
|
45
|
+
@app_stacks
|
46
|
+
end
|
47
|
+
# rubocop:enable MethodLength
|
48
|
+
|
49
|
+
def stackup!
|
50
|
+
load_stack!
|
51
|
+
load_compare_list!
|
52
|
+
build_copy_list!
|
53
|
+
merge!
|
54
|
+
end
|
55
|
+
|
56
|
+
def options
|
57
|
+
AppStack.options
|
58
|
+
end
|
59
|
+
|
60
|
+
def directory
|
61
|
+
@app_dir ||= (conf_file && File.dirname(conf_file))
|
62
|
+
File.expand_path(@app_dir)
|
63
|
+
end
|
64
|
+
|
65
|
+
# use the direct name of the config file as app name
|
66
|
+
def app_name
|
67
|
+
directory && File.basename(directory)
|
68
|
+
end
|
69
|
+
|
70
|
+
# echo message only if verbose specified to be true
|
71
|
+
def info(msg, var = nil)
|
72
|
+
return unless options && options.verbose
|
73
|
+
|
74
|
+
msg = "[#{app_name || self.class.name}] ".green + msg
|
75
|
+
msg += "\n" if var && var.inspect.size > 30
|
76
|
+
|
77
|
+
print msg
|
78
|
+
if var
|
79
|
+
print Term::ANSIColor.bold
|
80
|
+
var.inspect.size > 30 ? PP.pp(var) : PP.singleline_pp(var)
|
81
|
+
print Term::ANSIColor.reset
|
82
|
+
end
|
83
|
+
puts unless var && var.inspect.size > 30
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -2,51 +2,57 @@
|
|
2
2
|
|
3
3
|
require 'optparse'
|
4
4
|
|
5
|
-
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
5
|
+
module AppStack
|
6
|
+
# parse command line options based
|
7
|
+
# rubocop:disable MethodLength
|
8
|
+
class CliOptions
|
9
|
+
attr_accessor :force, :simulate, :verbose, :recursive, :conf_file
|
10
|
+
|
11
|
+
def initialize(argv = ARGV)
|
12
|
+
parse_opt!(argv)
|
13
|
+
case argv.size
|
14
|
+
when 1 then self.conf_file = argv.shift
|
15
|
+
when 0 then self.conf_file = nil
|
16
|
+
else fail "too many configuration files (#{argv.join(', ')})."
|
17
|
+
end
|
16
18
|
end
|
17
|
-
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
# parse command line options
|
21
|
+
def parse_opt!(argv)
|
22
|
+
opt_parser = OptionParser.new do |opts|
|
23
|
+
opts.banner = 'Usage: appstack [options] [config_file]'
|
23
24
|
|
24
|
-
|
25
|
-
|
25
|
+
opts.separator ''
|
26
|
+
opts.separator 'Available options:'
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
28
|
+
opts.on('-f', '--force', 'force overwrite') do
|
29
|
+
self.force = true
|
30
|
+
end
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
opts.on('-s', '--simulate', 'simulate only, do not copy') do
|
33
|
+
self.simulate = true
|
34
|
+
end
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
36
|
+
opts.on('-r', '--recursive', 'recursively stackup all apps') do
|
37
|
+
self.recursive = true
|
38
|
+
end
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
40
|
+
opts.on('--verbose', 'show debug messages') do
|
41
|
+
self.verbose = true
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on_tail('-h', '--help', 'show this message') do
|
45
|
+
puts opts
|
46
|
+
exit
|
47
|
+
end
|
43
48
|
|
44
|
-
|
45
|
-
|
46
|
-
|
49
|
+
opts.on_tail('-v', '--version', 'show version number') do
|
50
|
+
puts AppStack::VERSION
|
51
|
+
exit
|
52
|
+
end
|
47
53
|
end
|
48
|
-
end
|
49
54
|
|
50
|
-
|
55
|
+
opt_parser.parse!(argv)
|
56
|
+
end
|
51
57
|
end
|
52
58
|
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module AppStack
|
4
|
+
|
5
|
+
# items as copy list candidate
|
6
|
+
class CompareFile
|
7
|
+
attr_accessor :from_app, :from_file, :to_app, :to_file,
|
8
|
+
:import, :render, :diff
|
9
|
+
def initialize(opts)
|
10
|
+
opts.each { |k, v| send("#{k}=".to_sym, v) }
|
11
|
+
@to_file ||= to_app.get_file_path(from_file_short_name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def render?
|
15
|
+
render ? true : false
|
16
|
+
end
|
17
|
+
|
18
|
+
def import?
|
19
|
+
import ? true : false
|
20
|
+
end
|
21
|
+
|
22
|
+
def sync?
|
23
|
+
import ? false : true
|
24
|
+
end
|
25
|
+
|
26
|
+
def operation
|
27
|
+
return 'render' if render?
|
28
|
+
import? ? 'import' : 'sync'
|
29
|
+
end
|
30
|
+
|
31
|
+
def diff?
|
32
|
+
return true unless File.exists?(to_file)
|
33
|
+
c_file = from_file
|
34
|
+
if render?
|
35
|
+
c_file = Tempfile.new(File.basename(from_file)).path
|
36
|
+
render! c_file
|
37
|
+
end
|
38
|
+
|
39
|
+
@diff ||= Diffy::Diff.new(to_file, c_file, source: 'files')
|
40
|
+
@diff && @diff.to_s.chomp.size > 0 ? true : false
|
41
|
+
end
|
42
|
+
|
43
|
+
def local_new?
|
44
|
+
File.exists?(to_file) && File.mtime(to_file) > File.mtime(from_file)
|
45
|
+
end
|
46
|
+
|
47
|
+
def remote_new?
|
48
|
+
return true unless File.exists?(to_file)
|
49
|
+
File.mtime(from_file) > File.mtime(to_file)
|
50
|
+
end
|
51
|
+
|
52
|
+
def from_file_short_name
|
53
|
+
from_file.sub(/^#{from_app.directory}\//, '')
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_file_short_name
|
57
|
+
to_file.sub(/^#{to_app.directory}\//, '')
|
58
|
+
end
|
59
|
+
|
60
|
+
def label
|
61
|
+
"#{operation} #{from_file_short_name.blue} from " +
|
62
|
+
"#{from_app.app_name.bold} as #{to_file_short_name.blue}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def skip(msg)
|
66
|
+
"Skip #{operation} ".white + from_file_short_name.blue + ' from '.white +
|
67
|
+
from_app.app_name.bold + ': ' + msg
|
68
|
+
end
|
69
|
+
|
70
|
+
def process
|
71
|
+
render? ? render! : copy!
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def render!(to = nil)
|
77
|
+
to ||= to_file
|
78
|
+
# return if @options.simulate
|
79
|
+
oh = File.open(to, 'wb')
|
80
|
+
if from_file.match(/\.liquid$/)
|
81
|
+
oh.write Liquid::Template
|
82
|
+
.parse(File.open(from_file, 'r:utf-8').read).render(to_app.attrs)
|
83
|
+
else
|
84
|
+
tilt = Tilt.new(from_file)
|
85
|
+
oh.write tilt.render(OpenStruct.new(to_app.attrs))
|
86
|
+
end
|
87
|
+
oh.close
|
88
|
+
end
|
89
|
+
|
90
|
+
def copy!
|
91
|
+
target_dir = File.dirname(to_file)
|
92
|
+
FileUtils.mkdir_p target_dir unless File.directory?(target_dir)
|
93
|
+
FileUtils.cp_r from_file, to_file
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
# reopen app class, extend with load_compare list function
|
99
|
+
class App
|
100
|
+
def load_compare_list!
|
101
|
+
# @import_list.merge(@sync_list).merge(@render_list
|
102
|
+
load_copy_list!(@import_list, import: true)
|
103
|
+
load_copy_list!(@sync_list)
|
104
|
+
load_render_list!(@render_list, render: true)
|
105
|
+
load_local_render_list!(render: true)
|
106
|
+
end
|
107
|
+
|
108
|
+
def load_copy_list!(list, opts = {})
|
109
|
+
list.each do |app, exp|
|
110
|
+
exp.each do |p| # string as group name or hash as file mapping
|
111
|
+
opts.merge! from_app: @app_stacks[app.to_s], to_app: self
|
112
|
+
p.is_a?(String) ? load_group!(p, opts) : load_file_map!(p, opts)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# rubocop:disable MethodLength
|
118
|
+
def load_render_list!(list, opts = {})
|
119
|
+
list.each do |appname, hsh|
|
120
|
+
app = @app_stacks[appname.to_s]
|
121
|
+
hsh.each do |fr, to|
|
122
|
+
to = fr.sub(/\.(erb|liquid|slim)$/, '') unless to.size > 0
|
123
|
+
from_file = app.get_file_path(fr)
|
124
|
+
if File.exists?(from_file)
|
125
|
+
@compare_list << CompareFile.new(
|
126
|
+
opts.merge(from_app: app, to_app: self,
|
127
|
+
from_file: from_file,
|
128
|
+
to_file: get_file_path(to)))
|
129
|
+
else
|
130
|
+
warn "[WARN] #{fr.blue} not found in #{app.app_name.bold}, skip."
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def load_local_render_list!(opts = {})
|
137
|
+
config.render_local.each do |fr, to|
|
138
|
+
from_file = get_file_path(fr)
|
139
|
+
if File.exists?(from_file)
|
140
|
+
@compare_list << CompareFile.new(
|
141
|
+
opts.merge(from_app: self, to_app: self,
|
142
|
+
from_file: from_file, to_file: get_file_path(to)))
|
143
|
+
else
|
144
|
+
warn "[WARN] #{fr.blue} not found in #{app_name.bold} (local), skip."
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# rubocop:disable LineLength
|
150
|
+
def load_group!(group, opts)
|
151
|
+
app = opts[:from_app]
|
152
|
+
fail ParseError, "Error on #{@conf_file}, #{app.app_name.bold} did not export group #{group.bold}" unless app.export_groups[group.to_s]
|
153
|
+
app.get_group_files(group, opts[:from_app]).each do |full_path|
|
154
|
+
@compare_list << CompareFile.new(opts.merge(from_file: full_path))
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def load_file_map!(hsh, opts)
|
159
|
+
hsh.each do |f, t|
|
160
|
+
t = f unless t && t.size > 0
|
161
|
+
from_file = opts[:from_app].get_file_path(f)
|
162
|
+
if File.exists?(from_file)
|
163
|
+
@compare_list << CompareFile.new(
|
164
|
+
opts.merge(from_file: opts[:from_app].get_file_path(f),
|
165
|
+
to_file: opts[:to_app].get_file_path(t)))
|
166
|
+
else
|
167
|
+
warn "[WARN] #{f.blue} not found in #{opts[:from_app].app_name.bold}, skip."
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def get_group_files(group, app)
|
173
|
+
file_list = []
|
174
|
+
export_groups[group.to_s].each do |fp|
|
175
|
+
if fp.match(/[\?\*\+\{\}]+/)
|
176
|
+
Dir[get_file_path(fp)].each { |f| file_list << f }
|
177
|
+
else
|
178
|
+
file = get_file_path(fp)
|
179
|
+
|
180
|
+
if File.exists?(file)
|
181
|
+
file_list << file
|
182
|
+
else
|
183
|
+
warn "[WARN] #{fp.blue} not found for #{app.app_name.bold}, group #{group.bold}, skip."
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
file_list
|
188
|
+
end
|
189
|
+
# rubocop:enable LineLength, MethodLength
|
190
|
+
|
191
|
+
def get_file_path(fp)
|
192
|
+
File.expand_path(fp, directory)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module AppStack
|
4
|
+
class ParseError < ArgumentError; end
|
5
|
+
|
6
|
+
# configuration from yaml file
|
7
|
+
class Configuration
|
8
|
+
attr_accessor :import, :sync, :export, :exclude, :render, :render_local,
|
9
|
+
:attrs, :stack_dir
|
10
|
+
|
11
|
+
# rubocop:disable LineLength
|
12
|
+
def initialize(filename = nil)
|
13
|
+
# use yaml file to set configuration
|
14
|
+
config = default_config.dup
|
15
|
+
YAML.load(File.open(filename, 'r:utf-8').read).each do |k, v|
|
16
|
+
fail ParseError, "unkown option `#{k}` in #{filename}" unless default_config[k.to_sym]
|
17
|
+
fail ParseError, "'#{k}' must be a #{default_config[k.to_sym].class.to_s}" unless v.is_a?(default_config[k.to_sym].class)
|
18
|
+
config[k.to_sym] = v
|
19
|
+
end if filename
|
20
|
+
|
21
|
+
config.each { |k, v| send("#{k}=", v) }
|
22
|
+
end
|
23
|
+
# rubocop:enable LineLength
|
24
|
+
|
25
|
+
def default_config
|
26
|
+
{
|
27
|
+
import: [],
|
28
|
+
sync: [],
|
29
|
+
render: [],
|
30
|
+
render_local: {},
|
31
|
+
export: [],
|
32
|
+
exclude: [],
|
33
|
+
attrs: {},
|
34
|
+
stack_dir: '..'
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# reopen App, mixin
|
40
|
+
module ConfigParser
|
41
|
+
def load_config
|
42
|
+
info 'load configuration file from:', @conf_file
|
43
|
+
Configuration.new(@conf_file)
|
44
|
+
end
|
45
|
+
|
46
|
+
def validate_config!
|
47
|
+
parse_export_groups!
|
48
|
+
@sync_list = parse_stack_list!('sync')
|
49
|
+
info 'load sync list as', @sync_list
|
50
|
+
@import_list = parse_stack_list!('import')
|
51
|
+
info 'load import list as', @import_list
|
52
|
+
@render_list = parse_render_list!
|
53
|
+
|
54
|
+
# rubocop:disable LineLength
|
55
|
+
config.exclude.each { |f| fail "Error on #{@conf_file} for exclude settings, #{f.inspect} is not a String" unless f.is_a?(String) }
|
56
|
+
end
|
57
|
+
|
58
|
+
# rubocop:disable LineLength, MethodLength
|
59
|
+
def parse_export_groups!
|
60
|
+
@export_groups = { 'default' => [] }
|
61
|
+
config.export.each do |exp|
|
62
|
+
case
|
63
|
+
when exp.is_a?(Hash) then @export_groups.merge!(exp)
|
64
|
+
when exp.is_a?(String) then @export_groups['default'] << exp
|
65
|
+
else fail ParseError, "Error on #{@conf_file}, wrong type for export: '#{exp.inspect}'"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# validate export file list format
|
70
|
+
@export_groups.each do |gname, list|
|
71
|
+
fail ParseError, "Error on #{@conf_file}, group name must be an String, not #{gname}" unless gname.is_a?(String)
|
72
|
+
fail ParseError, "Error on #{@conf_file} export group #{gname}, export files should be defined as a Array of String, #{list} is not an array." unless list.is_a?(Array)
|
73
|
+
list.each { |f| fail ParseError, "Error on #{@conf_file} export group #{gname}, export files should be defined as a Array of string, not #{f}" unless f.is_a?(String) }
|
74
|
+
end
|
75
|
+
@export_groups
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def parse_stack_list!(list_type)
|
81
|
+
hlist = {}
|
82
|
+
config.send(list_type.to_sym).each do |exp|
|
83
|
+
case
|
84
|
+
when exp.is_a?(String) then # exp is the app name with default group
|
85
|
+
hlist[exp] = ['default']
|
86
|
+
when exp.is_a?(Hash)
|
87
|
+
exp.each do |app, list|
|
88
|
+
fail ParseError, "Error on #{@conf_file}, #{list_type} group, #{app} is not a String" unless app.is_a?(String)
|
89
|
+
fail ParseError, "Error on #{@conf_file}, #{list_type} group, #{list} is not a Array" unless list.is_a?(Array)
|
90
|
+
list.each do |g|
|
91
|
+
fail ParseError, "Error on #{@conf_file}, #{app} in #{list_type}, #{g} should be an String (file group) or a Hash (file mapping)" unless g.is_a?(Hash) || g.is_a?(String)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
hlist.merge!(exp)
|
95
|
+
else fail ParseError, "Error on #{@conf_file}, #{list_type} group, should be an array of String or Hash." unless list.is_a?(Array)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
hlist
|
99
|
+
end
|
100
|
+
|
101
|
+
def parse_render_list!
|
102
|
+
hlist = {}
|
103
|
+
config.render.each do |exp|
|
104
|
+
fail ParseError, "Error on #{@conf_file}, render group, #{exp.inspect} is not a Hash" unless exp.is_a?(Hash)
|
105
|
+
exp.each do |app, list|
|
106
|
+
fail ParseError, "Error on #{@conf_file}, #{list_type} group, #{app} is not a String" unless app.is_a?(String)
|
107
|
+
fail ParseError, "Error on #{@conf_file}, render for #{app}, #{list.inspect} is not a Hash" unless list.is_a?(Hash)
|
108
|
+
list.each do |fr, to|
|
109
|
+
fail ParseError, "Error on #{@conf_file}, render for #{app}, #{fr.inspect} is not a String" unless fr.is_a?(String)
|
110
|
+
fail ParseError, "Error on #{@conf_file}, render for #{app}, #{to.inspect} is not a String" unless to.is_a?(String)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
hlist.merge! exp
|
114
|
+
end
|
115
|
+
hlist
|
116
|
+
end
|
117
|
+
# rubocop:enable LineLength
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module AppStack
|
4
|
+
# mixin app class for build copy list
|
5
|
+
# rubocop:disable MethodLength
|
6
|
+
class App
|
7
|
+
def build_copy_list!
|
8
|
+
@copy_list, @diff_list = [], []
|
9
|
+
compare_list.each do |f|
|
10
|
+
info f.label
|
11
|
+
if File.exists?(f.to_file)
|
12
|
+
if f.import?
|
13
|
+
info f.skip('target exists')
|
14
|
+
next
|
15
|
+
end
|
16
|
+
|
17
|
+
if f.diff?
|
18
|
+
info f.label, 'add to diff list'
|
19
|
+
@diff_list << f
|
20
|
+
else
|
21
|
+
info f.skip('not changed')
|
22
|
+
next
|
23
|
+
end
|
24
|
+
end
|
25
|
+
info f.label, 'add to copy list'
|
26
|
+
@copy_list << f
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def merge!
|
31
|
+
case
|
32
|
+
when AppStack.options.force then do_copy!
|
33
|
+
when AppStack.options.simulate then do_simulate!
|
34
|
+
else diff_list.size > 0 ? do_warn : do_copy!
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def do_copy!
|
41
|
+
@copy_list.map(&:process)
|
42
|
+
end
|
43
|
+
|
44
|
+
def do_simulate!
|
45
|
+
@copy_list.each { |f| puts f.label }
|
46
|
+
end
|
47
|
+
|
48
|
+
def do_warn
|
49
|
+
puts 'These files are changed remotely:'
|
50
|
+
i = 1
|
51
|
+
@diff_list.each do |f|
|
52
|
+
puts "[#{i.to_s.bold}] " + f.to_file_short_name.blue
|
53
|
+
puts "from: #{f.from_file_short_name.blue}"
|
54
|
+
puts f.diff.to_s(:color)
|
55
|
+
i += 1
|
56
|
+
end
|
57
|
+
puts 'use -f (--force) option to overwrite all files.'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|