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