sparkleology 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2008-09-17
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
@@ -0,0 +1,34 @@
1
+ History.txt
2
+ Manifest.txt
3
+ PostInstall.txt
4
+ README.rdoc
5
+ Rakefile
6
+ bin/sparkleology
7
+ config/hoe.rb
8
+ config/requirements.rb
9
+ lib/sparkleology.rb
10
+ lib/sparkleology/command.rb
11
+ lib/sparkleology/exceptions.rb
12
+ lib/sparkleology/find_application_command.rb
13
+ lib/sparkleology/latest_version_command.rb
14
+ lib/sparkleology/version.rb
15
+ script/console
16
+ script/destroy
17
+ script/generate
18
+ script/txt2html
19
+ setup.rb
20
+ tasks/deployment.rake
21
+ tasks/environment.rake
22
+ tasks/website.rake
23
+ test/fixtures/Applications/NonSparkleApp.app/Contents/Info.plist
24
+ test/fixtures/Applications/ValidSparkleApp.app/Contents/Info.plist
25
+ test/test_command.rb
26
+ test/test_find_application_command.rb
27
+ test/test_helper.rb
28
+ test/test_latest_version_command.rb
29
+ test/test_sparkleology.rb
30
+ website/index.html
31
+ website/index.txt
32
+ website/javascripts/rounded_corners_lite.inc.js
33
+ website/stylesheets/screen.css
34
+ website/template.html.erb
File without changes
@@ -0,0 +1,73 @@
1
+ = sparkleology
2
+
3
+ * http://drnicutilities.rubyforge.org/sparkleology
4
+ * http://github.com/drnic/sparkleology
5
+
6
+ == DESCRIPTION:
7
+
8
+ Takes a Mac OS X application name that uses Sparkle for auto-updates, and returns information about that application's Sparkle RSS feed or the latest download URL for that Application.
9
+
10
+ The command-line app +sparkleology+ has two modes of operation depending on the argument/STDIN provided. If the last argument, or STDIN, is:
11
+
12
+ * an application name => the Sparkle RSS feed URL is returned
13
+ * a Sparkle RSS feed URL => the latest version download URL is returned
14
+
15
+ That is, the result of sparkleology can be passed to itself.
16
+
17
+ sparkleology Skitch => http://update.plasq.com/skitch-appcast.xml
18
+ sparkleology http://update.plasq.com/skitch-appcast.xml => http://skitch.com/download/skitch-b6.2-v10678.zip
19
+
20
+ Therefore, since +sparkleology+ can take the argument as STDIN as well, you can pipe the results together:
21
+
22
+ sparkleology Skitch | sparkleology
23
+ sparkleology Skitch | xargs sparkleology
24
+
25
+ both return http://skitch.com/download/skitch-b6.2-v10678.zip
26
+
27
+ The reason that +sparkleology+ returns the RSS feed URL if you give it an App Name is that this feed URL is permanently consistent, whereas the download URL possibly changes with each new application version. So, the feed URL could be stored and then used later (say on a machine that does not have the original app already installed) to fetch the latest version of the application, which may or may not be already installed.
28
+
29
+ We're using this in the http://github.com/bjeanes/noober script for installing a list of apps on a fresh OS X machine.
30
+
31
+ == FEATURES/PROBLEMS:
32
+
33
+ * Can return the RSS feed for a given application that contains the "latest version" URL
34
+ * Can return the "latest version" URL for a given application, either from the Application name or the RSS feed URL for an Application (say if the Application isn't yet installed)
35
+
36
+ == SYNOPSIS:
37
+
38
+ Allows for scripting of fetching and/or installing the latest version of a specific OS X application.
39
+
40
+ == REQUIREMENTS:
41
+
42
+ * RubyGems:
43
+ * libxml-ruby
44
+ * plist
45
+
46
+ == INSTALL:
47
+
48
+ * sudo gem install sparkleology
49
+
50
+ == LICENSE:
51
+
52
+ (The MIT License)
53
+
54
+ Copyright (c) 2008 Dr Nic Williams, Mocra
55
+
56
+ Permission is hereby granted, free of charge, to any person obtaining
57
+ a copy of this software and associated documentation files (the
58
+ 'Software'), to deal in the Software without restriction, including
59
+ without limitation the rights to use, copy, modify, merge, publish,
60
+ distribute, sublicense, and/or sell copies of the Software, and to
61
+ permit persons to whom the Software is furnished to do so, subject to
62
+ the following conditions:
63
+
64
+ The above copyright notice and this permission notice shall be
65
+ included in all copies or substantial portions of the Software.
66
+
67
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
68
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
69
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
70
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
71
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
72
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
73
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created on 2008-9-17.
4
+ # Copyright (c) 2008. All rights reserved.
5
+
6
+ begin
7
+ require 'rubygems'
8
+ rescue LoadError
9
+ # no rubygems to load, so we fail silently
10
+ end
11
+
12
+ require 'optparse'
13
+ require File.dirname(__FILE__) + "/../lib/sparkleology"
14
+
15
+ # NOTE: the option -p/--path= is given as an example, and should probably be replaced in your application.
16
+
17
+ OPTIONS = {
18
+ :path => '~'
19
+ }
20
+ MANDATORY_OPTIONS = %w( )
21
+
22
+ parser = OptionParser.new do |opts|
23
+ opts.banner = <<BANNER
24
+ Takes a Mac OS X application name that uses Sparkle for auto-updates, and
25
+ returns information about that application's Sparkle RSS feed or the latest
26
+ download URL for that Application.
27
+
28
+ Usage: #{File.basename($0)} [options]
29
+
30
+ Options are:
31
+ BANNER
32
+ opts.separator ""
33
+ # opts.on("-p", "--path=PATH", String,
34
+ # "The root path for selecting files",
35
+ # "Default: ~") { |OPTIONS[:path]| }
36
+ opts.on("-h", "--help",
37
+ "Show this help message.") { puts opts; exit }
38
+ opts.parse!(ARGV)
39
+
40
+ if MANDATORY_OPTIONS && MANDATORY_OPTIONS.find { |option| OPTIONS[option.to_sym].nil? }
41
+ puts opts; exit
42
+ end
43
+ end
44
+
45
+ # path = OPTIONS[:path]
46
+
47
+ app_name_or_feed_url = ARGV.last || STDIN.read
48
+
49
+ print Sparkleology::Command.for(app_name_or_feed_url).run
@@ -0,0 +1,76 @@
1
+ require 'sparkleology/version'
2
+
3
+ AUTHOR = 'Dr Nic Williams' # can also be an array of Authors
4
+ EMAIL = "drnicwilliams@gmail.com"
5
+ DESCRIPTION = <<-EOS
6
+ Takes a Mac OS X application name that uses Sparkle for auto-updates, and returns information about that application's Sparkle RSS feed or the latest download URL for that Application.
7
+ EOS
8
+ GEM_NAME = 'sparkleology' # what ppl will type to install your gem
9
+ RUBYFORGE_PROJECT = 'drnicutilities' # The unix name for your project
10
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
11
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
12
+ EXTRA_DEPENDENCIES = [
13
+ ['plist', '>= 3.0.0'],
14
+ ['libxml-ruby', '>= 0.8.3']
15
+ ] # An array of rubygem dependencies [name, version]
16
+
17
+ @config_file = "~/.rubyforge/user-config.yml"
18
+ @config = nil
19
+ RUBYFORGE_USERNAME = "unknown"
20
+ def rubyforge_username
21
+ unless @config
22
+ begin
23
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
24
+ rescue
25
+ puts <<-EOS
26
+ ERROR: No rubyforge config file found: #{@config_file}
27
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
28
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
29
+ EOS
30
+ exit
31
+ end
32
+ end
33
+ RUBYFORGE_USERNAME.replace @config["username"]
34
+ end
35
+
36
+
37
+ REV = nil
38
+ # UNCOMMENT IF REQUIRED:
39
+ # REV = YAML.load(`svn info`)['Revision']
40
+ VERS = Sparkleology::VERSION::STRING + (REV ? ".#{REV}" : "")
41
+ RDOC_OPTS = ['--quiet', '--title', 'sparkleology documentation',
42
+ "--opname", "index.html",
43
+ "--line-numbers",
44
+ "--main", "README",
45
+ "--inline-source"]
46
+
47
+ class Hoe
48
+ def extra_deps
49
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
50
+ @extra_deps
51
+ end
52
+ end
53
+
54
+ # Generate all the Rake tasks
55
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
56
+ $hoe = Hoe.new(GEM_NAME, VERS) do |p|
57
+ p.developer(AUTHOR, EMAIL)
58
+ p.description = DESCRIPTION
59
+ p.summary = DESCRIPTION
60
+ p.url = HOMEPATH
61
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
62
+ p.test_globs = ["test/**/test_*.rb"]
63
+ p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
64
+
65
+ # == Optional
66
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
67
+ #p.extra_deps = EXTRA_DEPENDENCIES
68
+
69
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
70
+ end
71
+
72
+ CHANGES = $hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
73
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
74
+ $hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
75
+ $hoe.rsync_args = '-av --delete --ignore-errors'
76
+ $hoe.spec.post_install_message = File.open(File.dirname(__FILE__) + "/../PostInstall.txt").read rescue ""
@@ -0,0 +1,15 @@
1
+ require 'fileutils'
2
+ include FileUtils
3
+
4
+ require 'rubygems'
5
+ %w[rake hoe newgem rubigen].each do |req_gem|
6
+ begin
7
+ require req_gem
8
+ rescue LoadError
9
+ puts "This Rakefile requires the '#{req_gem}' RubyGem."
10
+ puts "Installation: gem install #{req_gem} -y"
11
+ exit
12
+ end
13
+ end
14
+
15
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
@@ -0,0 +1,21 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ module Sparkleology
5
+ end
6
+
7
+ # core libs
8
+ require "open-uri"
9
+ require "tempfile"
10
+
11
+ # external gems
12
+ require "rubygems"
13
+ gem 'plist'
14
+ require 'plist'
15
+ gem 'libxml-ruby'
16
+ require "xml"
17
+
18
+ require "sparkleology/exceptions"
19
+ require "sparkleology/find_application_command"
20
+ require 'sparkleology/latest_version_command'
21
+ require 'sparkleology/command'
@@ -0,0 +1,11 @@
1
+ class Sparkleology::Command
2
+ def self.for(app_name_or_feed_url)
3
+ feed_url?(app_name_or_feed_url) ?
4
+ Sparkleology::LatestVersionCommand.new(app_name_or_feed_url) :
5
+ Sparkleology::FindApplicationCommand.new(app_name_or_feed_url)
6
+ end
7
+
8
+ def self.feed_url?(app_name_or_feed_url)
9
+ app_name_or_feed_url =~ /:/
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ module Sparkleology
2
+ class InvalidApplicationNameException < Exception; end
3
+ class NonSparkleApplicationException < Exception; end
4
+ end
@@ -0,0 +1,42 @@
1
+ # This command class takes an OS X application name, such as 'Skitch',
2
+ # and returns the Sparkle RSS feed URL containing the latest update
3
+ # information.
4
+ # Pass the application name to the initializer, and call +run+.
5
+ # +Sparkleology::FindApplicationCommand.new('Skitch').run+
6
+ class Sparkleology::FindApplicationCommand
7
+ attr_reader :app_name
8
+
9
+ def initialize(app_name)
10
+ @app_name = app_name
11
+ end
12
+
13
+ def run
14
+ url_for_latest_versions
15
+ end
16
+
17
+ def plist_path
18
+ @plist_path ||= begin
19
+ full_app_name = app_name.gsub(/\.app$/,'') + ".app"
20
+ self.paths.
21
+ map { |root_path| File.join(root_path, full_app_name, "Contents/Info.plist") }.
22
+ find { |plist| File.exists?(plist) }
23
+ end || begin
24
+ raise Sparkleology::InvalidApplicationNameException, app_name
25
+ end
26
+ end
27
+
28
+ def url_for_latest_versions
29
+ application_info = Plist::parse_xml(plist_path)
30
+ application_info['SUFeedURL'] || begin
31
+ raise Sparkleology::NonSparkleApplicationException, app_name
32
+ end
33
+ end
34
+
35
+ def paths
36
+ ["#{home_path}/Applications", "/Applications"]
37
+ end
38
+
39
+ def home_path
40
+ ENV['TEST_HOME'] || ENV['HOME'] || File.expand_path('~')
41
+ end
42
+ end
@@ -0,0 +1,29 @@
1
+ class Sparkleology::LatestVersionCommand
2
+ attr_reader :rss_feed_url
3
+
4
+ def initialize(rss_feed_url)
5
+ @rss_feed_url = rss_feed_url
6
+ end
7
+
8
+ def run
9
+ fetch_feed_in_temp_file
10
+ feed_xml = XML::Document.file(tmp_file_path)
11
+ node = feed_xml.find_first "//rss/channel/item"
12
+ node = node.find_first "enclosure"
13
+ node["url"]
14
+ end
15
+
16
+ def fetch_feed_in_temp_file
17
+ File.open(tmp_file_path, "w+") do |file|
18
+ file << self.fetch_feed
19
+ end
20
+ end
21
+
22
+ def fetch_feed
23
+ open(rss_feed_url).read
24
+ end
25
+
26
+ def tmp_file_path
27
+ File.join(Dir::tmpdir, "sparkleology.rss")
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ module Sparkleology
2
+ module VERSION #:nodoc:
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/sparkleology.rb'}"
9
+ puts "Loading sparkleology gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ GEM_NAME = 'sparkleology' # what ppl will type to install your gem
4
+ RUBYFORGE_PROJECT = 'sparkleology'
5
+
6
+ require 'rubygems'
7
+ begin
8
+ require 'newgem'
9
+ require 'rubyforge'
10
+ rescue LoadError
11
+ puts "\n\nGenerating the website requires the newgem RubyGem"
12
+ puts "Install: gem install newgem\n\n"
13
+ exit(1)
14
+ end
15
+ require 'redcloth'
16
+ require 'syntax/convertors/html'
17
+ require 'erb'
18
+ require File.dirname(__FILE__) + "/../lib/#{GEM_NAME}/version.rb"
19
+
20
+ version = Sparkleology::VERSION::STRING
21
+ download = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
22
+
23
+ def rubyforge_project_id
24
+ RubyForge.new.autoconfig["group_ids"][RUBYFORGE_PROJECT]
25
+ end
26
+
27
+ class Fixnum
28
+ def ordinal
29
+ # teens
30
+ return 'th' if (10..19).include?(self % 100)
31
+ # others
32
+ case self % 10
33
+ when 1: return 'st'
34
+ when 2: return 'nd'
35
+ when 3: return 'rd'
36
+ else return 'th'
37
+ end
38
+ end
39
+ end
40
+
41
+ class Time
42
+ def pretty
43
+ return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
44
+ end
45
+ end
46
+
47
+ def convert_syntax(syntax, source)
48
+ return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
49
+ end
50
+
51
+ if ARGV.length >= 1
52
+ src, template = ARGV
53
+ template ||= File.join(File.dirname(__FILE__), '/../website/template.html.erb')
54
+ else
55
+ puts("Usage: #{File.split($0).last} source.txt [template.html.erb] > output.html")
56
+ exit!
57
+ end
58
+
59
+ template = ERB.new(File.open(template).read)
60
+
61
+ title = nil
62
+ body = nil
63
+ File.open(src) do |fsrc|
64
+ title_text = fsrc.readline
65
+ body_text_template = fsrc.read
66
+ body_text = ERB.new(body_text_template).result(binding)
67
+ syntax_items = []
68
+ body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)</\1>!m){
69
+ ident = syntax_items.length
70
+ element, syntax, source = $1, $2, $3
71
+ syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}</#{element}>"
72
+ "syntax-temp-#{ident}"
73
+ }
74
+ title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
75
+ body = RedCloth.new(body_text).to_html
76
+ body.gsub!(%r!(?:<pre><code>)?syntax-temp-(\d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
77
+ end
78
+ stat = File.stat(src)
79
+ created = stat.ctime
80
+ modified = stat.mtime
81
+
82
+ $stdout << template.result(binding)