repo_manager 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemfiles +115 -0
- data/.gitattributes +1 -0
- data/.gitignore +7 -0
- data/.rspec +3 -0
- data/.yardopts +11 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +99 -0
- data/Guardfile +63 -0
- data/HISTORY.markdown +12 -0
- data/LICENSE +20 -0
- data/README.markdown +192 -0
- data/Rakefile +94 -0
- data/TODO.markdown +15 -0
- data/VERSION +1 -0
- data/bin/repo +151 -0
- data/cucumber.yml +28 -0
- data/examples/pc_saved_game_backup/.gitignore +2 -0
- data/examples/pc_saved_game_backup/INSTALL.markdown +420 -0
- data/examples/pc_saved_game_backup/README.markdown +108 -0
- data/examples/pc_saved_game_backup/remote/.gitignore +2 -0
- data/examples/pc_saved_game_backup/repo_manager/Gemfile +12 -0
- data/examples/pc_saved_game_backup/repo_manager/Gemfile.lock +66 -0
- data/examples/pc_saved_game_backup/repo_manager/assets/.gitignore +2 -0
- data/examples/pc_saved_game_backup/repo_manager/features/support/aruba.rb +15 -0
- data/examples/pc_saved_game_backup/repo_manager/features/support/env.rb +11 -0
- data/examples/pc_saved_game_backup/repo_manager/features/support/steps.rb +3 -0
- data/examples/pc_saved_game_backup/repo_manager/features/tasks/update.feature +144 -0
- data/examples/pc_saved_game_backup/repo_manager/global/default/asset.conf +2 -0
- data/examples/pc_saved_game_backup/repo_manager/repo.conf +64 -0
- data/examples/pc_saved_game_backup/repo_manager/tasks/.gitignore +0 -0
- data/examples/pc_saved_game_backup/repo_manager/tasks/remote.rb +57 -0
- data/examples/pc_saved_game_backup/repo_manager/tasks/update.rb +65 -0
- data/examples/pc_saved_game_backup/saved_games/hearts/save1 +1 -0
- data/examples/pc_saved_game_backup/saved_games/hearts/save2 +1 -0
- data/examples/pc_saved_game_backup/saved_games/mines/my_profile.ini +1 -0
- data/examples/pc_saved_game_backup/saved_games/mines/saves/save1 +1 -0
- data/examples/pc_saved_game_backup/saved_games/mines/saves/save2 +1 -0
- data/features/actions/git.feature +296 -0
- data/features/actions/help.feature +53 -0
- data/features/actions/list.feature +624 -0
- data/features/actions/path.feature +195 -0
- data/features/actions/status.feature +261 -0
- data/features/actions/task.feature +127 -0
- data/features/assets/configuration.feature +204 -0
- data/features/assets/rendering.feature +42 -0
- data/features/assets/user_attributes.feature +98 -0
- data/features/bin.feature +42 -0
- data/features/logger.feature +218 -0
- data/features/settings.feature +240 -0
- data/features/support/aruba.rb +15 -0
- data/features/support/env.rb +11 -0
- data/features/support/steps.rb +3 -0
- data/features/tasks/add/asset.feature +178 -0
- data/features/tasks/generate/init.feature +56 -0
- data/lib/repo_manager.rb +36 -0
- data/lib/repo_manager/actions.rb +8 -0
- data/lib/repo_manager/actions/action_helper.rb +39 -0
- data/lib/repo_manager/actions/app_action.rb +30 -0
- data/lib/repo_manager/actions/base_action.rb +296 -0
- data/lib/repo_manager/actions/git_action.rb +113 -0
- data/lib/repo_manager/actions/help_action.rb +52 -0
- data/lib/repo_manager/actions/list_action.rb +123 -0
- data/lib/repo_manager/actions/path_action.rb +22 -0
- data/lib/repo_manager/actions/status_action.rb +192 -0
- data/lib/repo_manager/actions/task_action.rb +71 -0
- data/lib/repo_manager/app.rb +116 -0
- data/lib/repo_manager/assets.rb +3 -0
- data/lib/repo_manager/assets/app_asset.rb +15 -0
- data/lib/repo_manager/assets/asset_accessors.rb +67 -0
- data/lib/repo_manager/assets/asset_configuration.rb +137 -0
- data/lib/repo_manager/assets/asset_manager.rb +72 -0
- data/lib/repo_manager/assets/base_asset.rb +199 -0
- data/lib/repo_manager/assets/repo_asset.rb +30 -0
- data/lib/repo_manager/core.rb +2 -0
- data/lib/repo_manager/core/array.rb +21 -0
- data/lib/repo_manager/core/hash.rb +83 -0
- data/lib/repo_manager/errors.rb +10 -0
- data/lib/repo_manager/extensions/hash.rb +86 -0
- data/lib/repo_manager/git.rb +2 -0
- data/lib/repo_manager/git/lib.rb +69 -0
- data/lib/repo_manager/git/status.rb +196 -0
- data/lib/repo_manager/logger.rb +39 -0
- data/lib/repo_manager/settings.rb +98 -0
- data/lib/repo_manager/tasks.rb +3 -0
- data/lib/repo_manager/tasks/add/asset.rb +213 -0
- data/lib/repo_manager/tasks/generate/init.rb +42 -0
- data/lib/repo_manager/tasks/generate/templates/config/repo.conf.tt +61 -0
- data/lib/repo_manager/tasks/generate/templates/init/assets/.gitignore +0 -0
- data/lib/repo_manager/tasks/generate/templates/init/global/default/asset.conf +2 -0
- data/lib/repo_manager/tasks/generate/templates/init/tasks/.gitignore +0 -0
- data/lib/repo_manager/tasks/task_manager.rb +166 -0
- data/lib/repo_manager/tasks/thor_helper.rb +29 -0
- data/lib/repo_manager/test/asset_steps.rb +19 -0
- data/lib/repo_manager/test/base_steps.rb +152 -0
- data/lib/repo_manager/test/repo_api.rb +41 -0
- data/lib/repo_manager/test/repo_steps.rb +83 -0
- data/lib/repo_manager/test/test_api.rb +88 -0
- data/lib/repo_manager/views.rb +2 -0
- data/lib/repo_manager/views/app_view.rb +15 -0
- data/lib/repo_manager/views/base_view.rb +137 -0
- data/lib/repo_manager/views/templates/css/basic.css +26 -0
- data/lib/repo_manager/views/templates/default.erb +40 -0
- data/lib/repo_manager/views/templates/default.slim +37 -0
- data/lib/repo_manager/views/view_helper.rb +55 -0
- data/repo_manager.gemspec +75 -0
- data/spec/basic_app/actions/action_helper_spec.rb +54 -0
- data/spec/basic_app/assets/base_asset_spec.rb +210 -0
- data/spec/basic_app/core_spec.rb +78 -0
- data/spec/basic_app/settings_spec.rb +64 -0
- data/spec/basic_app/views/view_helper_spec.rb +28 -0
- data/spec/basic_gem/aruba_helper_spec.rb +33 -0
- data/spec/basic_gem/basic_gem_spec.rb +84 -0
- data/spec/basic_gem/gemspec_spec.rb +68 -0
- data/spec/repo_manager/git_spec.rb +31 -0
- data/spec/spec_helper.rb +25 -0
- 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,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
|
+
|
data/lib/repo_manager.rb
ADDED
@@ -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
|