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.
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