repo_manager 0.7.1

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