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,15 @@
1
+ require 'aruba/api'
2
+ require 'fileutils'
3
+
4
+ APP_BIN_PATH = File.join(FileUtils.pwd, 'bin', 'repo')
5
+
6
+ module Aruba
7
+ module Api
8
+
9
+ # override aruba avoid 'current_ruby' call and make sure
10
+ # that binary run on Win32 without the binstubs
11
+ def detect_ruby(cmd)
12
+ cmd = cmd.gsub(/^repo/, "ruby -S #{APP_BIN_PATH}")
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ require 'repo_manager'
2
+ require 'aruba/cucumber'
3
+ require 'rspec/expectations'
4
+
5
+ Before do
6
+ @aruba_timeout_seconds = 10
7
+ end
8
+
9
+ Before('@slow_process') do
10
+ @aruba_io_wait_seconds = 2
11
+ end
@@ -0,0 +1,3 @@
1
+ require 'repo_manager/test/base_steps'
2
+ require 'repo_manager/test/asset_steps'
3
+ require 'repo_manager/test/repo_steps'
@@ -0,0 +1,178 @@
1
+ @announce
2
+ Feature: Task to generate asset configurations
3
+
4
+ Generate config files automatically by searching each top level folder
5
+ contained in the given command line FOLDER. If a '.git' folder exists,
6
+ then that top level folder will be used to generate a new config file.
7
+
8
+ Help:
9
+
10
+ repo help add:asset
11
+
12
+ repo help add:assets
13
+
14
+ USAGE :
15
+
16
+ repo add:asset WORKING_FOLDER
17
+
18
+ repo add:assets TOP_LEVEL_FOLDER
19
+
20
+ Options:
21
+ -r, [--refresh] # Refresh existing blank attributes
22
+ -f, [--filter=one two three] # List of regex folder name filters
23
+
24
+ Runtime options:
25
+ -s, [--skip] # Skip files that already exist
26
+ -q, [--quiet] # Suppress status output
27
+ -p, [--pretend] # Run but do not make any changes
28
+ -f, [--force] # Overwrite files that already exist
29
+
30
+ Examples
31
+
32
+ cond add:assets c:/users/robert/documents/
33
+ cond add:assets ~/workspace/delphi
34
+ cond add:assets ~/workspace --filter guard-*,repo_manager-*
35
+
36
+ General Notes:
37
+
38
+ * task is not recursive and only looks for .git folder in direct children of the top level folder
39
+ * task will skip existing asset names and existing asset paths unless using the '--refresh' switch
40
+ * add '.condenser' file to application path to have Condenser always skip this application
41
+
42
+ Example output (~/repo_manager/assets/asset.conf):
43
+
44
+ ---
45
+ path: some/path/my_repo_name
46
+
47
+
48
+ Background: Test repositories and a valid config file
49
+ Given a repo in folder "workspace/repo1_path" with the following:
50
+ | filename | status | content |
51
+ | .gitignore | C | |
52
+ And a repo in folder "workspace/repo2_path" with the following:
53
+ | filename | status | content |
54
+ | .gitignore | C | |
55
+ And a directory named "workspace/not_a_repo"
56
+
57
+
58
+ Scenario: Point at a top level folder that contains two repos and on non repo folder
59
+ Given a file named "repo.conf" with:
60
+ """
61
+ ---
62
+ folders:
63
+ assets : assets
64
+ """
65
+ And a directory named "assets"
66
+ When I run `repo add:assets workspace` interactively
67
+ When I type "y"
68
+ Then the exit status should be 0
69
+ And the output should contain:
70
+ """
71
+ Found 2 asset(s)
72
+ """
73
+ And the file "assets/repo1_path/asset.conf" should match:
74
+ """
75
+ path: .*/workspace/repo1_path
76
+ """
77
+ And the file "assets/repo2_path/asset.conf" should match:
78
+ """
79
+ path: .*/workspace/repo2_path
80
+ """
81
+
82
+ Scenario: Point at a single working folder
83
+ Given a file named "repo.conf" with:
84
+ """
85
+ ---
86
+ folders:
87
+ assets : assets
88
+ """
89
+ And a directory named "assets"
90
+ When I run `repo add:asset workspace/repo1_path` interactively
91
+ When I type "y"
92
+ Then the exit status should be 0
93
+ And the output should contain:
94
+ """
95
+ Found 1 asset(s)
96
+ """
97
+ And the file "assets/repo1_path/asset.conf" should match:
98
+ """
99
+ path: .*/workspace/repo1_path
100
+ """
101
+
102
+ Scenario: Point at a single working folder relative to repo.conf
103
+ Given a file named "repo_manager/repo.conf" with:
104
+ """
105
+ ---
106
+ folders:
107
+ assets : assets
108
+ """
109
+ And a directory named "repo_manager/assets"
110
+ When I run `repo add:asset workspace/repo1_path` interactively
111
+ When I type "y"
112
+ Then the exit status should be 0
113
+ And the output should contain:
114
+ """
115
+ Found 1 asset(s)
116
+ """
117
+ And the file "repo_manager/assets/repo1_path/asset.conf" should match:
118
+ """
119
+ path: .*/workspace/repo1_path
120
+ """
121
+
122
+ Scenario: Point at an invalid working folder
123
+ Given a file named "repo.conf" with:
124
+ """
125
+ ---
126
+ folders:
127
+ assets : assets
128
+ """
129
+ And a directory named "assets"
130
+ When I run `repo add:asset workspace/not_a_repo` interactively
131
+ Then the exit status should be 1
132
+ And the output should not contain:
133
+ """
134
+ Found 1 asset(s)
135
+ """
136
+ And the output should contain:
137
+ """
138
+ unable to find '.git' folder
139
+ """
140
+
141
+ Scenario: Point at a single working folder and give it a non-default name
142
+ Given a file named "repo.conf" with:
143
+ """
144
+ ---
145
+ folders:
146
+ assets : assets
147
+ """
148
+ And a directory named "assets"
149
+ When I run `repo add:asset workspace/repo1_path --name=repo1` interactively
150
+ When I type "y"
151
+ Then the exit status should be 0
152
+ And the output should contain:
153
+ """
154
+ Found 1 asset(s)
155
+ """
156
+ And the file "assets/repo1/asset.conf" should match:
157
+ """
158
+ path: .*/workspace/repo1_path
159
+ """
160
+
161
+ Scenario: Attempting to add an asset that exists under a different name
162
+ Given a file named "repo.conf" with:
163
+ """
164
+ ---
165
+ folders:
166
+ assets : assets
167
+ """
168
+ And a directory named "assets"
169
+ And the folder "assets" with the following asset configurations:
170
+ | name | path |
171
+ | repo1_path | workspace/repo1_path |
172
+ When I run `repo add:asset workspace/repo1_path --name repo1` interactively
173
+ When I type "y"
174
+ Then the exit status should be 1
175
+ And its output should contain:
176
+ """
177
+ asset already exists under a different name
178
+ """
@@ -0,0 +1,56 @@
1
+ @announce
2
+ Feature: Generate init task
3
+
4
+ End-user generation of a repo_manager configuration
5
+
6
+ Example commands:
7
+
8
+ repo task generate:init .repo_manager
9
+ repo task generate:init .
10
+ repo task generate:init repo_manager --force --verbose
11
+
12
+ Scenario: Specify path on the command line
13
+ When I run `repo task generate:init nodefault --no-config --verbose`
14
+ Then the exit status should be 0
15
+ Then the output should contain:
16
+ """
17
+ creating initial file structure
18
+ """
19
+ And the following files should exist:
20
+ | nodefault/assets/.gitignore |
21
+ | nodefault/global/default/asset.conf |
22
+ | nodefault/tasks/.gitignore |
23
+
24
+ Scenario: Path not specified
25
+ When I run `repo task generate:init --no-config`
26
+ Then the exit status should be 1
27
+ Then the output should contain:
28
+ """
29
+ repo init requires at least 1 argument
30
+ """
31
+
32
+ Scenario: Config file not found
33
+ When I run `repo task generate:init . --config=BadConfig.conf`
34
+ Then the exit status should be 1
35
+ Then the output should contain:
36
+ """
37
+ config file not found
38
+ """
39
+
40
+ Scenario: A file exists at destination and is not overwritten when answering 'No'
41
+ Given a file named ".gitignore" with:
42
+ """
43
+ .my.file.3234134
44
+ """
45
+ When I run `repo task generate:init . --no-config` interactively
46
+ When I type "n"
47
+ Then the exit status should be 0
48
+ And the following files should exist:
49
+ | .gitignore |
50
+ And the file ".gitignore" should contain:
51
+ """
52
+ .my.file.3234134
53
+ """
54
+ And the following files should exist:
55
+ | assets/.gitignore |
56
+
@@ -0,0 +1,36 @@
1
+ # require all files here
2
+ require 'rbconfig'
3
+ require 'repo_manager/core'
4
+ require 'repo_manager/errors'
5
+ require 'repo_manager/assets'
6
+ require 'repo_manager/views'
7
+ require 'repo_manager/actions'
8
+ require 'repo_manager/git'
9
+ require 'repo_manager/app'
10
+ require 'repo_manager/settings'
11
+ require 'repo_manager/logger'
12
+
13
+
14
+ # Master namespace
15
+ module RepoManager
16
+
17
+ # Contents of the VERSION file
18
+ #
19
+ # Example format: 0.0.1
20
+ #
21
+ # @return [String] the contents of the version file in #.#.# format
22
+ def self.version
23
+ version_info_file = File.join(File.dirname(__FILE__), *%w[.. VERSION])
24
+ File.open(version_info_file, "r") do |f|
25
+ f.read.strip
26
+ end
27
+ end
28
+
29
+ # Platform constants
30
+ unless defined?(RepoManager::WINDOWS)
31
+ WINDOWS = RbConfig::CONFIG['host_os'] =~ /mswin|mingw/i
32
+ CYGWIN = RbConfig::CONFIG['host_os'] =~ /cygwin/i
33
+ end
34
+
35
+ end
36
+
@@ -0,0 +1,8 @@
1
+ require 'repo_manager/actions/base_action'
2
+ require 'repo_manager/actions/app_action'
3
+ require 'repo_manager/actions/help_action'
4
+ require 'repo_manager/actions/list_action'
5
+ require 'repo_manager/actions/task_action'
6
+ require 'repo_manager/actions/path_action'
7
+ require 'repo_manager/actions/git_action'
8
+ require 'repo_manager/actions/status_action'
@@ -0,0 +1,39 @@
1
+ require 'pathname'
2
+ require 'rbconfig'
3
+ require 'fileutils'
4
+
5
+ module RepoManager
6
+ module ActionHelper
7
+
8
+ def shell_quote(string)
9
+ return "" if string.nil? or string.empty?
10
+ if windows?
11
+ %{"#{string}"}
12
+ else
13
+ string.split("'").map{|m| "'#{m}'" }.join("\\'")
14
+ end
15
+ end
16
+
17
+ def windows?
18
+ RbConfig::CONFIG['host_os'] =~ /mswin|mingw/i
19
+ end
20
+
21
+ # @return[String] the relative path from the CWD
22
+ def relative_path(path)
23
+ return unless path
24
+
25
+ path = Pathname.new(File.expand_path(path, FileUtils.pwd))
26
+ cwd = Pathname.new(FileUtils.pwd)
27
+
28
+ if windows?
29
+ # c:/home D:/path/here will faile with ArgumentError: different prefix
30
+ return path.to_s if path.to_s.capitalize[0] != cwd.to_s.capitalize[0]
31
+ end
32
+
33
+ path = path.relative_path_from(cwd)
34
+ path = "./#{path}" unless path.absolute? || path.to_s.match(/^\./)
35
+ path.to_s
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,30 @@
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 action functionality specific to an
11
+ # application implementation. Put application specific code here.
12
+ class AppAction < BaseAction
13
+
14
+ # Used by asset factory to create assets. Override in app_action.rb or a
15
+ # descendant to set the class to be instantiated by by the AssetManager.
16
+ #
17
+ # @return [Symbol] asset type
18
+ def asset_type
19
+ :repo_asset
20
+ end
21
+
22
+ # alias for items/assets
23
+ #
24
+ # @return [Array] of repos
25
+ def repos
26
+ items
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,296 @@
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 'repo_manager/assets/asset_manager'
10
+
11
+ module RepoManager
12
+
13
+ # An abstract superclass for basic action functionality
14
+ class BaseAction
15
+ # main configuration hash
16
+ attr_reader :configuration
17
+
18
+ # options hash, read from configuration hash
19
+ attr_reader :options
20
+
21
+ # args as passed on command line
22
+ attr_reader :args
23
+
24
+ # filename to template for rendering
25
+ attr_accessor :template
26
+
27
+ # filename to write output
28
+ attr_accessor :output
29
+
30
+ # numeric exit code set from return of process method
31
+ attr_reader :exit_code
32
+
33
+ # bin wrapper option parser object
34
+ attr_accessor :option_parser
35
+
36
+ def initialize(args=[], configuration={})
37
+ @configuration = configuration
38
+ @options = configuration[:options] || {}
39
+ @args = args
40
+ logger.debug "initialize with args: #{args.inspect}"
41
+ end
42
+
43
+ # Parse generic action options for all decendant actions
44
+ #
45
+ # @return [OptionParser] for use by decendant actions
46
+ def parse_options(parser_configuration = {})
47
+ raise_on_invalid_option = parser_configuration.has_key?(:raise_on_invalid_option) ? parser_configuration[:raise_on_invalid_option] : true
48
+ parse_base_options = parser_configuration.has_key?(:parse_base_options) ? parser_configuration[:parse_base_options] : true
49
+ logger.debug "base_action parsing args: #{args.inspect}, raise_on_invalid_option: #{raise_on_invalid_option}, parse_base_options: #{parse_base_options}"
50
+
51
+ @option_parser ||= OptionParser.new
52
+
53
+ option_parser.banner = help + "\n\nOptions:"
54
+
55
+ if parse_base_options
56
+ option_parser.on("--template [NAME]", "Use a template to render output. (default=default.slim)") do |t|
57
+ options[:template] = t.nil? ? "default.slim" : t
58
+ @template = options[:template]
59
+ end
60
+
61
+ option_parser.on("--output FILENAME", "Render output directly to a file") do |f|
62
+ options[:output] = f
63
+ @output = options[:output]
64
+ end
65
+
66
+ option_parser.on("--force", "Overwrite file output without prompting") do |f|
67
+ options[:force] = f
68
+ end
69
+
70
+ option_parser.on("-r", "--repos a1,a2,a3", "--asset a1,a2,a3", "--filter a1,a2,a3", Array, "List of regex asset name filters") do |list|
71
+ options[:filter] = list
72
+ end
73
+
74
+ # NOTE: OptionParser will add short options, there is no way to stop '-m' from being the same as '--match'
75
+ option_parser.on("--match [MODE]", "Asset filter match mode. MODE=ALL (default), FIRST, EXACT, or ONE (fails if more than 1 match)") do |m|
76
+ options[:match] = m || "ALL"
77
+ options[:match].upcase!
78
+ unless ["ALL", "FIRST", "EXACT", "ONE"].include?(options[:match])
79
+ puts "invalid match mode option: #{options[:match]}"
80
+ exit 1
81
+ end
82
+ end
83
+ end
84
+
85
+ # allow decendants to add options
86
+ yield option_parser if block_given?
87
+
88
+ # reprocess args for known options, see binary wrapper for first pass
89
+ # (first pass doesn't know about action specific options), find all
90
+ # action options that may come after the action/subcommand (options
91
+ # before subcommand have already been processed) and its args
92
+ logger.debug "(BaseAction) args before reprocessing: #{args.inspect}"
93
+ begin
94
+ option_parser.order!(args)
95
+ rescue OptionParser::InvalidOption => e
96
+ if raise_on_invalid_option
97
+ puts "option error: #{e}"
98
+ puts option_parser
99
+ exit 1
100
+ else
101
+ # parse and consume until we hit an unknown option (not arg), put it back so it
102
+ # can be shifted into the new array
103
+ e.recover(args)
104
+ end
105
+ end
106
+ logger.debug "(BaseAction) args before unknown collection: #{args.inspect}"
107
+
108
+ unknown_args = []
109
+ while unknown_arg = args.shift
110
+ logger.debug "(BaseAction) unknown_arg: #{unknown_arg.inspect}"
111
+ unknown_args << unknown_arg
112
+ begin
113
+ # consume options and stop at an arg
114
+ option_parser.order!(args)
115
+ rescue OptionParser::InvalidOption => e
116
+ if raise_on_invalid_option
117
+ puts "option error: #{e}"
118
+ puts option_parser
119
+ exit 1
120
+ else
121
+ # parse and consume until we hit an unknown option (not arg), put it back so it
122
+ # can be shifted into the new array
123
+ e.recover(args)
124
+ end
125
+ end
126
+ end
127
+ logger.debug "(BaseAction) args after unknown collection: #{args.inspect}"
128
+
129
+ @args = unknown_args.dup
130
+ logger.debug "(BaseAction) args after reprocessing: #{args.inspect}"
131
+
132
+ option_parser
133
+ end
134
+
135
+ def execute
136
+ before_execute
137
+ parse_options
138
+ @exit_code = process
139
+ after_execute
140
+ @exit_code
141
+ end
142
+
143
+ # handle "assets to items" transformations, if any, and write to output
144
+ def process
145
+ write_to_output(render)
146
+ end
147
+
148
+ # TODO: add exception handler and pass return values
149
+ def write_to_output(content)
150
+ if output
151
+ logger.debug "write_to_output called with output : #{output}"
152
+ if overwrite_output?
153
+ logger.debug "writing output to : #{output}"
154
+ File.open(output, 'wb') {|f| f.write(content) }
155
+ else
156
+ logger.info "existing file not overwritten. To overwrite automatically, use the '--force' option."
157
+ end
158
+ else
159
+ logger.debug "base_action writing to STDOUT"
160
+ print content
161
+ end
162
+ return 0
163
+ end
164
+
165
+ # TODO: create items/app_item class with at least the 'name' accessor
166
+ #
167
+ # assets: raw configuration handling system for items
168
+ def assets
169
+ return @assets if @assets
170
+ @assets = AssetManager.new.assets(asset_options)
171
+ end
172
+
173
+ # Used by asset factory to create assets. Override in app_action.rb or a
174
+ # descendant to set the class to be instantiated by by the AssetManager.
175
+ #
176
+ # @return [Symbol] asset type
177
+ def asset_type
178
+ :app_asset
179
+ end
180
+
181
+ # asset options separated from assets to make it easier to override assets
182
+ def asset_options
183
+ # include all base action options
184
+ result = options.dup
185
+
186
+ # anything left on the command line should be filters as all options have
187
+ # been consumed, for pass through options, filters must be ignored by overwritting them
188
+ filters = args.dup
189
+ filters += result[:filter] if result[:filter]
190
+ result = result.merge(:filter => filters) unless filters.empty?
191
+
192
+ # asset type to create
193
+ type = result[:type] || asset_type
194
+ result = result.merge(:type => type)
195
+
196
+ # optional key: :assets_folder, absolute path or relative to config file if :base_folder is specified
197
+ result = result.merge(:assets_folder => configuration[:folders][:assets]) if configuration[:folders]
198
+
199
+ # optional key: :base_folder is the folder that contains the main config file
200
+ result = result.merge(:base_folder => File.dirname(configuration[:configuration_filename])) if configuration[:configuration_filename]
201
+
202
+ result
203
+ end
204
+
205
+ # items to be rendered, defaults to assets, override to suit
206
+ #
207
+ # @return [Array] of items to be rendered
208
+ def items
209
+ assets
210
+ end
211
+
212
+ # Render items result to a string
213
+ #
214
+ # @return [String] suitable for displaying on STDOUT or writing to a file
215
+ def render(view_options=configuration)
216
+ logger.debug "base_action rendering"
217
+ result = ""
218
+ if template
219
+ logger.debug "base_action rendering with template : #{template}"
220
+ view = AppView.new(items, view_options)
221
+ view.template = template
222
+ result = view.render
223
+ else
224
+ items.each_with_index do |item, index|
225
+ result += "\n" unless index == 0
226
+ result += item.name.green + ":\n"
227
+ if item.respond_to?(:attributes)
228
+ attributes = item.attributes.dup
229
+ result += attributes.recursively_stringify_keys!.to_conf.gsub(/\s+$/, '') # strip trailing whitespace from YAML
230
+ result += "\n"
231
+ end
232
+ end
233
+ end
234
+ result
235
+ end
236
+
237
+ # Convert method comments block to help text
238
+ #
239
+ # @return [String] suitable for displaying on STDOUT
240
+ def help(help_options={})
241
+ comment_starting_with = help_options[:comment_starting_with] || ""
242
+ located_in_file = help_options[:located_in_file] || __FILE__
243
+ text = File.read(located_in_file)
244
+
245
+ result = text.match(/(^\s*#\s*#{comment_starting_with}.*)^\s*class .* AppAction/m)
246
+ result = $1
247
+ result = result.gsub(/ @example/, '')
248
+ result = result.gsub(/ @return \[Number\]/, ' Exit code:')
249
+ result = result.gsub(/ @return .*/, '')
250
+ result = result.gsub(/ @see .*$/, '')
251
+
252
+ # strip the leading whitespace, the '#' and space
253
+ result = result.gsub(/^\s*# ?/, '')
254
+
255
+ # strip surrounding whitespace
256
+ result.strip
257
+ end
258
+
259
+ # @return [Boolean] true if output doesn't exist or it is OK to overwrite
260
+ def overwrite_output?
261
+ return true unless File.exists?(output)
262
+
263
+ if options[:force]
264
+ logger.debug "overwriting output with --force option"
265
+ return true
266
+ end
267
+
268
+ unless STDOUT.isatty
269
+ logger.debug "TTY not detected, skipping overwrite prompt"
270
+ return false
271
+ end
272
+
273
+ result = false
274
+ print "File '#{output}' exists. Would you like overwrite? [y/n]: "
275
+ case gets.strip
276
+ when 'Y', 'y', 'yes'
277
+ logger.debug "user answered yes to overwrite prompt"
278
+ result = true
279
+ else
280
+ logger.debug "user answered no to overwrite prompt"
281
+ end
282
+
283
+ result
284
+ end
285
+
286
+ # callbacks
287
+ def before_execute
288
+ logger.debug "callback: before_execute"
289
+ end
290
+
291
+ def after_execute
292
+ logger.debug "callback: after_execute"
293
+ end
294
+
295
+ end
296
+ end