geoffrey 0.0.6 → 0.1.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.
data/.gitignore CHANGED
@@ -3,3 +3,4 @@ pkg/*
3
3
  .bundle
4
4
  .yardoc
5
5
  doc
6
+ log
@@ -1,12 +1,15 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- geoffrey (0.0.5)
4
+ geoffrey (0.1.0)
5
5
  plist4r
6
+ progressbar
7
+ thor
6
8
 
7
9
  GEM
8
10
  remote: http://rubygems.org/
9
11
  specs:
12
+ autotest (4.3.2)
10
13
  bluecloth (2.0.7)
11
14
  haml (3.0.18)
12
15
  libxml-ruby (1.1.4)
@@ -18,18 +21,23 @@ GEM
18
21
  haml
19
22
  libxml-ruby
20
23
  libxml4r
24
+ progressbar (0.9.0)
21
25
  rake (0.8.7)
22
26
  rspec (1.3.0)
27
+ thor (0.14.0)
23
28
  yard (0.6.0)
24
29
 
25
30
  PLATFORMS
26
31
  ruby
27
32
 
28
33
  DEPENDENCIES
34
+ autotest
29
35
  bluecloth (~> 2.0.7)
30
36
  bundler (~> 1.0.0.rc.5)
31
37
  geoffrey!
32
38
  mocha (~> 0.9.8)
33
39
  plist4r
40
+ progressbar
34
41
  rspec (~> 1.3.0)
42
+ thor
35
43
  yard (~> 0.6.0)
data/README.md CHANGED
@@ -1,12 +1,6 @@
1
1
  geoffrey
2
2
  ========
3
3
 
4
- ## ALERT!
5
-
6
- This is very early alpha code, use with precaution. It's a spinoff of myself willing to
7
- improve my [dotfiles project](http://github.com/mrsimo/dotfiles) a step further. All
8
- code examples in this README come from it.
9
-
10
4
  ## Links
11
5
 
12
6
  * [Documentation](http://rubydoc.info/gems/geoffrey/frames) (this README is better viewed there ;)
@@ -14,10 +8,13 @@ code examples in this README come from it.
14
8
  ## Introduction
15
9
 
16
10
  Geoffrey's your little helper, that who just wants to makes your life easier.
11
+ It helps you install applications into your OS X system.
12
+
13
+ It will help you in two ways, with a command line application, and an API to
14
+ let you create your own recipes.
17
15
 
18
- It aims to help you create little scripts to setup your OSX the way you like.
19
- If, like me, you use different computers at work, home, or even when travelling,
20
- you'll know it's a pain in the ass to keep everything up to date.
16
+ If, like me, you use different computers at work, home, or even when
17
+ travelling, you'll know it's a pain in the ass to keep everything up to date.
21
18
 
22
19
  Inspired in the numerous dotfiles projects, specially
23
20
  [holman's](http://github.com/holman/dotfiles),
@@ -27,8 +24,24 @@ the intention is to bring customization to a bigger extend.
27
24
 
28
25
  $ [sudo] gem install geoffrey
29
26
 
30
- Geoffrey's Talents
31
- ==================
27
+ Command line
28
+ ============
29
+
30
+ Type `geoffrey` to see help with the commands. Right now you can list the
31
+ provided recipes, and install them.
32
+
33
+ $ geoffrey list
34
+ adium growl skype unrarx
35
+
36
+ $ geoffrey install adium
37
+ >> Adium is a free instant messaging application for Mac OS X that can connect to AIM, MSN, Jabber, Yahoo, and more.
38
+ >> Downloading http://adiumx.cachefly.net/Adium_1.3.10.dmg
39
+ : 100% |oooooooooooooooooooooooo| 21.3MB 451.6KB/s ETA: 00:00:00
40
+ >> Moving file to /Applications
41
+ >> Install successful
42
+
43
+ API
44
+ ===
32
45
 
33
46
  Geoffrey has many talents, and thus you'll find he can help you in one of these areas:
34
47
 
@@ -67,10 +80,3 @@ up and running with Pro scheme, Menlo Font 14pt, and a size 520x100.
67
80
  end
68
81
 
69
82
  See {Geoffrey::plist} for extended documentation.
70
-
71
- ## A few words
72
-
73
- This is work done in my free hours, it's not yet finished. The code is dirty and mainly undocumented. It all started on #whyday,
74
- but I'm so lazy that I've needed a lot of time to do just this.
75
-
76
- Thanks!
data/Rakefile CHANGED
@@ -1,9 +1,11 @@
1
1
  require 'bundler'
2
2
  Bundler::GemHelper.install_tasks
3
+ require 'spec/rake/spectask'
4
+ require 'mocha'
3
5
 
4
6
  desc "Execute specs"
5
- task :spec do
6
- puts `bundle exec spec spec`
7
+ Spec::Rake::SpecTask.new('spec') do |t|
8
+ t.spec_files = FileList['spec/**/*.rb'].reject{|f| f =~ /templates/ }
7
9
  end
8
10
 
9
11
  desc "Generate docs"
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: utf-8 -*-
3
+
4
+ require 'geoffrey'
5
+ require 'geoffrey/package'
6
+ require 'geoffrey/cli'
7
+
8
+ Geoffrey::CLI.start
@@ -16,13 +16,16 @@ Gem::Specification.new do |s|
16
16
 
17
17
  s.add_development_dependency "bundler", "~> 1.0.0.rc.5"
18
18
  s.add_development_dependency "rspec", "~> 1.3.0"
19
+ s.add_development_dependency "autotest"
19
20
  s.add_development_dependency "mocha", "~> 0.9.8"
20
21
  s.add_development_dependency "yard", "~> 0.6.0"
21
22
  s.add_development_dependency "bluecloth", "~> 2.0.7"
22
23
 
23
24
  s.add_dependency 'plist4r'
25
+ s.add_dependency 'progressbar'
26
+ s.add_dependency 'thor'
24
27
 
25
28
  s.files = `git ls-files`.split("\n")
26
- s.executables = `git ls-files`.split("\n").select{|f| f =~ /^bin/}
29
+ s.executables = ['geoffrey']
27
30
  s.require_path = 'lib'
28
31
  end
@@ -5,6 +5,13 @@ module Geoffrey
5
5
 
6
6
  class << self
7
7
 
8
+ def environment=(value)
9
+ @@environment = value if value
10
+ end
11
+ def environment
12
+ @@environment ||= :production
13
+ end
14
+
8
15
  # Define a package installation from the URL specified. It will download
9
16
  # the file, and if it's compressed, it will decompress it.It currently
10
17
  # supports zip, tar.gz, .gz and .dmg files. Once extracted it will look for
@@ -18,6 +25,7 @@ module Geoffrey
18
25
  # * +:if+ inverse of inverse
19
26
  # * +:file+ what file has to be installed from all of the ones that came in
20
27
  # the compressed file.
28
+ # * +:filename+ set the name of the file you are downloading.
21
29
  #
22
30
  # If you don't want to install a .pkg but do something else, you can just
23
31
  # override the install method. See the examples
@@ -0,0 +1,58 @@
1
+ require 'thor'
2
+
3
+ module Geoffrey
4
+ class CLI < Thor
5
+
6
+ attr_accessor :package
7
+
8
+ desc "list", "list of all the available templates"
9
+ def list(filter = '')
10
+ names = if filter && filter != ''
11
+ list_of_template_names.select{ |name| name.downcase.include?(filter.downcase) }
12
+ else
13
+ list_of_template_names
14
+ end
15
+ puts names.join(' ')
16
+ end
17
+
18
+ desc "install [template]", "install the specified template"
19
+ method_options :verbose => :boolean
20
+ def install(name)
21
+ if file = file_for_template(name)
22
+ @package = Geoffrey::Package.new
23
+ @package.verbose(true) if options[:verbose]
24
+ @package.instance_eval File.read(file)
25
+ @package.download_and_decompress
26
+ @package.install
27
+ else
28
+ puts "Template not found"
29
+ exit 1
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ #--
36
+ # TODO Might be useful for the future to have multiple dirs for templates.
37
+ def templates_dir
38
+ File.dirname(__FILE__) + '/../../templates'
39
+ end
40
+
41
+ def list_of_templates
42
+ Dir[templates_dir + '/*']
43
+ end
44
+
45
+ def list_of_template_names
46
+ list_of_templates.map{ |file| name_from_file(file) }
47
+ end
48
+
49
+ def file_for_template(name)
50
+ list_of_templates.select{ |file| name_from_file(file) == name }.first
51
+ end
52
+
53
+ def name_from_file(file)
54
+ file.split("/").last.split(".").first
55
+ end
56
+
57
+ end
58
+ end
@@ -2,6 +2,8 @@ require 'tmpdir'
2
2
  require 'tempfile'
3
3
  require 'open-uri'
4
4
  require 'fileutils'
5
+ require 'progressbar'
6
+ require 'geoffrey/reporter'
5
7
 
6
8
  module Geoffrey
7
9
 
@@ -10,43 +12,49 @@ module Geoffrey
10
12
 
11
13
  # This class helps installing packages and other apps into your system.
12
14
  class Package
13
- attr_accessor :url, :options, :format, :filename, :dir, :file
15
+
16
+ include Geoffrey::Reporter
17
+
18
+ attr_accessor :url, :description, :options, :format, :filename, :dir, :file
14
19
 
15
20
  def initialize
16
21
  @options = {}
17
22
  end
18
23
 
19
- # Accepts anything parseable by URI
20
- def url(val = nil)
21
- @url = val if val
22
- @url
23
- end
24
-
25
- def options(val = nil)
26
- @options = val if val
27
- @options
24
+ [:url,:name,:description,:options].each do |attrib|
25
+ class_eval <<-GEOF
26
+ def #{attrib}(val=nil)
27
+ val.nil? ? @#{attrib} : @#{attrib} = val
28
+ end
29
+ GEOF
28
30
  end
29
31
 
30
32
  # Uses the url provided to download the file.
31
33
  # Checks the options first to see if it shouldn't be done.
32
- #--
33
- # TODO add some option to define the file name for weird url's
34
34
  def download_and_decompress
35
+ echo "URL not specified" unless @url && @url != ""
36
+
37
+ get_filename and debug("filename detected: #{@filename}")
38
+
35
39
  return unless should_install
36
40
 
41
+ echo @description if @description && @description != ""
42
+
37
43
  begin
38
- @file = Tempfile.new("geoffrey")
39
- @file.write open(@url).read
44
+ @file = Tempfile.new("geoffrey") and debug("downloading file into #{@file.path}")
45
+
46
+ read_with_progress_bar
40
47
  @file.close
41
48
 
42
- @filename = @url.split("/").last
43
- get_format
49
+ get_format and debug("format detected: #{@format}")
44
50
 
45
51
 
46
- @dir = "#{Dir.tmpdir}/#{filename}"
52
+ @dir = "#{Dir.tmpdir}/#{filename}" and debug("will put files into temporary dir: #{@dir}")
53
+
47
54
  FileUtils.rm_rf dir if File.exist?(dir)
48
55
  FileUtils.mkdir_p dir
49
- rescue
56
+ rescue Exception => e
57
+ echo "Can't find file #{@url}"
50
58
  raise NotFoundError
51
59
  end
52
60
 
@@ -64,10 +72,13 @@ module Geoffrey
64
72
 
65
73
  case File.extname file_to_install
66
74
  when ".pkg"
75
+ echo "Installing package"
67
76
  execute "installer -pkg #{file_to_install} -target /"
68
77
  when ".app"
69
- FileUtils.mv file_to_install, "/Appliactions"
78
+ echo "Moving file to /Applications"
79
+ FileUtils.mv file_to_install, "/Applications"
70
80
  end
81
+ echo "Install successful"
71
82
  end
72
83
 
73
84
  # If it was specified through options, then get that, otherwise
@@ -87,13 +98,19 @@ module Geoffrey
87
98
  private
88
99
 
89
100
  def should_install
90
- if @options[:unless] && @options[:unless].respond_to?(:call)
91
- !@options[:unless].call
92
- elsif @options[:if] && @options[:if].respond_to?(:call)
93
- @options[:if].call
101
+ ok_to_go = if @url && @url != ""
102
+ if @options[:unless] && @options[:unless].respond_to?(:call)
103
+ !@options[:unless].call
104
+ elsif @options[:if] && @options[:if].respond_to?(:call)
105
+ @options[:if].call
106
+ else
107
+ true
108
+ end
94
109
  else
95
- true
110
+ false
96
111
  end
112
+ echo "#{filename} is already installed." unless ok_to_go
113
+ ok_to_go
97
114
  end
98
115
 
99
116
  # Simple wrapper to execute things, using sudo or not as per defiend in the
@@ -103,12 +120,29 @@ module Geoffrey
103
120
  opts = @options.merge(opts)
104
121
  command = "sudo #{command}" if opts[:sudo]
105
122
  command = "#{command} > /dev/null 2>&1" unless opts[:verbose]
106
- `#{command}`
123
+ debug "-- executing: #{command}"
124
+ returned = `#{command}`
125
+ debug "-- returned:"
126
+ debug returned
127
+ returned
107
128
  end
108
129
  end
109
130
 
131
+ def read_with_progress_bar
132
+ pbar = nil
133
+ open(@url, :content_length_proc => lambda { |t|
134
+ if t && t > 0
135
+ echo "Downloading #{@url}"
136
+ pbar = ProgressBar.new("",t)
137
+ pbar.file_transfer_mode
138
+ end
139
+ }, :progress_proc => lambda { |s|
140
+ pbar.set s if pbar
141
+ }) { |f| @file.write f.read }
142
+ end
143
+
110
144
  def get_format
111
- @format = case File.extname @url
145
+ @format = case File.extname @filename
112
146
  when '.zip' then :zip
113
147
  when '.tar' then :tar
114
148
  when '.tgz' then :tgz
@@ -118,16 +152,23 @@ module Geoffrey
118
152
  end
119
153
  end
120
154
 
155
+ def get_filename
156
+ @filename = @options[:filename]
157
+ @filename ||= begin
158
+ escape_whitespaces(File.basename(@url).split("?").first) if @url && @url != ""
159
+ end
160
+ end
161
+
121
162
  # Actual decompressing of the file. Does so into a directory we created in
122
163
  # +#{Dir.tmpdir}/#{filename}+
123
164
  #--
124
- # TODO control errors. Maybe it's .zip but just doesn't work.
125
165
  # TODO better isolate each download inside the tmpdir? When debugging it's
126
166
  # misleading to have a folder with the file name and extension.
127
167
  # * Timestamp if clash?
128
168
  # * Random string?
129
169
  def decompress
130
- command = case format
170
+ debug "decompressing #{file.path}"
171
+ case format
131
172
  when :zip
132
173
  execute "unzip -o #{file.path} -d #{dir}", :sudo => false
133
174
  when :tar
@@ -144,21 +185,41 @@ module Geoffrey
144
185
 
145
186
  # Might be that the decompression failed
146
187
  raise DecompressionError unless $?.success?
188
+
189
+ debug "Files after decompression:"
190
+ debug `ls -la #{dir}`
147
191
  end
148
192
 
149
193
  # Mount the dmg file, copy contents to tmpdir, unmount, remove dmg
194
+ #--
195
+ # TODO change system calls with FileUtils maybe?
150
196
  def extract_from_dmg
151
197
  execute("mv #{file.path} #{dir}.dmg")
152
198
  str = execute("hdiutil attach #{dir}.dmg 2> /dev/null", :verbose => true)
153
199
  if $?.success?
154
200
  info = str.split("\t")
155
- dmg_dir = info.last.chomp
201
+
202
+ dmg_dir = escape_whitespaces(info.last.chomp)
203
+ debug "the dmg has been mounted here: #{dmg_dir}"
204
+
156
205
  device = info.first.strip.split("/").last
157
- execute("cp -r #{dmg_dir}/** #{dir}")
206
+ debug "the attached device is: #{device}"
207
+
208
+ debug "These are the files inside the dmg:"
209
+ debug `ls -la #{dmg_dir}`
210
+
211
+ execute("cp -r #{escape_whitespaces(dmg_dir)}/** #{dir}")
212
+
213
+ debug "Now these in the target"
214
+ debug `ls -la #{dir}`
215
+
158
216
  execute("hdiutil detach #{device}")
159
217
  execute("rm -f #{dir}.dmg")
160
218
  end
161
219
  end
162
220
 
221
+ def escape_whitespaces(path)
222
+ path.gsub(' ','\ ')
223
+ end
163
224
  end
164
225
  end
@@ -10,7 +10,11 @@ module Geoffrey
10
10
  :terminal => "#{ENV["HOME"]}/Library/Preferences/com.apple.Terminal.plist"
11
11
  }
12
12
 
13
- attr_accessor :data, :file, :process
13
+ attr_accessor :data, :file, :process, :changes
14
+
15
+ def initialize
16
+ @changes = {}
17
+ end
14
18
 
15
19
  # Set the file you'll edit. if you pass a symbol then it'll use the file
16
20
  # defined in the FILES hash.
@@ -30,7 +34,7 @@ module Geoffrey
30
34
  end
31
35
 
32
36
  def options
33
- data
37
+ self
34
38
  end
35
39
 
36
40
  def [](key)
@@ -38,12 +42,12 @@ module Geoffrey
38
42
  end
39
43
 
40
44
  def []=(key,value)
45
+ old_value = data[key]
41
46
  data[key] = value
47
+ @changes[key] = value if old_value != value
42
48
  end
43
49
 
44
50
  # Saves the changes defined into the file.
45
- #--
46
- # TODO don't reopen the process if it wasn't opened to begin with
47
51
  def save
48
52
  kill
49
53
  data.save
@@ -53,11 +57,21 @@ module Geoffrey
53
57
  private
54
58
 
55
59
  def kill
56
- system "killall '#{@process}'" unless @process.nil?
60
+ unless @process.nil? || @changes.keys.size.zero?
61
+ `ps aux | grep #{@process}`.split("\n").each do |row|
62
+ unless row.include?("grep #{@process}")
63
+ columns = row.split(" ")
64
+ @pid = columns[1].to_i
65
+ Process.kill("TERM", @pid) rescue nil
66
+ end
67
+ end
68
+ end
57
69
  end
58
70
 
59
71
  def open
60
- system "open '#{@process}'" unless @process.nil?
72
+ unless @process.nil? || @changes.keys.size.zero? || @pid.nil?
73
+ system "open '#{@process}'"
74
+ end
61
75
  end
62
76
 
63
77
  end
@@ -0,0 +1,42 @@
1
+ require 'logger'
2
+
3
+ #--
4
+ # H4X!
5
+ # Changing logger format, didn't like the other one
6
+ class Logger
7
+ def format_message(level, time, progname, msg)
8
+ ">> #{msg}\n"
9
+ end
10
+ end
11
+ # /H4X
12
+
13
+ module Geoffrey
14
+
15
+ # This module is intended to be used in real action classes so that they can
16
+ # report what they're doing. The idea is to abstract the functionality in
17
+ # order to test it better and help the future children.
18
+ module Reporter
19
+
20
+ def logger
21
+ unless @logger
22
+ @logger = Logger.new($stdout)
23
+ @logger.level = Logger::INFO
24
+ end
25
+ @logger
26
+ end
27
+
28
+ def echo(msg)
29
+ logger.info msg
30
+ end
31
+
32
+ def debug(msg)
33
+ logger.debug msg
34
+ end
35
+
36
+ def verbose(mode)
37
+ @logger = Logger.new($stdout)
38
+ @logger.level = (mode ? Logger::DEBUG : Logger::INFO)
39
+ end
40
+
41
+ end
42
+ end
@@ -1,3 +1,3 @@
1
1
  module Geoffrey
2
- VERSION = "0.0.6"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ require 'geoffrey/cli'
4
+
5
+ describe Geoffrey::CLI do
6
+
7
+ before(:each) do
8
+ @app = Geoffrey::CLI.new
9
+ @app.stubs(:templates_dir).returns(File.dirname(__FILE__) + '/templates')
10
+ end
11
+
12
+ context 'list' do
13
+
14
+ it "prints a list of templates" do
15
+ output = capturing_output { @app.list }
16
+ output.should include("simbl")
17
+ output.should include("growl")
18
+ end
19
+
20
+ it "filters through items in the list" do
21
+ output = capturing_output { @app.list("gr") }
22
+ output.should include("growl")
23
+ output.should_not include("simbl")
24
+ end
25
+
26
+ it "filters through items in the list ignoring case" do
27
+ output = capturing_output { @app.list("Growl") }
28
+ output.should include("growl")
29
+ output.should_not include("simbl")
30
+ end
31
+
32
+ end
33
+
34
+ context 'install' do
35
+
36
+ # Can't really test something's installed for real, so we just expect
37
+ # that it will call the proper methods on the Geoffrey::Package object.
38
+ it "knows what to install" do
39
+ Geoffrey::Package.any_instance.expects(:url).with("http://some.fic/tional/url.dmg").once
40
+ Geoffrey::Package.any_instance.expects(:description).with("This package should do awesome stuff").once
41
+ Geoffrey::Package.any_instance.expects(:options).with(:sudo => true).once.returns(nil)
42
+ Geoffrey::Package.any_instance.expects(:download_and_decompress).once.returns(nil)
43
+ Geoffrey::Package.any_instance.expects(:install).once
44
+ @app.install("growl")
45
+ end
46
+
47
+ end
48
+
49
+ #--
50
+ # This doesn't really have to be tested, right? It's not public api, we
51
+ # should test the public methods that depend on these. Or not?
52
+ context 'finding templates' do
53
+
54
+ it "knows about template files" do
55
+ @app.send(:list_of_template_names).should == ['growl','simbl']
56
+ @app.send(:list_of_templates).each do |file|
57
+ File.exists?(file).should be_true
58
+ end
59
+ end
60
+
61
+ it "knows how to find the file a template is defined it" do
62
+ @app.send(:file_for_template,'growl').should == File.dirname(__FILE__) + '/templates/growl.rb'
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+ require 'geoffrey'
3
+
4
+ describe "Geoffrey" do
5
+
6
+ context "environment" do
7
+
8
+ it "has an environment attribute" do
9
+ Geoffrey.should respond_to(:"environment=")
10
+ Geoffrey.should respond_to(:"environment")
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -3,10 +3,18 @@ require 'geoffrey/package'
3
3
 
4
4
  describe Geoffrey::Package do
5
5
 
6
- it "allows for the url to be specified" do
6
+ it "receives different options" do
7
+ [:url, :name, :description, :options].each do |opt|
8
+ package = Geoffrey::Package.new
9
+ package.send(opt, 'foo')
10
+ package.send(opt).should == 'foo'
11
+ end
12
+ end
13
+
14
+ it "allows for a description to be specified" do
7
15
  package = Geoffrey::Package.new
8
- package.url 'http://localhost/crappy.zip'
9
- package.url.should == 'http://localhost/crappy.zip'
16
+ package.description 'foo'
17
+ package.description.should == 'foo'
10
18
  end
11
19
 
12
20
  it "allows for some options to be specified" do
@@ -33,19 +41,23 @@ describe Geoffrey::Package do
33
41
  end
34
42
 
35
43
  it "doesnt install the thing if a :unless option is provided" do
36
- package = Geoffrey::Package.new
37
- package.url("whatever")
38
- package.options :unless => Proc.new { true }
39
- package.download_and_decompress.should == nil
40
- package.install.should == nil
44
+ capturing_output do
45
+ package = Geoffrey::Package.new
46
+ package.url("whatever")
47
+ package.options :unless => Proc.new { true }
48
+ package.download_and_decompress.should == nil
49
+ package.install.should == nil
50
+ end
41
51
  end
42
52
 
43
53
  it "inverse if passed a :if option" do
44
- package = Geoffrey::Package.new
45
- package.url("whatever")
46
- package.options :if => Proc.new { false }
47
- package.download_and_decompress.should == nil
48
- package.install.should == nil
54
+ capturing_output do
55
+ package = Geoffrey::Package.new
56
+ package.url("whatever")
57
+ package.options :if => Proc.new { false }
58
+ package.download_and_decompress.should == nil
59
+ package.install.should == nil
60
+ end
49
61
  end
50
62
 
51
63
  end
@@ -55,9 +67,11 @@ describe Geoffrey::Package do
55
67
  context 'package downloading/uncompressing' do
56
68
 
57
69
  it "raises an error if the file can't be found/read" do
58
- package = Geoffrey::Package.new
59
- package.url 'really/impossible/to/exist.dmg'
60
- lambda { package.download_and_decompress}.should raise_exception(Geoffrey::NotFoundError)
70
+ capturing_output do
71
+ package = Geoffrey::Package.new
72
+ package.url 'really/impossible/to/exist.dmg'
73
+ lambda { package.download_and_decompress }.should raise_exception(Geoffrey::NotFoundError)
74
+ end
61
75
  end
62
76
 
63
77
  it "raises an error if the file has bad format" do
@@ -75,6 +89,13 @@ describe Geoffrey::Package do
75
89
  File.read(package.file_to_install).chomp.should == "foo"
76
90
  end
77
91
 
92
+ it "removes dirty from url's (but only from http/ftp" do
93
+ package = Geoffrey::Package.new
94
+ package.url(File.dirname(__FILE__) + "/static/test.txt?foo=bar&baz=jar")
95
+ package.send(:get_filename)
96
+ package.filename.should == "test.txt"
97
+ end
98
+
78
99
  it "smartly unzips the file if it's a zip" do
79
100
  package = Geoffrey::Package.new
80
101
  package.url(File.dirname(__FILE__) + "/static/test.txt.zip")
@@ -96,9 +117,25 @@ describe Geoffrey::Package do
96
117
  File.read(package.file_to_install).chomp.should == "foo"
97
118
  end
98
119
 
120
+ # Testing with a dmg that has a whitespace as a name because testing dmg's
121
+ # is too slow, so we do it all in one.
122
+ #--
123
+ # TODO this test fails :( but it does work in real examples.
99
124
  it "also supports .dmg files" do
100
125
  package = Geoffrey::Package.new
101
- package.url(File.dirname(__FILE__) + "/static/test.dmg")
126
+ package.url(File.dirname(__FILE__) + "/static/test 1.dmg")
127
+ package.download_and_decompress
128
+ File.read(package.file_to_install).chomp.should == "foo"
129
+ end
130
+
131
+ # test.txt.gz.123 is test.txt.gz copied and renamed. This way geoffrey
132
+ # knows what file format to use.
133
+ # TODO maybe would be better to just set the strategy to use when
134
+ # extracting
135
+ it "allows for filename to be configured" do
136
+ package = Geoffrey::Package.new
137
+ package.url(File.dirname(__FILE__) + "/static/test.txt.gz.123")
138
+ package.options :filename => 'test.txt.gz'
102
139
  package.download_and_decompress
103
140
  File.read(package.file_to_install).chomp.should == "foo"
104
141
  end
@@ -17,7 +17,7 @@ describe Geoffrey::Plist do
17
17
  it "also accepts symbol values for already known values" do
18
18
  plist = Geoffrey::Plist.new
19
19
  plist.file :terminal
20
- plist.file.should include("com.apple.Terminal.plist")
20
+ plist.file.should include("com.apple.Terminal.plist")
21
21
  end
22
22
 
23
23
  it "parses correct plist items" do
@@ -26,18 +26,16 @@ describe Geoffrey::Plist do
26
26
  plist.data.class.should == Plist4r::Plist
27
27
  end
28
28
 
29
- it "knows that it has to close some appication before applying changes" do
29
+ it "doesn't restart applications if no changes where really made" do
30
30
  plist = Geoffrey::Plist.new
31
31
  plist.file(@plist_file.path)
32
32
  plist.affects "/some/process"
33
-
34
- # Since I don't know how to test this without starting and shutting down processes
35
- # I'll just suppose that some internal functions are called
36
- plist.expects(:system).twice
33
+ plist.expects(:system).never
37
34
  plist.save
38
35
  end
39
36
 
40
37
  context "plist modification" do
38
+
41
39
  it "allows adding new plist item" do
42
40
  plist = Geoffrey::Plist.new
43
41
  plist.file(@plist_file.path)
@@ -0,0 +1,79 @@
1
+ require 'spec_helper'
2
+
3
+ require 'geoffrey'
4
+
5
+
6
+ class Foo
7
+ include Geoffrey::Reporter
8
+ end
9
+
10
+ describe Geoffrey::Reporter do
11
+
12
+ it "gives classes the ability to echo" do
13
+ foo = Foo.new
14
+ capturing_output { foo.echo "Hola" }.should include("Hola")
15
+ end
16
+
17
+ it "gives classes the ability to print debug information" do
18
+ foo = Foo.new
19
+ capturing_output { foo.debug "useful" }.should_not include("useful")
20
+ capturing_output { foo.verbose(true); foo.debug "useful" }.should include("useful")
21
+ capturing_output { foo.verbose(false); foo.debug "useful" }.should_not include("useful")
22
+ end
23
+
24
+ end
25
+
26
+ describe Geoffrey::Package do
27
+
28
+ before(:each) do
29
+ @package = Geoffrey::Package.new
30
+ end
31
+
32
+ it "prints proper error when no url specified" do
33
+ capturing_output { @package.download_and_decompress }.should include("URL not specified")
34
+ end
35
+
36
+ it "prints proper error when file can't be downloaded for some reason" do
37
+ capturing_output do
38
+ @package.url("/nothing/to/see/here.txt")
39
+ lambda { @package.download_and_decompress }.should raise_error
40
+ end.should include("Can't find file /nothing/to/see/here.txt")
41
+ end
42
+
43
+ it "prints the description when given" do
44
+ capturing_output do
45
+ @package.url(File.dirname(__FILE__) + "/static/test.txt")
46
+ @package.description 'Something useful'
47
+ @package.download_and_decompress
48
+ end.should include("Something useful")
49
+ end
50
+
51
+ it "prints proper message when the conditions don't apply" do
52
+ capturing_output do
53
+ @package.url(File.dirname(__FILE__) + "/static/test.txt")
54
+ @package.options :if => Proc.new { false }
55
+ @package.download_and_decompress
56
+ end.strip.should include("test.txt is already installed.")
57
+ end
58
+
59
+ end
60
+
61
+ describe Geoffrey::Plist do
62
+
63
+ it "prints proper error when file is not found or has bad format" do
64
+ pending
65
+ end
66
+
67
+ it "prints amount of changes done" do
68
+ pending
69
+ end
70
+
71
+ it "prints nothing if no values where changed" do
72
+ pending
73
+ end
74
+
75
+ it "prints information about process being restarted" do
76
+ pending
77
+ end
78
+
79
+ end
@@ -1,8 +1,11 @@
1
- require 'bundler'
2
- Bundler.setup(:default,:test)
3
- require 'geoffrey'
4
1
  require 'tempfile'
2
+ require 'stringio'
3
+ require 'geoffrey'
4
+ require 'support/helpers'
5
+
6
+ Geoffrey.environment = :test
5
7
 
6
8
  Spec::Runner.configure do |config|
7
9
  config.mock_with :mocha
10
+ config.include Helpers
8
11
  end
@@ -0,0 +1,17 @@
1
+ module Helpers
2
+
3
+ # Small helper to capture output oft hings happening inside a block
4
+ # @example
5
+ # out = capturing_output do
6
+ # puts "Hello!"
7
+ # end
8
+ # out # => "Hello\n"
9
+ def capturing_output(&block)
10
+ $stdout = StringIO.new
11
+ yield
12
+ out = $stdout.string
13
+ $stdout = STDOUT
14
+ return out
15
+ end
16
+
17
+ end
@@ -0,0 +1,3 @@
1
+ url 'http://some.fic/tional/url.dmg'
2
+ description "This package should do awesome stuff"
3
+ options :sudo => true
File without changes
@@ -0,0 +1,4 @@
1
+ name 'Adium'
2
+ description "Adium is a free instant messaging application for Mac OS X that can connect to AIM, MSN, Jabber, Yahoo, and more."
3
+ url 'http://adiumx.cachefly.net/Adium_1.3.10.dmg'
4
+ options :unless => Proc.new { File.exists?("/Applications/Adium.app") }
@@ -0,0 +1,4 @@
1
+ name "Growl"
2
+ description "Growl lets Mac OS X applications unintrusively tell you when things happen."
3
+ url 'http://growl.cachefly.net/Growl-1.2.1.dmg'
4
+ options :sudo => true, :unless => Proc.new { File.exists?("/Library/PreferencePanes/Growl.prefPane") }
@@ -0,0 +1,4 @@
1
+ name "Skype"
2
+ description "Call landlines and mobiles worldwide"
3
+ url 'http://www.skype.com/go/getskype-macosx.dmg'
4
+ options :unless => Proc.new { File.exists?("/Applications/Skype.app") }
@@ -0,0 +1,4 @@
1
+ name "UnRarX"
2
+ description "UnRarX - Mac OS X RAR Extraction Utility"
3
+ url 'http://www.unrarx.com/files/UnRarX_2.2.zip'
4
+ options :unless => Proc.new { File.exists?("/Applications/UnRarX.app") }
metadata CHANGED
@@ -1,13 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: geoffrey
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
5
4
  prerelease: false
6
5
  segments:
7
6
  - 0
7
+ - 1
8
8
  - 0
9
- - 6
10
- version: 0.0.6
9
+ version: 0.1.0
11
10
  platform: ruby
12
11
  authors:
13
12
  - Albert Llop
@@ -15,7 +14,7 @@ autorequire:
15
14
  bindir: bin
16
15
  cert_chain: []
17
16
 
18
- date: 2010-08-31 00:00:00 +02:00
17
+ date: 2010-09-10 00:00:00 +02:00
19
18
  default_executable:
20
19
  dependencies:
21
20
  - !ruby/object:Gem::Dependency
@@ -26,7 +25,6 @@ dependencies:
26
25
  requirements:
27
26
  - - ~>
28
27
  - !ruby/object:Gem::Version
29
- hash: 15424063
30
28
  segments:
31
29
  - 1
32
30
  - 0
@@ -44,7 +42,6 @@ dependencies:
44
42
  requirements:
45
43
  - - ~>
46
44
  - !ruby/object:Gem::Version
47
- hash: 27
48
45
  segments:
49
46
  - 1
50
47
  - 3
@@ -53,72 +50,107 @@ dependencies:
53
50
  type: :development
54
51
  version_requirements: *id002
55
52
  - !ruby/object:Gem::Dependency
56
- name: mocha
53
+ name: autotest
57
54
  prerelease: false
58
55
  requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ type: :development
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: mocha
67
+ prerelease: false
68
+ requirement: &id004 !ruby/object:Gem::Requirement
59
69
  none: false
60
70
  requirements:
61
71
  - - ~>
62
72
  - !ruby/object:Gem::Version
63
- hash: 43
64
73
  segments:
65
74
  - 0
66
75
  - 9
67
76
  - 8
68
77
  version: 0.9.8
69
78
  type: :development
70
- version_requirements: *id003
79
+ version_requirements: *id004
71
80
  - !ruby/object:Gem::Dependency
72
81
  name: yard
73
82
  prerelease: false
74
- requirement: &id004 !ruby/object:Gem::Requirement
83
+ requirement: &id005 !ruby/object:Gem::Requirement
75
84
  none: false
76
85
  requirements:
77
86
  - - ~>
78
87
  - !ruby/object:Gem::Version
79
- hash: 7
80
88
  segments:
81
89
  - 0
82
90
  - 6
83
91
  - 0
84
92
  version: 0.6.0
85
93
  type: :development
86
- version_requirements: *id004
94
+ version_requirements: *id005
87
95
  - !ruby/object:Gem::Dependency
88
96
  name: bluecloth
89
97
  prerelease: false
90
- requirement: &id005 !ruby/object:Gem::Requirement
98
+ requirement: &id006 !ruby/object:Gem::Requirement
91
99
  none: false
92
100
  requirements:
93
101
  - - ~>
94
102
  - !ruby/object:Gem::Version
95
- hash: 1
96
103
  segments:
97
104
  - 2
98
105
  - 0
99
106
  - 7
100
107
  version: 2.0.7
101
108
  type: :development
102
- version_requirements: *id005
109
+ version_requirements: *id006
103
110
  - !ruby/object:Gem::Dependency
104
111
  name: plist4r
105
112
  prerelease: false
106
- requirement: &id006 !ruby/object:Gem::Requirement
113
+ requirement: &id007 !ruby/object:Gem::Requirement
107
114
  none: false
108
115
  requirements:
109
116
  - - ">="
110
117
  - !ruby/object:Gem::Version
111
- hash: 3
112
118
  segments:
113
119
  - 0
114
120
  version: "0"
115
121
  type: :runtime
116
- version_requirements: *id006
122
+ version_requirements: *id007
123
+ - !ruby/object:Gem::Dependency
124
+ name: progressbar
125
+ prerelease: false
126
+ requirement: &id008 !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ segments:
132
+ - 0
133
+ version: "0"
134
+ type: :runtime
135
+ version_requirements: *id008
136
+ - !ruby/object:Gem::Dependency
137
+ name: thor
138
+ prerelease: false
139
+ requirement: &id009 !ruby/object:Gem::Requirement
140
+ none: false
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ segments:
145
+ - 0
146
+ version: "0"
147
+ type: :runtime
148
+ version_requirements: *id009
117
149
  description: Geoffrey is a little helper to aid in the installation of packages and configuration of various Mac OSX applications.
118
150
  email:
119
151
  - mrsimo@gmail.com
120
- executables: []
121
-
152
+ executables:
153
+ - geoffrey
122
154
  extensions: []
123
155
 
124
156
  extra_rdoc_files: []
@@ -129,17 +161,23 @@ files:
129
161
  - Gemfile.lock
130
162
  - README.md
131
163
  - Rakefile
164
+ - bin/geoffrey
132
165
  - geoffrey.gemspec
133
166
  - lib/geoffrey.rb
167
+ - lib/geoffrey/cli.rb
134
168
  - lib/geoffrey/package.rb
135
169
  - lib/geoffrey/plist.rb
170
+ - lib/geoffrey/reporter.rb
136
171
  - lib/geoffrey/version.rb
172
+ - spec/cli_spec.rb
173
+ - spec/geoffrey_spec.rb
137
174
  - spec/package_spec.rb
138
175
  - spec/plist_spec.rb
176
+ - spec/reporter_spec.rb
139
177
  - spec/spec.opts
140
178
  - spec/spec_helper.rb
141
179
  - spec/static/example.plist
142
- - spec/static/test.dmg
180
+ - spec/static/test 1.dmg
143
181
  - spec/static/test.error.dmg
144
182
  - spec/static/test.error.gz
145
183
  - spec/static/test.error.tar.gz
@@ -148,7 +186,15 @@ files:
148
186
  - spec/static/test.tgz
149
187
  - spec/static/test.txt
150
188
  - spec/static/test.txt.gz
189
+ - spec/static/test.txt.gz.123
151
190
  - spec/static/test.txt.zip
191
+ - spec/support/helpers.rb
192
+ - spec/templates/growl.rb
193
+ - spec/templates/simbl.rb
194
+ - templates/adium.rb
195
+ - templates/growl.rb
196
+ - templates/skype.rb
197
+ - templates/unrarx.rb
152
198
  has_rdoc: true
153
199
  homepage:
154
200
  licenses: []
@@ -163,7 +209,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
163
209
  requirements:
164
210
  - - ">="
165
211
  - !ruby/object:Gem::Version
166
- hash: 3
167
212
  segments:
168
213
  - 0
169
214
  version: "0"
@@ -172,7 +217,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
172
217
  requirements:
173
218
  - - ">="
174
219
  - !ruby/object:Gem::Version
175
- hash: 23
176
220
  segments:
177
221
  - 1
178
222
  - 3