repo_manager 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/.gemfiles +115 -0
  2. data/.gitattributes +1 -0
  3. data/.gitignore +7 -0
  4. data/.rspec +3 -0
  5. data/.yardopts +11 -0
  6. data/Gemfile +11 -0
  7. data/Gemfile.lock +99 -0
  8. data/Guardfile +63 -0
  9. data/HISTORY.markdown +12 -0
  10. data/LICENSE +20 -0
  11. data/README.markdown +192 -0
  12. data/Rakefile +94 -0
  13. data/TODO.markdown +15 -0
  14. data/VERSION +1 -0
  15. data/bin/repo +151 -0
  16. data/cucumber.yml +28 -0
  17. data/examples/pc_saved_game_backup/.gitignore +2 -0
  18. data/examples/pc_saved_game_backup/INSTALL.markdown +420 -0
  19. data/examples/pc_saved_game_backup/README.markdown +108 -0
  20. data/examples/pc_saved_game_backup/remote/.gitignore +2 -0
  21. data/examples/pc_saved_game_backup/repo_manager/Gemfile +12 -0
  22. data/examples/pc_saved_game_backup/repo_manager/Gemfile.lock +66 -0
  23. data/examples/pc_saved_game_backup/repo_manager/assets/.gitignore +2 -0
  24. data/examples/pc_saved_game_backup/repo_manager/features/support/aruba.rb +15 -0
  25. data/examples/pc_saved_game_backup/repo_manager/features/support/env.rb +11 -0
  26. data/examples/pc_saved_game_backup/repo_manager/features/support/steps.rb +3 -0
  27. data/examples/pc_saved_game_backup/repo_manager/features/tasks/update.feature +144 -0
  28. data/examples/pc_saved_game_backup/repo_manager/global/default/asset.conf +2 -0
  29. data/examples/pc_saved_game_backup/repo_manager/repo.conf +64 -0
  30. data/examples/pc_saved_game_backup/repo_manager/tasks/.gitignore +0 -0
  31. data/examples/pc_saved_game_backup/repo_manager/tasks/remote.rb +57 -0
  32. data/examples/pc_saved_game_backup/repo_manager/tasks/update.rb +65 -0
  33. data/examples/pc_saved_game_backup/saved_games/hearts/save1 +1 -0
  34. data/examples/pc_saved_game_backup/saved_games/hearts/save2 +1 -0
  35. data/examples/pc_saved_game_backup/saved_games/mines/my_profile.ini +1 -0
  36. data/examples/pc_saved_game_backup/saved_games/mines/saves/save1 +1 -0
  37. data/examples/pc_saved_game_backup/saved_games/mines/saves/save2 +1 -0
  38. data/features/actions/git.feature +296 -0
  39. data/features/actions/help.feature +53 -0
  40. data/features/actions/list.feature +624 -0
  41. data/features/actions/path.feature +195 -0
  42. data/features/actions/status.feature +261 -0
  43. data/features/actions/task.feature +127 -0
  44. data/features/assets/configuration.feature +204 -0
  45. data/features/assets/rendering.feature +42 -0
  46. data/features/assets/user_attributes.feature +98 -0
  47. data/features/bin.feature +42 -0
  48. data/features/logger.feature +218 -0
  49. data/features/settings.feature +240 -0
  50. data/features/support/aruba.rb +15 -0
  51. data/features/support/env.rb +11 -0
  52. data/features/support/steps.rb +3 -0
  53. data/features/tasks/add/asset.feature +178 -0
  54. data/features/tasks/generate/init.feature +56 -0
  55. data/lib/repo_manager.rb +36 -0
  56. data/lib/repo_manager/actions.rb +8 -0
  57. data/lib/repo_manager/actions/action_helper.rb +39 -0
  58. data/lib/repo_manager/actions/app_action.rb +30 -0
  59. data/lib/repo_manager/actions/base_action.rb +296 -0
  60. data/lib/repo_manager/actions/git_action.rb +113 -0
  61. data/lib/repo_manager/actions/help_action.rb +52 -0
  62. data/lib/repo_manager/actions/list_action.rb +123 -0
  63. data/lib/repo_manager/actions/path_action.rb +22 -0
  64. data/lib/repo_manager/actions/status_action.rb +192 -0
  65. data/lib/repo_manager/actions/task_action.rb +71 -0
  66. data/lib/repo_manager/app.rb +116 -0
  67. data/lib/repo_manager/assets.rb +3 -0
  68. data/lib/repo_manager/assets/app_asset.rb +15 -0
  69. data/lib/repo_manager/assets/asset_accessors.rb +67 -0
  70. data/lib/repo_manager/assets/asset_configuration.rb +137 -0
  71. data/lib/repo_manager/assets/asset_manager.rb +72 -0
  72. data/lib/repo_manager/assets/base_asset.rb +199 -0
  73. data/lib/repo_manager/assets/repo_asset.rb +30 -0
  74. data/lib/repo_manager/core.rb +2 -0
  75. data/lib/repo_manager/core/array.rb +21 -0
  76. data/lib/repo_manager/core/hash.rb +83 -0
  77. data/lib/repo_manager/errors.rb +10 -0
  78. data/lib/repo_manager/extensions/hash.rb +86 -0
  79. data/lib/repo_manager/git.rb +2 -0
  80. data/lib/repo_manager/git/lib.rb +69 -0
  81. data/lib/repo_manager/git/status.rb +196 -0
  82. data/lib/repo_manager/logger.rb +39 -0
  83. data/lib/repo_manager/settings.rb +98 -0
  84. data/lib/repo_manager/tasks.rb +3 -0
  85. data/lib/repo_manager/tasks/add/asset.rb +213 -0
  86. data/lib/repo_manager/tasks/generate/init.rb +42 -0
  87. data/lib/repo_manager/tasks/generate/templates/config/repo.conf.tt +61 -0
  88. data/lib/repo_manager/tasks/generate/templates/init/assets/.gitignore +0 -0
  89. data/lib/repo_manager/tasks/generate/templates/init/global/default/asset.conf +2 -0
  90. data/lib/repo_manager/tasks/generate/templates/init/tasks/.gitignore +0 -0
  91. data/lib/repo_manager/tasks/task_manager.rb +166 -0
  92. data/lib/repo_manager/tasks/thor_helper.rb +29 -0
  93. data/lib/repo_manager/test/asset_steps.rb +19 -0
  94. data/lib/repo_manager/test/base_steps.rb +152 -0
  95. data/lib/repo_manager/test/repo_api.rb +41 -0
  96. data/lib/repo_manager/test/repo_steps.rb +83 -0
  97. data/lib/repo_manager/test/test_api.rb +88 -0
  98. data/lib/repo_manager/views.rb +2 -0
  99. data/lib/repo_manager/views/app_view.rb +15 -0
  100. data/lib/repo_manager/views/base_view.rb +137 -0
  101. data/lib/repo_manager/views/templates/css/basic.css +26 -0
  102. data/lib/repo_manager/views/templates/default.erb +40 -0
  103. data/lib/repo_manager/views/templates/default.slim +37 -0
  104. data/lib/repo_manager/views/view_helper.rb +55 -0
  105. data/repo_manager.gemspec +75 -0
  106. data/spec/basic_app/actions/action_helper_spec.rb +54 -0
  107. data/spec/basic_app/assets/base_asset_spec.rb +210 -0
  108. data/spec/basic_app/core_spec.rb +78 -0
  109. data/spec/basic_app/settings_spec.rb +64 -0
  110. data/spec/basic_app/views/view_helper_spec.rb +28 -0
  111. data/spec/basic_gem/aruba_helper_spec.rb +33 -0
  112. data/spec/basic_gem/basic_gem_spec.rb +84 -0
  113. data/spec/basic_gem/gemspec_spec.rb +68 -0
  114. data/spec/repo_manager/git_spec.rb +31 -0
  115. data/spec/spec_helper.rb +25 -0
  116. metadata +472 -0
@@ -0,0 +1,71 @@
1
+ module RepoManager
2
+
3
+ # @group CLI actions
4
+ #
5
+ # Invoke external tasks, normally Thor tasks
6
+ #
7
+ # @example Usage: repo task TASK [args]
8
+ #
9
+ # repo task repo_manager:sweep:screenshots /to/some/folder
10
+ # repo repo_manager:sweep:screenshots /to/some/folder
11
+ #
12
+ # @example General task help:
13
+ #
14
+ # repo help task
15
+ #
16
+ # @example Help for specific task
17
+ #
18
+ # repo task help repo_manager:sweep:screenshots
19
+ # repo help repo_manager:sweep:screenshots
20
+ #
21
+ # @example Display a list of tasks
22
+ #
23
+ # repo task -T
24
+ # repo -T
25
+ #
26
+ # repo task --tasks
27
+ # repo --tasks
28
+ #
29
+ # @return [Number] exit code from task
30
+ class TaskAction < AppAction
31
+
32
+ # Add action specific options
33
+ def parse_options
34
+ super(:raise_on_invalid_option => false, :parse_base_options => false) do |opts|
35
+
36
+ opts.on("-T", "--tasks", "List tasks") do |t|
37
+ options[:tasks] = t
38
+ end
39
+
40
+ end
41
+ end
42
+
43
+ def process
44
+ # Thor actions can include toxic side effects,
45
+ # keep the namespace clean until needed
46
+ require 'repo_manager/tasks/task_manager'
47
+ task_manager = RepoManager::TaskManager.new(configuration)
48
+
49
+ if options[:tasks]
50
+ task_manager.list_tasks
51
+ return 0
52
+ end
53
+
54
+ raise "task name required" if args.empty?
55
+
56
+ target = args.shift
57
+
58
+ if target == "help"
59
+ target = args.shift
60
+ task_manager.task_help(target)
61
+ else
62
+ task_manager.invoke(target, args)
63
+ end
64
+ end
65
+
66
+ def help
67
+ super(:comment_starting_with => "Invoke", :located_in_file => __FILE__)
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,116 @@
1
+ require 'term/ansicolor'
2
+
3
+ class String
4
+ include Term::ANSIColor
5
+ end
6
+
7
+ module RepoManager
8
+
9
+ AVAILABLE_ACTIONS = %w[help list task path status git]
10
+
11
+ # Commands that don't need to have the 'git' arg precede them. These are not
12
+ # necessarily the same as the allowed 'commands' specified in the repo.conf
13
+ # file.
14
+ # NOTE: `git help --all` for a full list of git supported commands
15
+ GIT_NATIVE_SUPPORT = %w[add config commit diff fetch log pull push show ls-files grep]
16
+
17
+ class App
18
+
19
+ # bin wrapper option parser object
20
+ attr_accessor :option_parser
21
+
22
+ def initialize(argv=[], configuration={})
23
+ @configuration = configuration
24
+ @options = configuration[:options] || {}
25
+ @argv = argv
26
+ $stdout.sync = true
27
+
28
+ config_filename = configuration[:configuration_filename]
29
+ RepoManager::Logger::Manager.new(config_filename, :logging, configuration)
30
+
31
+ logger.debug "configuration: #{@configuration.inspect}"
32
+ logger.debug "argv: #{@argv.inspect}"
33
+ logger.debug "config file: #{configuration[:configuration_filename]}" if configuration[:configuration_filename]
34
+ end
35
+
36
+ def execute
37
+ begin
38
+
39
+ args = @argv
40
+ if action_argument_required?
41
+ action = @argv.shift
42
+
43
+ # push action back to args if this is a native pass-through command
44
+ if GIT_NATIVE_SUPPORT.include?(action)
45
+ args.unshift(action)
46
+ action = 'git'
47
+ end
48
+
49
+ # special case: actionless tasks
50
+ action = 'task' if action.nil? && @options.include?(:tasks)
51
+
52
+ # special case: `repo sweep:screenshots` is an acceptable task action
53
+ if action && action.match(/[a-zA-Z]+:+/)
54
+ args.unshift(action)
55
+ action = 'task'
56
+ end
57
+
58
+ # special case: `repo help sweep:screenshots` is an acceptable task help action
59
+ if action == 'help' && args.any?
60
+ target = args[0]
61
+ if target.match(/[a-zA-Z]+:+/)
62
+ args.unshift(action)
63
+ action = 'task'
64
+ end
65
+ end
66
+
67
+ unless AVAILABLE_ACTIONS.include?(action)
68
+ if action.nil?
69
+ puts "repo action required"
70
+ else
71
+ puts "repo invalid action: #{action}"
72
+ end
73
+ puts "repo --help for more information"
74
+ exit 1
75
+ end
76
+ logger.debug "execute action: #{action} #{args.join(' ')}"
77
+ klass = Object.const_get('RepoManager').const_get("#{action.capitalize}Action")
78
+ app_action = klass.new(args, @configuration)
79
+ app_action.option_parser = self.option_parser
80
+ result = app_action.execute
81
+ else
82
+ #
83
+ # default action if action_argument_required? is false
84
+ #
85
+ result = 0
86
+ end
87
+
88
+ if result.is_a?(Numeric)
89
+ exit(result)
90
+ else
91
+ # handle all other return types
92
+ exit(result ? 0 : 1)
93
+ end
94
+
95
+ rescue SystemExit => e
96
+ # This is the normal exit point
97
+ logger.debug "repo run system exit: #{e}, status code: #{e.status}"
98
+ exit(e.status)
99
+ rescue Exception => e
100
+ logger.fatal("repo fatal exception: #{e.message}")
101
+ STDERR.puts("repo failed: #{e.message}".red)
102
+ STDERR.puts("Command failed, use '--verbose' for backtrace.") unless @options[:verbose]
103
+ STDERR.puts(e.backtrace.join("\n")) if @options[:verbose]
104
+ exit(1)
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ # true if application requires an action to be specified on the command line
111
+ def action_argument_required?
112
+ !AVAILABLE_ACTIONS.empty?
113
+ end
114
+
115
+ end
116
+ end
@@ -0,0 +1,3 @@
1
+ require 'repo_manager/assets/base_asset'
2
+ require 'repo_manager/assets/app_asset'
3
+ require 'repo_manager/assets/repo_asset'
@@ -0,0 +1,15 @@
1
+ ####################################################
2
+ # The file is was originally cloned from "Basic App"
3
+ # More information on "Basic App" can be found in the
4
+ # "Basic App" repository.
5
+ #
6
+ # See http://github.com/robertwahler
7
+ ####################################################
8
+ module RepoManager
9
+
10
+ # An abstract superclass for basic asset functionality specific to an
11
+ # application implementation. Put application specific code here.
12
+ class AppAsset < BaseAsset
13
+ end
14
+
15
+ end
@@ -0,0 +1,67 @@
1
+ module RepoManager
2
+
3
+ module AssetAccessors
4
+
5
+ # Given an array, create accessors
6
+ # NOTE: This is similar to using method_missing with a whitelist
7
+ #
8
+ # @return [void]
9
+ def create_accessors(*attrs)
10
+ return unless attrs
11
+ raise ArgumentError, "Expected 'user_attributes' to be an array" unless attrs.is_a? Array
12
+
13
+ # Define each of the attributes
14
+ attrs.flatten.each do |attr|
15
+ create_accessor(attr)
16
+ end
17
+ end
18
+
19
+ def create_accessor(attr)
20
+ create_reader(attr)
21
+ create_writer(attr)
22
+ end
23
+
24
+ def create_reader(attr)
25
+ return unless attr
26
+
27
+ method = "#{attr}".to_sym
28
+
29
+ if self.kind_of? RepoManager::BaseAsset
30
+ return if self.respond_to? method
31
+
32
+ self.class.send(:define_method, method) do
33
+ render(attributes[method])
34
+ end
35
+ else
36
+ return if respond_to? method
37
+
38
+ define_method(method) do
39
+ render(attributes[method])
40
+ end
41
+ end
42
+ end
43
+
44
+ def create_writer(attr)
45
+ return unless attr
46
+
47
+ method = "#{attr}=".to_sym
48
+
49
+ if self.kind_of? RepoManager::BaseAsset
50
+ return if self.respond_to? method
51
+
52
+ self.class.send(:define_method, method) do |value|
53
+ attributes[attr] = value
54
+ end
55
+ else
56
+ return if respond_to? method
57
+
58
+ define_method(method) do |value|
59
+ attributes[attr] = value
60
+ end
61
+ end
62
+
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,137 @@
1
+ require 'yaml'
2
+ require 'erb'
3
+ require 'pathname'
4
+ require 'fileutils'
5
+
6
+ module RepoManager
7
+
8
+ # asset_configuration saves just the user data by subtracting out the
9
+ # global hash and writing just the result
10
+ #
11
+ # An asset requires a global template but doesn't require a user datafile.
12
+ #
13
+ # LOADING:
14
+ #
15
+ # Load up the master template YAML file first, evaluate the ERB, save this
16
+ # hash for later use.
17
+ #
18
+ # Copy the results to the asset attributes and then load the user data. The
19
+ # user data doesn't contain ERB.
20
+ #
21
+ # SAVING:
22
+ #
23
+ # Compare existing asset attributes to the saved and eval'd master hash and write
24
+ # just the change assets to the user file.
25
+ #
26
+ class AssetConfiguration
27
+
28
+ # user datastore folder, can override parent datastore
29
+ attr_accessor :folder
30
+
31
+ # parent datastore defaults folder, read asset from here first if exists
32
+ attr_accessor :parent
33
+
34
+ attr_reader :asset
35
+
36
+ def initialize(asset)
37
+ #logger.debug "initializing new AssetConfiguration with asset class: #{asset.class.to_s}"
38
+ @asset = asset
39
+ end
40
+
41
+ # Save specific attributes to an asset configuration file. Only the param
42
+ # 'attrs' and the current contents of the config file are saved. Parent
43
+ # asset configurations are not saved.
44
+ #
45
+ def save(attrs=nil)
46
+ raise "a Hash of attributes to save must be specified" unless attrs && attrs.is_a?(Hash)
47
+ raise "folder must be set prior to saving attributes" unless folder
48
+
49
+ # merge attributes to asset that contains parent attributes
50
+ @asset.attributes.merge!(attrs)
51
+
52
+ # load contents of the user folder and merge in attributes passed to save
53
+ # so that we don't save parent attributes
54
+ contents = {}
55
+ if File.exists?(folder)
56
+ contents = load_contents(folder)
57
+ raise "expected contents to be a hash" unless contents.is_a?(Hash)
58
+ end
59
+
60
+ contents = contents.merge!(attrs)
61
+ write_contents(folder, contents)
62
+ end
63
+
64
+ # load an asset from a configuration folder
65
+ def load(ds=nil)
66
+ @folder ||= ds
67
+
68
+ contents = load_contents(folder)
69
+
70
+ # if a global parent folder is defined, load it first
71
+ parent = contents.delete(:parent) || parent
72
+ if parent
73
+ parent_folder = File.join(parent)
74
+ unless Pathname.new(parent_folder).absolute?
75
+ base_folder = File.dirname(folder)
76
+ parent_folder = File.join(base_folder, parent_folder)
77
+ end
78
+
79
+ logger.debug "AssetConfiguration loading parent: #{parent_folder}"
80
+ parent_configuration = RepoManager::AssetConfiguration.new(asset)
81
+
82
+ begin
83
+ parent_configuration.load(parent_folder)
84
+ rescue Exception => e
85
+ logger.warn "AssetConfiguration parent configuration load failed on: '#{parent_folder}' with: '#{e.message}'"
86
+ end
87
+ end
88
+
89
+ # Load all attributes as hash 'attributes' so that merging
90
+ # and adding new attributes doesn't require code changes. Note
91
+ # that the 'parent' setting is not merged to attributes
92
+ @asset.attributes.merge!(contents)
93
+ @asset.create_accessors(@asset.attributes[:user_attributes])
94
+ @asset
95
+ end
96
+
97
+ def to_hash
98
+ result = {}
99
+ result.merge!(:parent => parent.folder) if parent
100
+ result.merge!(:attributes => @asset.attributes)
101
+ result
102
+ end
103
+
104
+ private
105
+
106
+ # load the raw contents from an asset_folder, ignore parents
107
+ #
108
+ # @return [Hash] of the raw text contents
109
+ def load_contents(asset_folder)
110
+ file = File.join(asset_folder, 'asset.conf')
111
+ if File.exists?(file)
112
+ contents = YAML.load(ERB.new(File.open(file, "rb").read).result(@asset.get_binding))
113
+ if contents && contents.is_a?(Hash)
114
+ contents.recursively_symbolize_keys!
115
+ else
116
+ {}
117
+ end
118
+ else
119
+ {}
120
+ end
121
+ end
122
+
123
+ # write raw contents to an asset_folder
124
+ def write_contents(asset_folder, contents)
125
+ contents.recursively_stringify_keys!
126
+
127
+ FileUtils.mkdir(asset_folder) unless File.exists?(asset_folder)
128
+ filename = File.join(asset_folder, 'asset.conf')
129
+
130
+ #TODO, use "wb" and write CRLF on Windows
131
+ File.open(filename, "w") do |f|
132
+ f.write(contents.to_conf)
133
+ end
134
+ end
135
+
136
+ end
137
+ end
@@ -0,0 +1,72 @@
1
+ ####################################################
2
+ # The file is was originally cloned from "Basic App"
3
+ # More information on "Basic App" can be found in the
4
+ # "Basic App" repository.
5
+ #
6
+ # See http://github.com/robertwahler
7
+ ####################################################
8
+
9
+ require 'pathname'
10
+ require 'fileutils'
11
+
12
+ module RepoManager
13
+
14
+ class AssetManager
15
+
16
+ # @raise [Exception] unless asset_options contains base_folder or :assets if an absolute path
17
+ #
18
+ # @return [Array] of Asset
19
+ def assets(asset_options={})
20
+ logger.debug "asset_options: #{asset_options.inspect}"
21
+ # type of asset to create, used to guess the asset_folder name
22
+ type = asset_options[:type] || :app_asset
23
+
24
+ assets = []
25
+ filters = asset_options[:filter] || ['.*']
26
+ match_count = 0
27
+ logger.debug "generating assets array with filter array: #{filters.join(',')}"
28
+
29
+ assets_folder = asset_options[:assets_folder] || "assets"
30
+ pattern = File.join(assets_folder, '*/')
31
+ logger.debug "reading from asset type: '#{type}' from assets_folder:'#{assets_folder}' "
32
+
33
+ # asset folder can be relative to main config file
34
+ unless Pathname.new(pattern).absolute?
35
+ # base_folder is determined from the configuration file
36
+ # location, if it is not set, then the configuration file wasn't not found
37
+ raise "configuration file not found" unless asset_options[:base_folder]
38
+ base_folder = asset_options[:base_folder]
39
+ pattern = File.expand_path(File.join(base_folder, pattern))
40
+ end
41
+ logger.debug "asset glob pattern: #{pattern}"
42
+ folders = Dir.glob(pattern)
43
+ logger.debug "user assets folder is empty: #{pattern}" if folders.empty?
44
+
45
+ folders.sort.each do |folder|
46
+ folder_basename = Pathname.new(folder).basename.to_s
47
+ #logger.debug "matching folder: #{folder} using basename: #{folder_basename}"
48
+ if filters.find {|filter| matches?(folder_basename, filter, asset_options)}
49
+ logger.debug "match found for: #{folder_basename}"
50
+ match_count += 1
51
+ asset = RepoManager::AppAsset.create(type, folder, {})
52
+ assets << asset
53
+ break if ((asset_options[:match] == 'FIRST') || (asset_options[:match] == 'EXACT'))
54
+ raise "match mode = ONE, multiple matching assets found filter" if (asset_options[:match] == 'ONE' && match_count > 1)
55
+ end
56
+ end
57
+
58
+ assets
59
+ end
60
+
61
+ private
62
+
63
+ def matches?(str, filter, match_options={})
64
+ if (match_options[:match] == 'EXACT')
65
+ str == filter
66
+ else
67
+ str.match(/#{filter}/)
68
+ end
69
+ end
70
+ end
71
+
72
+ end