app_stack 1.1.4 → 1.2.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.
- data/Gemfile.lock +43 -0
- data/README.md +49 -38
- data/doc/coverage/.last_run.json +1 -0
- data/doc/coverage/.resultset.json +1 -0
- data/doc/coverage/assets/0.7.1/application.css +1110 -0
- data/doc/coverage/assets/0.7.1/application.js +626 -0
- data/doc/coverage/assets/0.7.1/fancybox/blank.gif +0 -0
- data/doc/coverage/assets/0.7.1/fancybox/fancy_close.png +0 -0
- data/doc/coverage/assets/0.7.1/fancybox/fancy_loading.png +0 -0
- data/doc/coverage/assets/0.7.1/fancybox/fancy_nav_left.png +0 -0
- data/doc/coverage/assets/0.7.1/fancybox/fancy_nav_right.png +0 -0
- data/doc/coverage/assets/0.7.1/fancybox/fancy_shadow_e.png +0 -0
- data/doc/coverage/assets/0.7.1/fancybox/fancy_shadow_n.png +0 -0
- data/doc/coverage/assets/0.7.1/fancybox/fancy_shadow_ne.png +0 -0
- data/doc/coverage/assets/0.7.1/fancybox/fancy_shadow_nw.png +0 -0
- data/doc/coverage/assets/0.7.1/fancybox/fancy_shadow_s.png +0 -0
- data/doc/coverage/assets/0.7.1/fancybox/fancy_shadow_se.png +0 -0
- data/doc/coverage/assets/0.7.1/fancybox/fancy_shadow_sw.png +0 -0
- data/doc/coverage/assets/0.7.1/fancybox/fancy_shadow_w.png +0 -0
- data/doc/coverage/assets/0.7.1/fancybox/fancy_title_left.png +0 -0
- data/doc/coverage/assets/0.7.1/fancybox/fancy_title_main.png +0 -0
- data/doc/coverage/assets/0.7.1/fancybox/fancy_title_over.png +0 -0
- data/doc/coverage/assets/0.7.1/fancybox/fancy_title_right.png +0 -0
- data/doc/coverage/assets/0.7.1/fancybox/fancybox-x.png +0 -0
- data/doc/coverage/assets/0.7.1/fancybox/fancybox-y.png +0 -0
- data/doc/coverage/assets/0.7.1/fancybox/fancybox.png +0 -0
- data/doc/coverage/assets/0.7.1/favicon_green.png +0 -0
- data/doc/coverage/assets/0.7.1/favicon_red.png +0 -0
- data/doc/coverage/assets/0.7.1/favicon_yellow.png +0 -0
- data/doc/coverage/assets/0.7.1/loading.gif +0 -0
- data/doc/coverage/assets/0.7.1/magnify.png +0 -0
- data/doc/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/doc/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/doc/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/doc/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/doc/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/doc/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/doc/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/doc/coverage/assets/0.7.1/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/doc/coverage/assets/0.7.1/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/doc/coverage/assets/0.7.1/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/doc/coverage/assets/0.7.1/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/doc/coverage/assets/0.7.1/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/doc/coverage/assets/0.7.1/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/doc/coverage/index.html +2420 -0
- data/lib/app_stack/config.rb +132 -0
- data/lib/app_stack/merger.rb +127 -0
- data/lib/app_stack/version.rb +1 -1
- data/lib/app_stack.rb +15 -246
- data/spec/config_spec.rb +82 -0
- data/spec/fixtures/config_sample/sample.yml +19 -0
- data/spec/fixtures/config_sample/wrong_key.yml +2 -0
- data/spec/fixtures/config_sample/wrong_type.yml +1 -0
- data/spec/fixtures/sample_files/module_1/Gemfile.erb +1 -0
- data/spec/fixtures/sample_files/module_1/app_stack.yml +10 -0
- data/spec/fixtures/sample_files/module_1/doc/excluding.txt +1 -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 +1 -0
- data/spec/fixtures/sample_files/module_2/app/lib/sequence.rb +0 -0
- data/spec/fixtures/sample_files/module_2/app_stack.yml +3 -0
- data/spec/fixtures/sample_files/module_3/app_stack.yml +3 -0
- 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 +15 -0
- data/spec/fixtures/sample_files/my_app/doc/excluding.txt +1 -0
- data/spec/fixtures/sample_files/my_app/spec/config_spec.rb +0 -0
- data/spec/fixtures/sample_files/my_app_back/app_stack.yml +15 -0
- data/spec/fixtures/sample_files/my_app_back/doc/excluding.txt +1 -0
- data/spec/fixtures/sample_files/my_app_back/spec/config_spec.rb +0 -0
- data/spec/merger_spec.rb +43 -0
- data/spec/spec_helper.rb +6 -8
- data/spec/stackup_spec.rb +11 -50
- metadata +94 -47
- data/spec/app_as_hash_spec.rb +0 -15
- data/spec/export_list_spec.rb +0 -66
- data/spec/fixtures/.app_stack.yml +0 -21
- data/spec/fixtures/app-sample.yml +0 -25
- data/spec/fixtures/incexc/.gitignore +0 -27
- data/spec/fixtures/incexc/incexc-config.yml +0 -24
- data/spec/fixtures/incexc/lib/anyway.rb +0 -1
- data/spec/fixtures/incexc/lib/extra/excludes.rb +0 -1
- data/spec/fixtures/incexc/lib/mixin/otherlib.rb +0 -1
- data/spec/fixtures/incexc/spec/spec_helper.rb +0 -1
- data/spec/fixtures/incexc/spec/support/api_test.rb +0 -1
- data/spec/fixtures/incexc-config.yml +0 -23
- data/spec/fixtures/my_app/.app_stack.yml +0 -31
- data/spec/fixtures/my_app/.gitignore +0 -27
- data/spec/fixtures/my_app/config/self_render.conf.erb +0 -1
- data/spec/fixtures/sample_config.yml +0 -21
- data/spec/fixtures/stack_apps/module-1/.app_stack.yml +0 -17
- data/spec/fixtures/stack_apps/module-1/Gemfile +0 -3
- data/spec/fixtures/stack_apps/module-1/lib/auth_util.rb +0 -5
- data/spec/fixtures/stack_apps/module-1/lib/libonly1.rb +0 -6
- data/spec/fixtures/stack_apps/module-2/.app_stack.yml +0 -22
- data/spec/fixtures/stack_apps/module-2/Gemfile.erb +0 -11
- data/spec/fixtures/stack_apps/module-2/lib/auth_util.rb +0 -6
- data/spec/fixtures/stack_apps/module-2/lib/libonly2.rb +0 -6
- data/spec/gitignore_list_spec.rb +0 -21
- data/spec/load_configuration_spec.rb +0 -16
- data/spec/register_self_spec.rb +0 -13
- /data/spec/fixtures/{stack_apps/module-2 → sample_files/module_1}/Gemfile +0 -0
- /data/spec/fixtures/{incexc → sample_files/module_1}/Rakefile +0 -0
- /data/spec/fixtures/{incexc/.rspec → sample_files/module_1/config/boot_sample.rb} +0 -0
- /data/spec/fixtures/{incexc/Rakefile.erb → sample_files/module_1/doc/including.txt} +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'yaml'
|
|
3
|
+
|
|
4
|
+
module AppStack
|
|
5
|
+
class ParseError < RuntimeError; end
|
|
6
|
+
|
|
7
|
+
# an object represent information from .app_stack.yml file
|
|
8
|
+
class Config
|
|
9
|
+
attr_reader :filename, :copy_list
|
|
10
|
+
|
|
11
|
+
def initialize(file)
|
|
12
|
+
@filename = File.expand_path(file)
|
|
13
|
+
yml = YAML.load(File.open(@filename, 'r:utf-8').read)
|
|
14
|
+
|
|
15
|
+
# default configuration, also restrict known keys and data type
|
|
16
|
+
@config = {
|
|
17
|
+
stack: [],
|
|
18
|
+
export: [],
|
|
19
|
+
exclude: [],
|
|
20
|
+
attrs: {},
|
|
21
|
+
stack_dir: '../',
|
|
22
|
+
tpl_ext: %w[.erb .haml .liquid]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# use yaml file to set configuration
|
|
26
|
+
yml.each do |k, v|
|
|
27
|
+
raise ParseError,
|
|
28
|
+
"unkown option `#{k}` in #{@filename}" unless @config[k.to_sym]
|
|
29
|
+
raise ParseError,
|
|
30
|
+
"'#{k}' must be a #{@config[k.to_sym].class.to_s}" unless
|
|
31
|
+
v.is_a?(@config[k.to_sym].class)
|
|
32
|
+
@config[k.to_sym] = v
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# convert stack dir to relative path:
|
|
36
|
+
@config[:stack_dir] = File.expand_path(@config[:stack_dir],
|
|
37
|
+
File.dirname(@filename))
|
|
38
|
+
|
|
39
|
+
# define reader accessors:
|
|
40
|
+
@config.each do |k, v|
|
|
41
|
+
self.class.send(:define_method, k.to_sym) { v }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# copy list from merge's view point
|
|
45
|
+
@copy_list = {}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def directory
|
|
49
|
+
File.expand_path('../', filename)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# use the direct name of the config file as app name
|
|
53
|
+
def app_name
|
|
54
|
+
File.basename(File.dirname(File.expand_path(filename)))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def attr_vars
|
|
58
|
+
@config[:attrs]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# get export files list (copy_from => copy_to hash) for a specific group
|
|
62
|
+
def export_files(group = 'default')
|
|
63
|
+
@export_files ||= parse_export_files
|
|
64
|
+
group = 'default' if group.to_s == 'defaults' # back-compatibility
|
|
65
|
+
@export_files[group.to_s]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def exclude_files
|
|
69
|
+
@export_files ||= find_files(@config[:exclude], false)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def add_copy_list(fhsh)
|
|
73
|
+
if fhsh.is_a?(Hash)
|
|
74
|
+
fhsh.each do |fr, to|
|
|
75
|
+
fr = File.expand_path(fr, directory)
|
|
76
|
+
@copy_list[fr] = to
|
|
77
|
+
end
|
|
78
|
+
else
|
|
79
|
+
@copy_list.merge! export_files(fhsh) # the group's name
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
# get all exported files in groups
|
|
86
|
+
def parse_export_files
|
|
87
|
+
# 'file_group_name' => { copy_from => copy_to_base_name }
|
|
88
|
+
@export_files ||= {}
|
|
89
|
+
export.each do |p_list| # pattern list
|
|
90
|
+
if p_list.is_a?(Hash) # group, file list
|
|
91
|
+
p_list.each do |n, p|
|
|
92
|
+
@export_files[n] ||= {}
|
|
93
|
+
@export_files[n].merge! find_files(p)
|
|
94
|
+
end
|
|
95
|
+
else
|
|
96
|
+
@export_files['default'] ||= {}
|
|
97
|
+
@export_files['default'].merge! find_files(p_list)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
@export_files
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# from file pattern to copy_from => copy_to (remove directory header) hash
|
|
104
|
+
def glob_files(dir, allow_hash)
|
|
105
|
+
fhsh = {}
|
|
106
|
+
if dir.is_a?(Hash)
|
|
107
|
+
raise 'Can not use hash here for ' + dir.keys.join.to_s + ' in file ' +
|
|
108
|
+
filename unless allow_hash
|
|
109
|
+
dir.each do |f, t|
|
|
110
|
+
t = '/' + t.gsub(/^\//, '')
|
|
111
|
+
fhsh[File.expand_path(f, directory)] = t
|
|
112
|
+
end
|
|
113
|
+
else
|
|
114
|
+
Dir[File.expand_path(dir, directory)].each do |f|
|
|
115
|
+
fhsh[f] = f.gsub(/^#{directory}/, '')
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
fhsh
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# from array (or string) of file patterns, glob files and build a
|
|
122
|
+
# large array
|
|
123
|
+
def find_files(group, allow_hash = true)
|
|
124
|
+
group = [ group ] unless group.is_a?(Array)
|
|
125
|
+
fhsh = {}
|
|
126
|
+
group.each do |file_pattern|
|
|
127
|
+
fhsh.merge! glob_files(file_pattern, allow_hash)
|
|
128
|
+
end
|
|
129
|
+
fhsh
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module AppStack
|
|
4
|
+
# runner for merge files from stack of apps
|
|
5
|
+
class Merger
|
|
6
|
+
attr_reader :stacks, :copy_list, :render_list
|
|
7
|
+
|
|
8
|
+
def initialize(conf_file, verbose = false)
|
|
9
|
+
# load configuration
|
|
10
|
+
@conf_file = conf_file
|
|
11
|
+
@config = Config.new(@conf_file)
|
|
12
|
+
@verbose = verbose
|
|
13
|
+
|
|
14
|
+
@stacks = []
|
|
15
|
+
@attr_vars = {}
|
|
16
|
+
@attr_last_mod = File.mtime(@conf_file)
|
|
17
|
+
parse_stacks
|
|
18
|
+
|
|
19
|
+
@copy_list = {}
|
|
20
|
+
@render_list = {}
|
|
21
|
+
@prepared = false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def prepare(verbose = false)
|
|
25
|
+
local_dir = Pathname.new(@config.directory)
|
|
26
|
+
@stacks.each do |stack|
|
|
27
|
+
stack.copy_list.each do |fr, to| # copy from, copy to
|
|
28
|
+
to = local_dir + to.gsub(/^\//, '')
|
|
29
|
+
if @config.exclude_files[to.to_s]
|
|
30
|
+
puts 'keep '.green.bold + to.to_s.gsub(@config.stack_dir, '') if verbose
|
|
31
|
+
elsif newer?(to, fr)
|
|
32
|
+
puts 'skip '.white.bold + to.to_s.gsub(@config.stack_dir, '') if verbose
|
|
33
|
+
else
|
|
34
|
+
# render list
|
|
35
|
+
tpl_file = nil
|
|
36
|
+
@config.tpl_ext.each do |ext|
|
|
37
|
+
tpl_file = fr + ext if File.exists?(fr + ext)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
if tpl_file
|
|
41
|
+
@render_list[to.to_s] = tpl_file if newer?(tpl_file, to) || attr_mod?(to)
|
|
42
|
+
else
|
|
43
|
+
@copy_list[to.to_s] = fr if newer?(fr, to)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
@prepared = true
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if verbose
|
|
51
|
+
@copy_list.each do |to, fr|
|
|
52
|
+
puts 'copy '.bold + fr.gsub(@config.stack_dir, '')
|
|
53
|
+
puts ' to '.bold + to.to_s.gsub(@config.stack_dir, '')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
@render_list.each do |to, fr|
|
|
57
|
+
puts 'render '.bold + fr.gsub(@config.stack_dir, '')
|
|
58
|
+
puts ' to '.bold + to.to_s.gsub(@config.stack_dir, '')
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def merge!
|
|
64
|
+
@prepared || prepare(@verbose)
|
|
65
|
+
@copy_list.each { |to, fr| copy_file!(fr, to) }
|
|
66
|
+
@render_list.each { |to, fr| render_file!(fr, to) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def copy_file!(fr, to)
|
|
72
|
+
target_dir = File.dirname(to)
|
|
73
|
+
FileUtils.mkdir_p target_dir unless File.directory?(target_dir)
|
|
74
|
+
FileUtils.copy fr, to
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def render_file!(fr, to)
|
|
78
|
+
var = @attr_vars.deep_merge(@config.attr_vars)
|
|
79
|
+
oh = File.open(to, 'wb')
|
|
80
|
+
if fr.match(/\.liquid$/)
|
|
81
|
+
require 'liquid'
|
|
82
|
+
oh.write Liquid::Template.parse(File.open(fr, 'r:utf-8').read).render(var)
|
|
83
|
+
else
|
|
84
|
+
tilt = Tilt.new(fr)
|
|
85
|
+
oh.write tilt.render(OpenStruct.new(var))
|
|
86
|
+
end
|
|
87
|
+
oh.close
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# get a list of stacked app object
|
|
91
|
+
def parse_stacks
|
|
92
|
+
@config.stack.each do |stk|
|
|
93
|
+
case
|
|
94
|
+
when stk.is_a?(String) then @stacks << load_stack(stk, ['default'])
|
|
95
|
+
when stk.is_a?(Hash)
|
|
96
|
+
stk.each { |key, groups| @stacks << load_stack(key, groups) }
|
|
97
|
+
else
|
|
98
|
+
raise 'stack must be a string or a hash, in ' + @conf_file
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# read stack from list, set copy_list
|
|
104
|
+
def load_stack(stack_name, groups)
|
|
105
|
+
conf_base_name = File.basename(@conf_file)
|
|
106
|
+
stack_dir = Pathname.new(@config.stack_dir) + stack_name
|
|
107
|
+
stack = Config.new(stack_dir + conf_base_name)
|
|
108
|
+
fmodtime = File.mtime(stack_dir + conf_base_name)
|
|
109
|
+
@attr_last_mod = fmodtime if fmodtime > @attr_last_mod # update file mod time
|
|
110
|
+
@attr_vars = @attr_vars.deep_merge stack.attr_vars
|
|
111
|
+
groups.each { |grp| stack.add_copy_list(grp) }
|
|
112
|
+
stack
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# if f1 newer than f2, or f2 not exits but f1 does.
|
|
116
|
+
def newer?(f1, f2)
|
|
117
|
+
return false unless File.exists?(f1)
|
|
118
|
+
return true unless File.exists?(f2)
|
|
119
|
+
File.mtime(f1) > File.mtime(f2)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def attr_mod?(to)
|
|
123
|
+
return true unless File.exists?(to)
|
|
124
|
+
@attr_last_mod > File.mtime(to)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
data/lib/app_stack/version.rb
CHANGED
data/lib/app_stack.rb
CHANGED
|
@@ -4,263 +4,32 @@ require 'yaml'
|
|
|
4
4
|
require 'find'
|
|
5
5
|
require 'fileutils'
|
|
6
6
|
require 'ostruct'
|
|
7
|
-
require 'tilt
|
|
7
|
+
require 'tilt'
|
|
8
|
+
require 'pathname'
|
|
8
9
|
require 'active_support/core_ext/hash/deep_merge'
|
|
9
10
|
|
|
10
11
|
require 'term/ansicolor'
|
|
11
12
|
# mixin String class for term-color methods
|
|
12
13
|
class String; include Term::ANSIColor end
|
|
13
14
|
|
|
15
|
+
require 'app_stack/config'
|
|
16
|
+
# require 'app_stack/copier'
|
|
17
|
+
require 'app_stack/merger'
|
|
18
|
+
|
|
14
19
|
## A namespace for app-stack based modules
|
|
15
20
|
module AppStack
|
|
16
|
-
CONF_FILE = '
|
|
17
|
-
|
|
18
|
-
# public entry ponit, receive a configuration filename,
|
|
19
|
-
# copy source files on to the directory contains the
|
|
20
|
-
# configuration file.
|
|
21
|
-
def stackup!(conf_file)
|
|
22
|
-
load_configuration(conf_file)
|
|
23
|
-
|
|
24
|
-
register_self!(@app_root)
|
|
25
|
-
merge_stacks!(@config['stack'])
|
|
26
|
-
render_self!(@self_files)
|
|
27
|
-
|
|
28
|
-
# rewrite configuration back to app-stack file
|
|
29
|
-
@origin_config['files'] = @files
|
|
30
|
-
File.open(conf_file, 'wb') { |fh| fh.puts YAML.dump(@origin_config) }
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# convert directory names, load configuration from yaml file
|
|
34
|
-
# rubocop:disable MethodLength
|
|
35
|
-
def load_configuration(conf_file)
|
|
36
|
-
@conf_file = conf_file || CONF_FILE
|
|
37
|
-
@config = YAML.load(File.read(@conf_file))
|
|
38
|
-
@origin_config = @config.dup
|
|
39
|
-
|
|
40
|
-
@app_root = @config['app_root'] || File.dirname(@conf_file)
|
|
41
|
-
@app_root = File.expand_path(@app_root)
|
|
42
|
-
@stack_dir = @config['stack_dir'] || '../stack_apps'
|
|
43
|
-
@stack_dir = File.expand_path(@app_root + '/' +
|
|
44
|
-
@stack_dir) if @stack_dir.match(/^\.\.?\//)
|
|
45
|
-
|
|
46
|
-
# default values
|
|
47
|
-
@verbose = @config['verbose'] || 1
|
|
48
|
-
@verbose = @verbose.to_i
|
|
49
|
-
|
|
50
|
-
@config['tpl_ext'] ||= '.erb'
|
|
51
|
-
raise ArgumentError, 'ERROR: `include` key depriciated, ' +
|
|
52
|
-
'please update your yml file' if @config['include']
|
|
53
|
-
@config['include'] = []
|
|
54
|
-
@config['exclude'] ||= []
|
|
55
|
-
@config['export'] ||= [] # even export can be blank
|
|
56
|
-
@config['stack'] ||= []
|
|
57
|
-
@config['files'] ||= {}
|
|
58
|
-
|
|
59
|
-
# attrs to assigned into template
|
|
60
|
-
@attrs = {}
|
|
61
|
-
# file list under the app_root
|
|
62
|
-
@self_files = []
|
|
63
|
-
|
|
64
|
-
@files = @config['files']
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# for files already in the app root, register to no-copy list
|
|
68
|
-
def register_self!(dir)
|
|
69
|
-
Find.find(dir).each do |f|
|
|
70
|
-
next if f == dir
|
|
71
|
-
basename = f.sub(/^#{dir}\//, '')
|
|
72
|
-
next if basename.match(/^.git$/)
|
|
73
|
-
next if basename.match(/^.git\W/)
|
|
74
|
-
next if gitignore_list(dir).include?(basename)
|
|
75
|
-
|
|
76
|
-
if @files[basename]
|
|
77
|
-
carp "From #{'self'.blue.bold} #{basename.bold} ",
|
|
78
|
-
'keep'.white, 2 if @files[basename] == '__self'
|
|
79
|
-
else
|
|
80
|
-
carp "From #{'self'.blue.bold} #{basename.bold}",
|
|
81
|
-
'registed'.green.bold, 1
|
|
82
|
-
@files[basename] = '__self'
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
@self_files << f unless @self_files.include?(f)
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# render file in app-root
|
|
90
|
-
def render_self!(files)
|
|
91
|
-
files.each do |f|
|
|
92
|
-
if File.exists?(f + @config['tpl_ext'])
|
|
93
|
-
basename = f.sub(/^#{@app_root}\//, '')
|
|
94
|
-
carp "From #{'self'.blue.bold} render #{basename.bold}",
|
|
95
|
-
render_file!(f + @config['tpl_ext'], f), 1
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# copy from a stack of applications
|
|
101
|
-
def merge_stacks!(stack)
|
|
102
|
-
stack.each do |app|
|
|
103
|
-
app_dir, groups = '', ['default']
|
|
104
|
-
if app.is_a?(Hash)
|
|
105
|
-
app.each { |k, v| app, groups = k, v }
|
|
106
|
-
end
|
|
107
|
-
app_dir = @stack_dir + '/' + app
|
|
108
|
-
|
|
109
|
-
raise "no directory found for #{app}" unless File.directory?(app_dir)
|
|
110
|
-
raise "no configuration found for #{app}" unless
|
|
111
|
-
File.exists?(app_dir + '/' + File.basename(@conf_file))
|
|
112
|
-
|
|
113
|
-
# loop over remote files
|
|
114
|
-
elist = export_list(app_dir, groups)
|
|
115
|
-
elist.each do |file|
|
|
116
|
-
# skip .erb file as template
|
|
117
|
-
next if file.match(/#{@config['tpl_ext']}$/) &&
|
|
118
|
-
elist.include?(file.sub(/#{@config['tpl_ext']}$/, ''))
|
|
119
|
-
# find the absolute path for source and target file for copy
|
|
120
|
-
src_f = File.expand_path(app_dir + '/' + file)
|
|
121
|
-
tgt_f = File.expand_path(@app_root + '/' + file)
|
|
21
|
+
CONF_FILE = './.app_stack.yml'
|
|
122
22
|
|
|
123
|
-
if @files[file] == '__self' # don't handle it
|
|
124
|
-
carp "From #{app.blue.bold} #{file.bold}",
|
|
125
|
-
'skip, use '.white + @files[file], 2
|
|
126
|
-
else # not registered as self, copy over
|
|
127
|
-
unless @files[file] == app
|
|
128
|
-
carp "By #{app.bold.blue}, overwrite #{@files[file].bold} #{file}",
|
|
129
|
-
'ok'.green, 1 if @files[file]
|
|
130
|
-
@files[file] = app
|
|
131
|
-
end
|
|
132
23
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
carp "From #{app.blue.bold} copy #{file.bold}",
|
|
139
|
-
copy_file!(src_f, tgt_f), 1
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
# register the copied file to app-root file list
|
|
143
|
-
@self_files << file unless @self_files.include?(file)
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
# print debug / information message to console based on verbose level
|
|
150
|
-
def carp(job, state = 'done'.green, v = 1)
|
|
151
|
-
return if @verbose < v
|
|
152
|
-
dots = 70 - job.size
|
|
153
|
-
job += ' ' + '.' * dots if dots > 0
|
|
154
|
-
puts job + ' ' + state
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
# fetch (and cache) git ignore file lists for a specific directory
|
|
158
|
-
def gitignore_list(dir)
|
|
159
|
-
@gitignore_list ||= {}
|
|
160
|
-
@gitignore_list[dir] ||= []
|
|
161
|
-
|
|
162
|
-
ilist = []
|
|
163
|
-
if File.exists?(dir + '/.gitignore')
|
|
164
|
-
File.read(dir + '/.gitignore').split("\n").each do |line|
|
|
165
|
-
Dir[dir + '/' + line].each do |f|
|
|
166
|
-
fn = f.sub(/^#{dir}\//, '')
|
|
167
|
-
ilist << fn unless ilist.include?(fn)
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
@gitignore_list[dir] = ilist
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
# if f1 newer than f2, or f2 not exits but f1 does.
|
|
175
|
-
def newer?(f1, f2)
|
|
176
|
-
return false unless File.exists?(f1)
|
|
177
|
-
return true unless File.exists?(f2)
|
|
178
|
-
File.mtime(f1) > File.mtime(f2)
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
# find a list of file to copy based on export setting
|
|
182
|
-
def export_list(dir, groups = ['default'])
|
|
183
|
-
dir_conf = YAML.load(File.read(dir + '/' + File.basename(@conf_file)))
|
|
184
|
-
dir_conf['export'] ||= []
|
|
185
|
-
dir_conf['exclude'] ||= []
|
|
186
|
-
|
|
187
|
-
# update attr list for assign to template files
|
|
188
|
-
@attrs.deep_merge! dir_conf['attrs'] if dir_conf['attrs'] &&
|
|
189
|
-
dir_conf['attrs'].is_a?(Hash)
|
|
190
|
-
|
|
191
|
-
group_patterns, flist = { 'default' => [] }, []
|
|
192
|
-
# export list defined in stack app's configuration
|
|
193
|
-
dir_conf['export'].each do |e|
|
|
194
|
-
if e.is_a?(Hash)
|
|
195
|
-
e.each { |k, v| group_patterns[k] = v }
|
|
196
|
-
else
|
|
197
|
-
group_patterns['default'] << e
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
group_patterns.each do |k, v|
|
|
202
|
-
raise "file for group #{k} is not a array." unless v.is_a?(Array)
|
|
203
|
-
next unless groups.include?(k) || groups.include?('all')
|
|
204
|
-
v.each do |e|
|
|
205
|
-
Dir[dir + '/' + e].each do |f|
|
|
206
|
-
fn = f.sub(/^#{dir}\/?/, '')
|
|
207
|
-
flist << fn unless flist.include?(fn)
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
exc_list = []
|
|
213
|
-
dir_conf['exclude'].each do |exc|
|
|
214
|
-
Dir[dir + '/' + exc].each do |f|
|
|
215
|
-
fn = f.sub(/^#{dir}\/?/, '')
|
|
216
|
-
exc_list << fn unless exc_list.include?(fn)
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
# adjust by include/exclude and
|
|
221
|
-
flist - gitignore_list(dir) - exc_list
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
# copy file if newer
|
|
225
|
-
def copy_file!(f, target)
|
|
226
|
-
# directory?
|
|
227
|
-
if File.directory?(f)
|
|
228
|
-
if File.directory?(target)
|
|
229
|
-
done = 'exists'.green
|
|
230
|
-
else
|
|
231
|
-
FileUtils.mkdir_p target
|
|
232
|
-
done = 'created'.bold.green
|
|
233
|
-
end
|
|
234
|
-
else
|
|
235
|
-
if newer?(f, target)
|
|
236
|
-
target_dir = File.dirname(target)
|
|
237
|
-
FileUtils.mkdir_p target_dir unless File.directory?(target_dir)
|
|
238
|
-
FileUtils.copy f, target
|
|
239
|
-
done = 'copied'.bold.green
|
|
240
|
-
else
|
|
241
|
-
done = 'keep'.white
|
|
242
|
-
end
|
|
243
|
-
end
|
|
244
|
-
done
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
# render from erb if newer
|
|
248
|
-
def render_file!(f, target)
|
|
249
|
-
done = 'keep'.white
|
|
250
|
-
# unless newer?(target, f)
|
|
251
|
-
tilt = Tilt::ERBTemplate.new(f)
|
|
252
|
-
oh = File.open(target, 'wb')
|
|
253
|
-
oh.write tilt.render(OpenStruct.new(@attrs.deep_merge(@config['attrs'])))
|
|
254
|
-
oh.close
|
|
24
|
+
# based on conf_file, merge up from stack of applications
|
|
25
|
+
def stackup!(conf_file)
|
|
26
|
+
# merge files from stacks
|
|
27
|
+
conf_file ||= CONF_FILE
|
|
28
|
+
mger = Merger.new(conf_file)
|
|
255
29
|
|
|
256
|
-
#
|
|
257
|
-
|
|
258
|
-
done = 'rendered'.bold.green
|
|
259
|
-
# end
|
|
260
|
-
# done
|
|
30
|
+
# not run, just prepare in vobose mode
|
|
31
|
+
ENV['NR'] ? mger.prepare(true) : mger.merge!
|
|
261
32
|
end
|
|
262
33
|
|
|
263
|
-
|
|
264
|
-
# rubocop:disable ModuleFunction
|
|
265
|
-
extend self
|
|
34
|
+
module_function :stackup!
|
|
266
35
|
end
|
data/spec/config_spec.rb
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
describe AppStack::Config do
|
|
5
|
+
context 'raise parse errors' do
|
|
6
|
+
it 'for unkown keys' do
|
|
7
|
+
expect do
|
|
8
|
+
AppStack::Config.new('spec/fixtures/config_sample/wrong_key.yml')
|
|
9
|
+
end.to raise_error(AppStack::ParseError)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'for wrong data types' do
|
|
13
|
+
expect do
|
|
14
|
+
AppStack::Config.new('spec/fixtures/config_sample/wrong_type.yml')
|
|
15
|
+
end.to raise_error(AppStack::ParseError)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context 'load configuration' do
|
|
20
|
+
subject :config do
|
|
21
|
+
AppStack::Config.new('spec/fixtures/config_sample/sample.yml')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'for default' do
|
|
25
|
+
config.stack_dir.should eq(File.expand_path('spec/fixtures'))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'set new value' do
|
|
29
|
+
config.attrs['outer'][0]['inter']['some_key'].should eq('some_value')
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context 'read properties' do
|
|
34
|
+
subject :config do
|
|
35
|
+
AppStack::Config.new('spec/fixtures/sample_files/my_app/app_stack.yml')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it '#directory' do
|
|
39
|
+
dir = File.expand_path('./spec/fixtures/sample_files/my_app')
|
|
40
|
+
config.directory.should eq(dir)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it '#filename' do
|
|
44
|
+
file = File.expand_path('./spec/fixtures/sample_files/my_app/app_stack.yml')
|
|
45
|
+
config.filename.should eq(file)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it '#app_name' do
|
|
49
|
+
config.app_name.should eq('my_app')
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
describe '#export_files' do
|
|
54
|
+
subject(:conf) { AppStack::Config.new('spec/fixtures/sample_files/module_1/app_stack.yml') }
|
|
55
|
+
it 'default file list' do
|
|
56
|
+
%w[mixins/b_lib.rb mixins/s_lib.rb samples/a_lib.rb].each do |fb|
|
|
57
|
+
file = File.expand_path('./spec/fixtures/sample_files/module_1/lib/' + fb)
|
|
58
|
+
conf.export_files[file].should eq('/lib/' + fb)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'load group files' do
|
|
63
|
+
file = File.expand_path('./spec/fixtures/sample_files/module_1/spec/config_spec.rb')
|
|
64
|
+
conf.export_files('tests')[file].should eq('/spec/config_spec.rb')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'file mapping hash' do
|
|
68
|
+
file = File.expand_path('./spec/fixtures/sample_files/module_1/config/boot_sample.rb')
|
|
69
|
+
conf.export_files('config')[file].should eq('/config/boot.rb')
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe 'other file lists' do
|
|
74
|
+
subject(:conf) { AppStack::Config.new('spec/fixtures/sample_files/my_app/app_stack.yml') }
|
|
75
|
+
it '#exclude_list' do
|
|
76
|
+
file = File.expand_path('./spec/fixtures/sample_files/my_app/doc/excluding.txt')
|
|
77
|
+
conf.exclude_files[file].should eq('/doc/excluding.txt')
|
|
78
|
+
p conf.export_files('config')
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
stack: {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Mod1: <%= application_code %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from module1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Mod2: {{ application_code }}
|
|
File without changes
|