geordi 0.18.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|