app_stack 1.3.4 → 1.4.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.
- 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
|