geordi 0.18.0 → 1.0.0
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.
- checksums.yaml +13 -5
- data/.ruby-version +1 -0
- data/README.md +199 -110
- data/Rakefile +40 -0
- data/bin/apache-site +1 -19
- data/bin/cap-all +1 -27
- data/bin/cleanup-directory +1 -11
- data/bin/console-for +1 -12
- data/bin/cuc +1 -2
- data/bin/cuc-show +1 -2
- data/bin/cuc-vnc-setup +1 -114
- data/bin/deploy-to-production +1 -91
- data/bin/dump-for +4 -32
- data/bin/dumple +2 -4
- data/bin/geordi +10 -0
- data/bin/gitpt +1 -2
- data/bin/launchy_browser +9 -3
- data/bin/load-dump +1 -4
- data/bin/migrate-all +1 -13
- data/bin/optimize-png +1 -118
- data/bin/power-rake +1 -7
- data/bin/remove-executable-flags +1 -6
- data/bin/rs +1 -26
- data/bin/run_tests +1 -2
- data/bin/setup-firefox-for-selenium +1 -3
- data/bin/shell-for +1 -8
- data/bin/tests +1 -5
- data/geordi.gemspec +19 -0
- data/lib/geordi/COMMAND_TEMPLATE +29 -0
- data/lib/geordi/capistrano.rb +9 -7
- data/lib/geordi/capistrano_config.rb +66 -0
- data/lib/geordi/cli.rb +28 -0
- data/lib/geordi/commands/all_targets.rb +26 -0
- data/lib/geordi/commands/apache_site.rb +22 -0
- data/lib/geordi/commands/bundle_install.rb +7 -0
- data/lib/geordi/commands/cleanup_directory.rb +16 -0
- data/lib/geordi/commands/commit.rb +171 -0
- data/lib/geordi/commands/console.rb +24 -0
- data/lib/geordi/commands/create_database_yml.rb +18 -0
- data/lib/geordi/commands/create_databases.rb +16 -0
- data/lib/geordi/commands/cucumber.rb +20 -0
- data/lib/geordi/commands/deploy.rb +65 -0
- data/lib/geordi/commands/devserver.rb +14 -0
- data/lib/geordi/commands/dump.rb +63 -0
- data/lib/geordi/commands/migrate.rb +26 -0
- data/lib/geordi/commands/png_optimize.rb +92 -0
- data/lib/geordi/commands/rake.rb +21 -0
- data/lib/geordi/commands/remove_executable_flags.rb +14 -0
- data/lib/geordi/commands/rspec.rb +40 -0
- data/lib/geordi/commands/security_update.rb +56 -0
- data/lib/geordi/commands/setup.rb +33 -0
- data/lib/geordi/commands/setup_firefox_for_selenium.rb +6 -0
- data/lib/geordi/commands/setup_vnc.rb +84 -0
- data/lib/geordi/commands/shell.rb +20 -0
- data/lib/geordi/commands/test.rb +9 -0
- data/lib/geordi/commands/unit.rb +11 -0
- data/lib/geordi/commands/update.rb +33 -0
- data/lib/geordi/commands/version.rb +7 -0
- data/lib/geordi/commands/vnc_show.rb +6 -0
- data/lib/geordi/commands/with_firefox_for_selenium.rb +18 -0
- data/lib/geordi/commands/with_rake.rb +11 -0
- data/lib/geordi/{cuc.rb → cucumber.rb} +28 -39
- data/lib/geordi/dump_loader.rb +44 -70
- data/lib/geordi/firefox_for_selenium.rb +93 -74
- data/lib/geordi/interaction.rb +47 -0
- data/lib/geordi/remote.rb +66 -0
- data/lib/geordi/util.rb +60 -0
- data/lib/geordi/version.rb +1 -1
- data/lib/geordi.rb +1 -0
- metadata +50 -16
- data/bin/install-gems-remotely +0 -18
- data/bin/install-gems-remotely.sh +0 -16
- data/bin/power-deploy +0 -18
- data/bin/remotify-local-branch +0 -12
- data/lib/geordi/gitpt.rb +0 -175
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'capistrano'
|
2
|
+
|
3
|
+
module Geordi
|
4
|
+
class CapistranoConfig
|
5
|
+
|
6
|
+
attr_accessor :root
|
7
|
+
|
8
|
+
def initialize(stage)
|
9
|
+
ENV['BUNDLE_BIN_PATH'] = 'Trick capistrano safeguard in deploy.rb into believing bundler is present by setting this variable.'
|
10
|
+
|
11
|
+
@stage = stage
|
12
|
+
@root = find_project_root!
|
13
|
+
load_capistrano_config
|
14
|
+
end
|
15
|
+
|
16
|
+
def user
|
17
|
+
@capistrano_config.fetch(:user)
|
18
|
+
end
|
19
|
+
|
20
|
+
def servers
|
21
|
+
@capistrano_config.find_servers(:roles => [:app])
|
22
|
+
end
|
23
|
+
|
24
|
+
def primary_server
|
25
|
+
@capistrano_config.find_servers(:roles => [:app], :only => { :primary => true }).first
|
26
|
+
end
|
27
|
+
|
28
|
+
def path
|
29
|
+
@capistrano_config.fetch(:deploy_to) + '/current'
|
30
|
+
end
|
31
|
+
|
32
|
+
def env
|
33
|
+
@capistrano_config.fetch(:rails_env, 'production')
|
34
|
+
end
|
35
|
+
|
36
|
+
def shell
|
37
|
+
@capistrano_config.fetch(:default_shell, 'bash --login')
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def load_capistrano_config
|
44
|
+
config = ::Capistrano::Configuration.new
|
45
|
+
config.load('deploy')
|
46
|
+
config.load('config/deploy')
|
47
|
+
if @stage and @stage != ''
|
48
|
+
config[:stage] = @stage
|
49
|
+
config.find_and_execute_task(@stage)
|
50
|
+
end
|
51
|
+
|
52
|
+
@capistrano_config = config
|
53
|
+
end
|
54
|
+
|
55
|
+
def find_project_root!
|
56
|
+
current = Dir.pwd
|
57
|
+
until (File.exists? 'Capfile')
|
58
|
+
Dir.chdir '..'
|
59
|
+
raise 'Call me from inside a Rails project!' if current == Dir.pwd
|
60
|
+
current = Dir.pwd
|
61
|
+
end
|
62
|
+
current
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
data/lib/geordi/cli.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'bundler'
|
3
|
+
require 'geordi/interaction'
|
4
|
+
require 'geordi/util'
|
5
|
+
|
6
|
+
module Geordi
|
7
|
+
class CLI < Thor
|
8
|
+
include Geordi::Interaction
|
9
|
+
|
10
|
+
# load all tasks defined in lib/geordi/commands
|
11
|
+
Dir[File.expand_path '../commands/*.rb', __FILE__].each do |file|
|
12
|
+
class_eval File.read(file), file
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def file_containing?(file, regex)
|
18
|
+
File.exists?(file) and File.read(file).scan(regex).any?
|
19
|
+
end
|
20
|
+
|
21
|
+
# fix weird implementation of #invoke
|
22
|
+
def invoke_cmd(name, *args)
|
23
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
24
|
+
invoke(name, args, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
desc 'all-targets COMMAND', 'Run a capistrano command on all deploy targets'
|
2
|
+
long_desc <<-LONGDESC
|
3
|
+
Example: `geordi all-targets deploy`
|
4
|
+
LONGDESC
|
5
|
+
|
6
|
+
def all_targets(*args)
|
7
|
+
targets = Dir['config/deploy/*.rb'].map { |file| File.basename(file, '.rb') }.sort
|
8
|
+
|
9
|
+
note 'Found the following deploy targets:'
|
10
|
+
puts targets
|
11
|
+
puts
|
12
|
+
|
13
|
+
print 'Continue? [yN] '
|
14
|
+
exit unless $stdin.gets =~ /[yes]+/
|
15
|
+
|
16
|
+
targets << nil if targets.empty? # default target
|
17
|
+
targets.each do |stage|
|
18
|
+
announce 'Target: ' + (stage || '(default)')
|
19
|
+
|
20
|
+
command = "bundle exec cap #{stage} " + args.join(' ')
|
21
|
+
note_cmd command
|
22
|
+
|
23
|
+
Util.system!(command, :fail_message => 'Capistrano failed. Have a look!')
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
desc 'apache-site VIRTUAL_HOST', 'Enable the given virtual host, disabling all others'
|
2
|
+
def apache_site(*args)
|
3
|
+
site = args.shift
|
4
|
+
old_cwd = Dir.pwd
|
5
|
+
Dir.chdir '/etc/apache2/sites-available/'
|
6
|
+
|
7
|
+
# show available sites if no site was passed as argument
|
8
|
+
unless site
|
9
|
+
puts 'ERROR: Argument site is missing.'
|
10
|
+
puts 'Please call: apache-site my-site'
|
11
|
+
puts
|
12
|
+
puts 'Available sites:'
|
13
|
+
Dir.new(".").each do |file|
|
14
|
+
puts "- #{file}" if file != '.' && file != '..'
|
15
|
+
end
|
16
|
+
exit
|
17
|
+
end
|
18
|
+
|
19
|
+
has_default = File.exists?('default')
|
20
|
+
exec "sudo a2dissite \*; sudo a2ensite #{"default " if has_default}#{site} && sudo apache2ctl restart"
|
21
|
+
Dir.chdir old_cwd
|
22
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
desc 'cleanup-directory', 'Remove unneeded files'
|
2
|
+
def cleanup_directory
|
3
|
+
|
4
|
+
announce 'Removing tempfiles'
|
5
|
+
for pattern in %w[ webrat-* capybara-* tmp/webrat-* tmp/capybara-* tmp/rtex/* log/*.log ]
|
6
|
+
note pattern
|
7
|
+
puts `rm -vfR #{pattern}`
|
8
|
+
end
|
9
|
+
|
10
|
+
announce 'Finding recursively and removing backup files'
|
11
|
+
for pattern in %w[ *~ ]
|
12
|
+
note pattern
|
13
|
+
`find . -name #{pattern} -exec rm {} ';'`
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
desc 'commit', 'Commit using a story title from Pivotal Tracker'
|
2
|
+
def commit
|
3
|
+
Gitpt.new.run
|
4
|
+
end
|
5
|
+
|
6
|
+
class Gitpt
|
7
|
+
include Geordi::Interaction
|
8
|
+
require 'yaml'
|
9
|
+
require 'highline'
|
10
|
+
require 'pivotal-tracker'
|
11
|
+
|
12
|
+
attr_reader :token, :initials, :settings_file, :deprecated_token_file,
|
13
|
+
:highline, :applicable_stories, :memberships
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@highline = HighLine.new
|
17
|
+
@settings_file = File.join(ENV['HOME'], '.gitpt')
|
18
|
+
@deprecated_token_file = File.join(ENV['HOME'], '.pt_token')
|
19
|
+
load_settings
|
20
|
+
settings_were_invalid = (not settings_valid?)
|
21
|
+
|
22
|
+
hello unless settings_valid?
|
23
|
+
request_settings while not settings_valid?
|
24
|
+
stored if settings_were_invalid
|
25
|
+
|
26
|
+
PivotalTracker::Client.use_ssl = true
|
27
|
+
PivotalTracker::Client.token = token
|
28
|
+
end
|
29
|
+
|
30
|
+
def settings_valid?
|
31
|
+
token and token.size > 10
|
32
|
+
end
|
33
|
+
|
34
|
+
def bold(string)
|
35
|
+
HighLine::BOLD + string + HighLine::RESET
|
36
|
+
end
|
37
|
+
|
38
|
+
def highlight(string)
|
39
|
+
bold HighLine::BLUE + string
|
40
|
+
end
|
41
|
+
|
42
|
+
def hello
|
43
|
+
highline.say HighLine::RESET
|
44
|
+
highline.say "Welcome to #{bold 'gitpt'}.\n\n"
|
45
|
+
end
|
46
|
+
|
47
|
+
def loading(message, &block)
|
48
|
+
print message
|
49
|
+
STDOUT.flush
|
50
|
+
yield
|
51
|
+
print "\r" + ' ' * message.size + "\r" # Remove loading message
|
52
|
+
STDOUT.flush
|
53
|
+
end
|
54
|
+
|
55
|
+
def stored
|
56
|
+
highline.say left(<<-MESSAGE)
|
57
|
+
Thank you. Your settings have been stored at #{highlight @settings_file}
|
58
|
+
You may remove that file for the wizard to reappear.
|
59
|
+
|
60
|
+
----------------------------------------------------
|
61
|
+
|
62
|
+
MESSAGE
|
63
|
+
end
|
64
|
+
|
65
|
+
def request_settings
|
66
|
+
highline.say highlight('Your settings are missing or invalid.')
|
67
|
+
highline.say "Please configure your Pivotal Tracker access.\n\n"
|
68
|
+
token = highline.ask bold("Your API key:") + " "
|
69
|
+
initials = highline.ask bold("Your PT initials") + " (optional, used for highlighting your stories): "
|
70
|
+
highline.say "\n"
|
71
|
+
|
72
|
+
settings = { :token => token, :initials => initials }
|
73
|
+
File.open settings_file, 'w' do |file|
|
74
|
+
file.write settings.to_yaml
|
75
|
+
end
|
76
|
+
load_settings
|
77
|
+
end
|
78
|
+
|
79
|
+
def load_settings
|
80
|
+
if File.exists? settings_file
|
81
|
+
settings = YAML.load(File.read settings_file)
|
82
|
+
@initials = settings[:initials]
|
83
|
+
@token = settings[:token]
|
84
|
+
else
|
85
|
+
if File.exists?(deprecated_token_file)
|
86
|
+
highline.say left(<<-MESSAGE)
|
87
|
+
#{HighLine::YELLOW}You are still using #{highlight(deprecated_token_file) + HighLine::YELLOW} which will be deprecated in a future version.
|
88
|
+
Please migrate your settings to ~/.gitpt or remove #{deprecated_token_file} for the wizard to cast magic.
|
89
|
+
MESSAGE
|
90
|
+
@token = File.read(deprecated_token_file)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def load_projects
|
96
|
+
project_id_filename = '.pt_project_id'
|
97
|
+
if File.exists?(project_id_filename)
|
98
|
+
project_ids = File.read('.pt_project_id').split(/[\s]+/).map(&:to_i)
|
99
|
+
end
|
100
|
+
|
101
|
+
unless project_ids and project_ids.size > 0
|
102
|
+
highline.say left(<<-MESSAGE)
|
103
|
+
Sorry, I could not find a project ID in #{highlight project_id_filename} :(
|
104
|
+
|
105
|
+
Please put at least one Pivotal Tracker project id into #{project_id_filename} in this directory.
|
106
|
+
You may add multiple IDs, separated using white space.
|
107
|
+
MESSAGE
|
108
|
+
exit 1
|
109
|
+
end
|
110
|
+
|
111
|
+
loading 'Connecting to Pivotal Tracker...' do
|
112
|
+
projects = project_ids.collect do |project_id|
|
113
|
+
PivotalTracker::Project.find(project_id)
|
114
|
+
end
|
115
|
+
|
116
|
+
@memberships = projects.collect(&:memberships).map(&:all).flatten
|
117
|
+
|
118
|
+
@applicable_stories = projects.collect do |project|
|
119
|
+
project.stories.all(:state => 'started,finished,rejected')
|
120
|
+
end.flatten
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def choose_story
|
125
|
+
selected_story = nil
|
126
|
+
|
127
|
+
highline.choose do |menu|
|
128
|
+
menu.header = "Choose a story"
|
129
|
+
applicable_stories.each do |story|
|
130
|
+
owner_name = story.owned_by
|
131
|
+
owner = if owner_name
|
132
|
+
owners = memberships.select{|member| member.name == owner_name}
|
133
|
+
owners.first ? owners.first.initials : '?'
|
134
|
+
else
|
135
|
+
'?'
|
136
|
+
end
|
137
|
+
|
138
|
+
state = story.current_state
|
139
|
+
if state == 'started'
|
140
|
+
state = HighLine::GREEN + state + HighLine::RESET
|
141
|
+
elsif state != 'finished'
|
142
|
+
state = HighLine::RED + state + HighLine::RESET
|
143
|
+
end
|
144
|
+
state += HighLine::BOLD if owner == initials
|
145
|
+
|
146
|
+
label = "(#{owner}, #{state}) #{story.name}"
|
147
|
+
label = bold(label) if owner == initials
|
148
|
+
menu.choice(label) { selected_story = story }
|
149
|
+
end
|
150
|
+
menu.hidden ''
|
151
|
+
end
|
152
|
+
|
153
|
+
if selected_story
|
154
|
+
message = highline.ask("\nAdd an optional message")
|
155
|
+
highline.say message
|
156
|
+
|
157
|
+
commit_message = "[##{selected_story.id}] #{selected_story.name}"
|
158
|
+
if message.strip != ''
|
159
|
+
commit_message << ' - '<< message.strip
|
160
|
+
end
|
161
|
+
|
162
|
+
exec('git', 'commit', '-m', commit_message)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def run
|
167
|
+
load_projects
|
168
|
+
choose_story
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
desc 'console [TARGET]', 'Open a Rails console locally or on a Capistrano deploy target'
|
2
|
+
long_desc <<-LONGDESC
|
3
|
+
Open a local Rails console: `geordi console`
|
4
|
+
|
5
|
+
Open a Rails console on `staging`: `geordi console staging`
|
6
|
+
LONGDESC
|
7
|
+
|
8
|
+
|
9
|
+
option :select_server, :default => false, :type => :boolean, :aliases => '-s'
|
10
|
+
|
11
|
+
def console(target = 'development', *args)
|
12
|
+
require 'geordi/remote'
|
13
|
+
|
14
|
+
if target == 'development'
|
15
|
+
announce 'Opening a local Rails console'
|
16
|
+
|
17
|
+
Util.system! Util.console_command(target)
|
18
|
+
|
19
|
+
else
|
20
|
+
announce 'Opening a Rails console on ' + target
|
21
|
+
|
22
|
+
Geordi::Remote.new(target).console(options)
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
desc 'create-database-yml', '[sic]', :hide => true
|
2
|
+
def create_database_yml
|
3
|
+
real_yml = 'config/database.yml'
|
4
|
+
sample_yml = 'config/database.sample.yml'
|
5
|
+
|
6
|
+
if File.exists?(sample_yml) and not File.exists?(real_yml)
|
7
|
+
announce 'Creating ' + real_yml
|
8
|
+
|
9
|
+
print 'Please enter your DB password: '
|
10
|
+
db_password = STDIN.gets.strip
|
11
|
+
|
12
|
+
sample = File.read(sample_yml)
|
13
|
+
real = sample.gsub(/password:.*$/, "password: #{db_password}")
|
14
|
+
File.open(real_yml, 'w') { |f| f.write(real) }
|
15
|
+
|
16
|
+
note "Created #{real_yml}."
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
desc 'create-databases', 'Create all databases', :hide => true
|
2
|
+
def create_databases
|
3
|
+
invoke_cmd 'create_database_yml'
|
4
|
+
invoke_cmd 'bundle_install'
|
5
|
+
|
6
|
+
announce 'Creating databases'
|
7
|
+
|
8
|
+
if File.exists?('config/database.yml')
|
9
|
+
command = 'bundle exec rake db:create:all'
|
10
|
+
command << ' parallel:create' if file_containing?('Gemfile', /parallel_tests/)
|
11
|
+
|
12
|
+
Util.system! command
|
13
|
+
else
|
14
|
+
puts 'config/database.yml does not exist. Nothing to do.'
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
desc 'cucumber [FILES]', 'Run Cucumber features'
|
2
|
+
long_desc <<-LONGDESC
|
3
|
+
Example: `geordi cucumber features/authentication_feature:3`
|
4
|
+
|
5
|
+
Runs Cucumber as you want: with `bundle exec`, `cucumber_spinner` detection,
|
6
|
+
separate Firefox for Selenium, etc.
|
7
|
+
LONGDESC
|
8
|
+
|
9
|
+
def cucumber(*files)
|
10
|
+
require 'geordi/cucumber'
|
11
|
+
|
12
|
+
invoke_cmd 'bundle_install'
|
13
|
+
|
14
|
+
if File.directory?('features')
|
15
|
+
announce 'Running features'
|
16
|
+
Geordi::Cucumber.new.run(files) or fail 'Features failed.'
|
17
|
+
else
|
18
|
+
note 'Cucumber not employed.'
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
desc 'deploy', 'Guided deployment'
|
2
|
+
def deploy
|
3
|
+
ENV['PAGER'] = 'cat'
|
4
|
+
|
5
|
+
master_branch = prompt('master branch', 'master')
|
6
|
+
production_branch = prompt('production branch', 'production')
|
7
|
+
production_stage = prompt('production capistrano stage', 'production')
|
8
|
+
|
9
|
+
announce "Checking if your #{master_branch} is up to date"
|
10
|
+
|
11
|
+
diff_size = call_or_fail("git fetch && git diff #{master_branch} origin/#{master_branch} | wc -l", true)
|
12
|
+
changes_size = call_or_fail('git status -s | wc -l', true)
|
13
|
+
|
14
|
+
if diff_size == '0' and changes_size == '0'
|
15
|
+
note 'All good.'
|
16
|
+
else
|
17
|
+
fail "Your #{master_branch} is not the same as on origin or holds uncommitted changes. Fix that first."
|
18
|
+
end
|
19
|
+
|
20
|
+
announce "Checking what's on #{production_stage} right now..."
|
21
|
+
|
22
|
+
call_or_fail "git checkout #{production_branch} && git pull"
|
23
|
+
|
24
|
+
announce "You are about to deploy the following commits from #{master_branch} to #{production_branch}:"
|
25
|
+
|
26
|
+
call_or_fail "git log #{production_branch}..#{master_branch} --oneline"
|
27
|
+
|
28
|
+
if prompt('Go ahead with the deployment?', 'n').downcase == 'y'
|
29
|
+
puts
|
30
|
+
capistrano_call = "cap #{production_stage} deploy:migrations"
|
31
|
+
if file_containing?('Gemfile', /capistrano/)
|
32
|
+
capistrano_call = "bundle exec #{capistrano_call}"
|
33
|
+
end
|
34
|
+
call_or_fail("git merge #{master_branch} && git push && #{capistrano_call}")
|
35
|
+
success 'Deployment complete.'
|
36
|
+
else
|
37
|
+
fail 'Deployment cancelled.'
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def call_or_fail(command, return_output = false)
|
45
|
+
note_cmd command
|
46
|
+
if return_output
|
47
|
+
result = `#{command}`.to_s.strip
|
48
|
+
$?.success? or fail "Error while calling #{command}: #{$?}"
|
49
|
+
else
|
50
|
+
result = system(command) or fail "Error while calling #{command}: #{$?}"
|
51
|
+
puts
|
52
|
+
end
|
53
|
+
result
|
54
|
+
end
|
55
|
+
|
56
|
+
def prompt(message, default)
|
57
|
+
print "#{message}"
|
58
|
+
print " [#{default}]" if default
|
59
|
+
print ": "
|
60
|
+
input = $stdin.gets.strip
|
61
|
+
if input.empty? && default
|
62
|
+
input = default
|
63
|
+
end
|
64
|
+
input
|
65
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
desc 'devserver', 'Start a development server'
|
2
|
+
|
3
|
+
option :port, :aliases => '-p', :default => '3000'
|
4
|
+
|
5
|
+
def devserver
|
6
|
+
invoke_cmd 'bundle_install'
|
7
|
+
require 'geordi/util'
|
8
|
+
|
9
|
+
announce 'Booting a development server'
|
10
|
+
note "URL: http://#{File.basename(Dir.pwd)}.vcap.me:#{options.port}"
|
11
|
+
puts
|
12
|
+
|
13
|
+
Util.system! Util.server_command + " -p #{options.port}"
|
14
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
desc 'dump [TARGET]', 'Handle dumps'
|
2
|
+
long_desc <<-DESC
|
3
|
+
When called without arguments, dumps the development database with `dumple`.
|
4
|
+
|
5
|
+
geordi dump
|
6
|
+
|
7
|
+
When called with the `--load` option, sources the specified dump into the
|
8
|
+
development database.
|
9
|
+
|
10
|
+
geordi dump -l tmp/staging.dump
|
11
|
+
|
12
|
+
When called with a capistrano deploy target (e.g. `staging`), remotely dumps
|
13
|
+
the specified target's database and downloads it to `tmp/`.
|
14
|
+
|
15
|
+
geordi dump staging
|
16
|
+
|
17
|
+
When called with a capistrano deploy target and the `--load` option, sources the
|
18
|
+
dump into the development database after downloading it.
|
19
|
+
|
20
|
+
geordi dump staging -l
|
21
|
+
DESC
|
22
|
+
|
23
|
+
option :load, :aliases => ['-l'], :type => :string, :desc => 'Load a dump'
|
24
|
+
option :select_server, :default => false, :type => :boolean, :aliases => '-s'
|
25
|
+
|
26
|
+
def dump(target = nil, *args)
|
27
|
+
require 'geordi/dump_loader'
|
28
|
+
require 'geordi/remote'
|
29
|
+
|
30
|
+
if target.nil?
|
31
|
+
if options.load
|
32
|
+
# validate load option
|
33
|
+
fail 'Missing a dump file.' if options.load == 'load'
|
34
|
+
File.exists?(options.load) or fail 'Could not find the given dump file: ' + options.load
|
35
|
+
|
36
|
+
loader = DumpLoader.new(options.load)
|
37
|
+
|
38
|
+
announce "Sourcing dump into the #{loader.config['database']} db"
|
39
|
+
loader.load
|
40
|
+
|
41
|
+
success "Your #{loader.config['database']} database has now the data of #{options.load}."
|
42
|
+
|
43
|
+
else
|
44
|
+
announce 'Dumping the development database'
|
45
|
+
Util.system! 'dumple development'
|
46
|
+
success 'Successfully dumped the development database.'
|
47
|
+
end
|
48
|
+
|
49
|
+
else
|
50
|
+
announce 'Dumping the database of ' + target
|
51
|
+
dump_path = Geordi::Remote.new(target).dump(options)
|
52
|
+
|
53
|
+
if options.load
|
54
|
+
loader = DumpLoader.new(dump_path)
|
55
|
+
|
56
|
+
announce "Sourcing dump into the #{loader.config['database']} db"
|
57
|
+
loader.load
|
58
|
+
|
59
|
+
success "Your #{loader.config['database']} database has now the data of #{target}."
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
desc 'migrate', 'Migrate all databases'
|
2
|
+
long_desc <<-LONGDESC
|
3
|
+
Example: `geordi migrate`
|
4
|
+
|
5
|
+
If you are using `parallel_tests`, this runs migrations in your development
|
6
|
+
environment and `rake parallel:prepare` afterwards. Otherwise, invokes `geordi rake`
|
7
|
+
with `db:migrate`.
|
8
|
+
LONGDESC
|
9
|
+
|
10
|
+
def migrate
|
11
|
+
invoke_cmd 'bundle_install'
|
12
|
+
announce 'Migrating'
|
13
|
+
|
14
|
+
if File.directory?('db/migrate')
|
15
|
+
if file_containing?('Gemfile', /parallel_tests/)
|
16
|
+
note 'Development and parallel test databases'
|
17
|
+
puts
|
18
|
+
|
19
|
+
Util.system! 'bundle exec rake db:migrate parallel:prepare'
|
20
|
+
else
|
21
|
+
invoke_cmd 'rake', 'db:migrate'
|
22
|
+
end
|
23
|
+
else
|
24
|
+
note 'No migrations directory found.'
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
desc 'png-optimize', 'Optimize .png files'
|
2
|
+
long_desc <<-LONGDESC
|
3
|
+
- Removes color profiles: cHRM, sRGB, gAMA, ICC, etc.
|
4
|
+
- Eliminates unused colors and reduces bit-depth (if possible)
|
5
|
+
- May reduce PNG file size lossless
|
6
|
+
|
7
|
+
Batch-optimize all `*.png` files in a directory:
|
8
|
+
|
9
|
+
geordi png-optimize directory
|
10
|
+
|
11
|
+
Batch-optimize the current directory:
|
12
|
+
|
13
|
+
geordi png-optimize .
|
14
|
+
|
15
|
+
Optimize a single file:
|
16
|
+
|
17
|
+
geordi png-optimize input.png
|
18
|
+
LONGDESC
|
19
|
+
|
20
|
+
def png_optimize(*args)
|
21
|
+
require 'fileutils'
|
22
|
+
|
23
|
+
announce 'Optimizing .png files'
|
24
|
+
|
25
|
+
if `which pngcrush`.strip.empty?
|
26
|
+
fail 'You have to install pngcrush first (sudo apt-get install pngcrush)'
|
27
|
+
end
|
28
|
+
|
29
|
+
po = PngOptimizer.new
|
30
|
+
path = args[0]
|
31
|
+
if File.directory?(path)
|
32
|
+
po.batch_optimize_inplace(path)
|
33
|
+
elsif File.file?(path)
|
34
|
+
po.optimize_inplace(path)
|
35
|
+
else
|
36
|
+
fail 'Neither directory nor file: ' + path
|
37
|
+
end
|
38
|
+
|
39
|
+
success 'PNG optimization completed.'
|
40
|
+
end
|
41
|
+
|
42
|
+
class PngOptimizer
|
43
|
+
|
44
|
+
def ends_with?(string, suffix)
|
45
|
+
string[-suffix.length, suffix.length] == suffix
|
46
|
+
end
|
47
|
+
|
48
|
+
def optimization_default_args
|
49
|
+
args = ""
|
50
|
+
args << "-rem alla " # remove everything except transparency
|
51
|
+
args << "-rem text " # remove text chunks
|
52
|
+
args << "-reduce " # eliminate unused colors and reduce bit-depth (if possible)
|
53
|
+
args
|
54
|
+
end
|
55
|
+
|
56
|
+
def optimize_file(input_file, output_file)
|
57
|
+
system "pngcrush #{optimization_default_args} '#{input_file}' '#{output_file}'"
|
58
|
+
end
|
59
|
+
|
60
|
+
def unused_tempfile_path(original)
|
61
|
+
dirname = File.dirname(original)
|
62
|
+
basename = File.basename(original)
|
63
|
+
count = 0
|
64
|
+
begin
|
65
|
+
tmp_name = "#{dirname}/#{basename}_temp_#{count += 1}.png"
|
66
|
+
end while File.exists?(tmp_name)
|
67
|
+
tmp_name
|
68
|
+
end
|
69
|
+
|
70
|
+
def optimize_inplace(input_file)
|
71
|
+
temp_file = unused_tempfile_path(input_file)
|
72
|
+
result = optimize_file(input_file, temp_file)
|
73
|
+
if result
|
74
|
+
FileUtils.rm(input_file)
|
75
|
+
FileUtils.mv("#{temp_file}", "#{input_file}")
|
76
|
+
else
|
77
|
+
fail 'Error:' + $?
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def batch_optimize_inplace(path)
|
82
|
+
# Dir[".png"] works case sensitive, so to catch all funky .png extensions we have to go the following way:
|
83
|
+
png_relative_paths = []
|
84
|
+
Dir["#{path}/*.*"].each do |file_name|
|
85
|
+
png_relative_paths << file_name if ends_with?(File.basename(file_name.downcase), ".png")
|
86
|
+
end
|
87
|
+
png_relative_paths.each do |png_relative_path|
|
88
|
+
optimize_inplace(png_relative_path)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|