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.
Files changed (113) hide show
  1. data/Gemfile.lock +43 -0
  2. data/README.md +49 -38
  3. data/doc/coverage/.last_run.json +1 -0
  4. data/doc/coverage/.resultset.json +1 -0
  5. data/doc/coverage/assets/0.7.1/application.css +1110 -0
  6. data/doc/coverage/assets/0.7.1/application.js +626 -0
  7. data/doc/coverage/assets/0.7.1/fancybox/blank.gif +0 -0
  8. data/doc/coverage/assets/0.7.1/fancybox/fancy_close.png +0 -0
  9. data/doc/coverage/assets/0.7.1/fancybox/fancy_loading.png +0 -0
  10. data/doc/coverage/assets/0.7.1/fancybox/fancy_nav_left.png +0 -0
  11. data/doc/coverage/assets/0.7.1/fancybox/fancy_nav_right.png +0 -0
  12. data/doc/coverage/assets/0.7.1/fancybox/fancy_shadow_e.png +0 -0
  13. data/doc/coverage/assets/0.7.1/fancybox/fancy_shadow_n.png +0 -0
  14. data/doc/coverage/assets/0.7.1/fancybox/fancy_shadow_ne.png +0 -0
  15. data/doc/coverage/assets/0.7.1/fancybox/fancy_shadow_nw.png +0 -0
  16. data/doc/coverage/assets/0.7.1/fancybox/fancy_shadow_s.png +0 -0
  17. data/doc/coverage/assets/0.7.1/fancybox/fancy_shadow_se.png +0 -0
  18. data/doc/coverage/assets/0.7.1/fancybox/fancy_shadow_sw.png +0 -0
  19. data/doc/coverage/assets/0.7.1/fancybox/fancy_shadow_w.png +0 -0
  20. data/doc/coverage/assets/0.7.1/fancybox/fancy_title_left.png +0 -0
  21. data/doc/coverage/assets/0.7.1/fancybox/fancy_title_main.png +0 -0
  22. data/doc/coverage/assets/0.7.1/fancybox/fancy_title_over.png +0 -0
  23. data/doc/coverage/assets/0.7.1/fancybox/fancy_title_right.png +0 -0
  24. data/doc/coverage/assets/0.7.1/fancybox/fancybox-x.png +0 -0
  25. data/doc/coverage/assets/0.7.1/fancybox/fancybox-y.png +0 -0
  26. data/doc/coverage/assets/0.7.1/fancybox/fancybox.png +0 -0
  27. data/doc/coverage/assets/0.7.1/favicon_green.png +0 -0
  28. data/doc/coverage/assets/0.7.1/favicon_red.png +0 -0
  29. data/doc/coverage/assets/0.7.1/favicon_yellow.png +0 -0
  30. data/doc/coverage/assets/0.7.1/loading.gif +0 -0
  31. data/doc/coverage/assets/0.7.1/magnify.png +0 -0
  32. data/doc/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  33. data/doc/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  34. data/doc/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  35. data/doc/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  36. data/doc/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  37. data/doc/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  38. data/doc/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  39. data/doc/coverage/assets/0.7.1/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  40. data/doc/coverage/assets/0.7.1/smoothness/images/ui-icons_222222_256x240.png +0 -0
  41. data/doc/coverage/assets/0.7.1/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  42. data/doc/coverage/assets/0.7.1/smoothness/images/ui-icons_454545_256x240.png +0 -0
  43. data/doc/coverage/assets/0.7.1/smoothness/images/ui-icons_888888_256x240.png +0 -0
  44. data/doc/coverage/assets/0.7.1/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  45. data/doc/coverage/index.html +2420 -0
  46. data/lib/app_stack/config.rb +132 -0
  47. data/lib/app_stack/merger.rb +127 -0
  48. data/lib/app_stack/version.rb +1 -1
  49. data/lib/app_stack.rb +15 -246
  50. data/spec/config_spec.rb +82 -0
  51. data/spec/fixtures/config_sample/sample.yml +19 -0
  52. data/spec/fixtures/config_sample/wrong_key.yml +2 -0
  53. data/spec/fixtures/config_sample/wrong_type.yml +1 -0
  54. data/spec/fixtures/sample_files/module_1/Gemfile.erb +1 -0
  55. data/spec/fixtures/sample_files/module_1/app_stack.yml +10 -0
  56. data/spec/fixtures/sample_files/module_1/doc/excluding.txt +1 -0
  57. data/spec/fixtures/sample_files/module_1/lib/mixins/b_lib.rb +0 -0
  58. data/spec/fixtures/sample_files/module_1/lib/mixins/s_lib.rb +0 -0
  59. data/spec/fixtures/sample_files/module_1/lib/samples/a_lib.rb +0 -0
  60. data/spec/fixtures/sample_files/module_1/spec/config_spec.rb +0 -0
  61. data/spec/fixtures/sample_files/module_2/Gemfile +0 -0
  62. data/spec/fixtures/sample_files/module_2/Gemfile.liquid +1 -0
  63. data/spec/fixtures/sample_files/module_2/app/lib/sequence.rb +0 -0
  64. data/spec/fixtures/sample_files/module_2/app_stack.yml +3 -0
  65. data/spec/fixtures/sample_files/module_3/app_stack.yml +3 -0
  66. data/spec/fixtures/sample_files/module_3/bin/del +0 -0
  67. data/spec/fixtures/sample_files/module_3/bin/rm +0 -0
  68. data/spec/fixtures/sample_files/module_3/config/application.yml +0 -0
  69. data/spec/fixtures/sample_files/module_3/config/database_development.yml +0 -0
  70. data/spec/fixtures/sample_files/module_3/config/database_production.yml +0 -0
  71. data/spec/fixtures/sample_files/module_3/lib/libfile_1.rb +0 -0
  72. data/spec/fixtures/sample_files/module_3/lib/libfile_2.rb +0 -0
  73. data/spec/fixtures/sample_files/my_app/app_stack.yml +15 -0
  74. data/spec/fixtures/sample_files/my_app/doc/excluding.txt +1 -0
  75. data/spec/fixtures/sample_files/my_app/spec/config_spec.rb +0 -0
  76. data/spec/fixtures/sample_files/my_app_back/app_stack.yml +15 -0
  77. data/spec/fixtures/sample_files/my_app_back/doc/excluding.txt +1 -0
  78. data/spec/fixtures/sample_files/my_app_back/spec/config_spec.rb +0 -0
  79. data/spec/merger_spec.rb +43 -0
  80. data/spec/spec_helper.rb +6 -8
  81. data/spec/stackup_spec.rb +11 -50
  82. metadata +94 -47
  83. data/spec/app_as_hash_spec.rb +0 -15
  84. data/spec/export_list_spec.rb +0 -66
  85. data/spec/fixtures/.app_stack.yml +0 -21
  86. data/spec/fixtures/app-sample.yml +0 -25
  87. data/spec/fixtures/incexc/.gitignore +0 -27
  88. data/spec/fixtures/incexc/incexc-config.yml +0 -24
  89. data/spec/fixtures/incexc/lib/anyway.rb +0 -1
  90. data/spec/fixtures/incexc/lib/extra/excludes.rb +0 -1
  91. data/spec/fixtures/incexc/lib/mixin/otherlib.rb +0 -1
  92. data/spec/fixtures/incexc/spec/spec_helper.rb +0 -1
  93. data/spec/fixtures/incexc/spec/support/api_test.rb +0 -1
  94. data/spec/fixtures/incexc-config.yml +0 -23
  95. data/spec/fixtures/my_app/.app_stack.yml +0 -31
  96. data/spec/fixtures/my_app/.gitignore +0 -27
  97. data/spec/fixtures/my_app/config/self_render.conf.erb +0 -1
  98. data/spec/fixtures/sample_config.yml +0 -21
  99. data/spec/fixtures/stack_apps/module-1/.app_stack.yml +0 -17
  100. data/spec/fixtures/stack_apps/module-1/Gemfile +0 -3
  101. data/spec/fixtures/stack_apps/module-1/lib/auth_util.rb +0 -5
  102. data/spec/fixtures/stack_apps/module-1/lib/libonly1.rb +0 -6
  103. data/spec/fixtures/stack_apps/module-2/.app_stack.yml +0 -22
  104. data/spec/fixtures/stack_apps/module-2/Gemfile.erb +0 -11
  105. data/spec/fixtures/stack_apps/module-2/lib/auth_util.rb +0 -6
  106. data/spec/fixtures/stack_apps/module-2/lib/libonly2.rb +0 -6
  107. data/spec/gitignore_list_spec.rb +0 -21
  108. data/spec/load_configuration_spec.rb +0 -16
  109. data/spec/register_self_spec.rb +0 -13
  110. /data/spec/fixtures/{stack_apps/module-2 → sample_files/module_1}/Gemfile +0 -0
  111. /data/spec/fixtures/{incexc → sample_files/module_1}/Rakefile +0 -0
  112. /data/spec/fixtures/{incexc/.rspec → sample_files/module_1/config/boot_sample.rb} +0 -0
  113. /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
@@ -2,5 +2,5 @@
2
2
 
3
3
  # AppStack module
4
4
  module AppStack
5
- VERSION = '1.1.4'
5
+ VERSION = '1.2.0'
6
6
  end
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/erb'
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 = '.app_stack.yml'
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
- if File.exists?(src_f + @config['tpl_ext'])
134
- carp "From #{app.blue.bold} render #{file.bold}",
135
- render_file!(src_f + @config['tpl_ext'], tgt_f), 1
136
-
137
- else
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
- # update template timestamp
257
- FileUtils.touch(f)
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
- # use module variables, skip `new`
264
- # rubocop:disable ModuleFunction
265
- extend self
34
+ module_function :stackup!
266
35
  end
@@ -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,19 @@
1
+ stack:
2
+ - mod_a
3
+ - mod_b:
4
+ - file_a
5
+ - file_b: file_b_target
6
+ export:
7
+ - Rakefile
8
+ - tests:
9
+ - spec/**/*
10
+ - others:
11
+ - Gemfile
12
+ - Procfile
13
+ exclude:
14
+ - exclude_me
15
+ attrs:
16
+ outer:
17
+ - inter:
18
+ some_key: some_value
19
+
@@ -0,0 +1,2 @@
1
+ stack: []
2
+ dir_name: '-'
@@ -0,0 +1 @@
1
+ stack: {}
@@ -0,0 +1 @@
1
+ Mod1: <%= application_code %>
@@ -0,0 +1,10 @@
1
+ export:
2
+ - lib/**/*.rb
3
+ - doc/**/*
4
+ - Gemfile
5
+ - Rakefile
6
+ - tests:
7
+ - spec/**/*_spec.rb
8
+ - config:
9
+ - config/boot_sample.rb: config/boot.rb
10
+
@@ -0,0 +1 @@
1
+ from module1
File without changes
@@ -0,0 +1 @@
1
+ Mod2: {{ application_code }}
@@ -0,0 +1,3 @@
1
+ export:
2
+ - app/**/*.rb
3
+ - Gemfile