app_stack 1.1.4 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|