├── test ├── test_helper.rb └── test_votigoto.rb ├── History.txt ├── lib ├── votigoto │ ├── version.rb │ ├── show.rb │ └── base.rb ├── votigoto.rb └── digest_auth.rb ├── README.txt ├── Manifest.txt ├── License.txt ├── website ├── template.rhtml ├── index.txt ├── stylesheets │ └── screen.css ├── index.html └── javascripts │ └── rounded_corners_lite.inc.js ├── bin └── tivo_growl ├── scripts └── txt2html ├── Rakefile └── setup.rb /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require File.dirname(__FILE__) + '/../lib/votigoto' 3 | -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | == 0.2.1 2007-08-13 2 | 3 | * Bugfixes 4 | 5 | == 0.2.0 2007-08-09 6 | 7 | * 1 major enhancement: 8 | * Initial release 9 | -------------------------------------------------------------------------------- /lib/votigoto/version.rb: -------------------------------------------------------------------------------- 1 | module Votigoto #:nodoc: 2 | module VERSION #:nodoc: 3 | MAJOR = 0 4 | MINOR = 2 5 | TINY = 1 6 | 7 | STRING = [MAJOR, MINOR, TINY].join('.') 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/test_votigoto.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/test_helper.rb' 2 | 3 | class TestVotigoto < Test::Unit::TestCase 4 | 5 | def setup 6 | end 7 | 8 | def test_truth 9 | assert true 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/votigoto.rb: -------------------------------------------------------------------------------- 1 | module Votigoto 2 | TIMEOUT = 30 3 | end 4 | 5 | require 'net/https' 6 | require 'digest/md5' 7 | require 'digest_auth' 8 | require 'rubygems' 9 | require 'hpricot' 10 | require 'timeout' 11 | require 'uri' 12 | require 'cgi' 13 | require 'votigoto/base' 14 | require 'votigoto/version' 15 | require 'votigoto/show' 16 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | README for votigoto 2 | =================== 3 | 4 | >> require 'votigoto' 5 | => true 6 | >> tivo = Votigoto::Base.new("10.0.0.148","SEKRET_MEDIA_ACCESS_KEY") 7 | => # 8 | >> tivo.shows.first.to_s 9 | => "The Daily Show With Jon Stewart - Senator Joe Biden (D-Del.)." -------------------------------------------------------------------------------- /Manifest.txt: -------------------------------------------------------------------------------- 1 | History.txt 2 | License.txt 3 | Manifest.txt 4 | README.txt 5 | Rakefile 6 | bin/tivo_growl 7 | lib/digest_auth.rb 8 | lib/votigoto.rb 9 | lib/votigoto/base.rb 10 | lib/votigoto/show.rb 11 | lib/votigoto/version.rb 12 | scripts/txt2html 13 | setup.rb 14 | test/test_helper.rb 15 | test/test_votigoto.rb 16 | website/index.html 17 | website/index.txt 18 | website/javascripts/rounded_corners_lite.inc.js 19 | website/stylesheets/screen.css 20 | website/template.rhtml 21 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007 Jesse Newland 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /lib/votigoto/show.rb: -------------------------------------------------------------------------------- 1 | class Votigoto::Show 2 | PROPS = { 3 | :string => %w(title episode_title description source_station program_id series_id), 4 | :int => %w(source_size duration source_channel), 5 | :custom => %w(capture_date content_url details_url in_progress) 6 | } 7 | def initialize(hpricot) 8 | PROPS[:string].each do |property| 9 | eval(%Q[ 10 | @#{property} = hpricot.at("details/#{property.gsub(/_/,'')}").inner_text rescue nil 11 | ]) 12 | end 13 | PROPS[:int].each do |property| 14 | eval(%Q[ 15 | @#{property} = hpricot.at("details/#{property.gsub(/_/,'')}").inner_text.to_i rescue nil 16 | ]) 17 | end 18 | @capture_date = Time.at(hpricot.at("details/capturedate").inner_text.to_i(16)) rescue nil 19 | @content_url = hpricot.at("links/content/url").inner_text rescue nil 20 | @details_url = hpricot.at("links/tivovideodetails/url").inner_text rescue nil 21 | @in_progress = hpricot.at("details/inprogress").inner_text == "Yes" rescue false 22 | end 23 | 24 | (PROPS[:string]+PROPS[:int]+PROPS[:custom]).each do |property| 25 | class_eval "attr_reader :#{property}" 26 | end 27 | 28 | def to_s 29 | if self.episode_title.nil? 30 | return self.title 31 | else 32 | return self.title + " - " + self.episode_title 33 | end 34 | end 35 | 36 | end -------------------------------------------------------------------------------- /website/template.rhtml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | <%= title %> 9 | 10 | 11 | 14 | 29 | 30 | 31 |
32 | 33 |

<%= title %>

34 |
35 |

Get Version

36 | <%= version %> 37 |
38 | <%= body %> 39 |

40 | Jesse Newland, <%= modified.pretty %>
41 | Theme extended from Paul Battley 42 |

43 |
44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /website/index.txt: -------------------------------------------------------------------------------- 1 | h1. votigoto 2 | 3 | Ruby can has TiVo®? 4 | 5 | h2. What? 6 | 7 | Simple Ruby abstraction of the TiVoToGo™ protocol. *Only works on Series2 TiVos*. 8 | 9 | h2. Why? 10 | 11 | Because my office is upstairs, my TiVo is downstairs, and the built in web interface is UGLY. 12 | 13 | h2. Installing 14 | 15 |
sudo gem install votigoto
16 | 17 | h2. Demonstration of usage 18 | 19 |
20 | >> require 'votigoto'
21 | => true
22 | >> tivo = Votigoto::Base.new("10.0.0.148","SEKRET_MEDIA_ACCESS_KEY")
23 | => #
24 | >> tivo.shows.first.to_s
25 | => "The Daily Show With Jon Stewart - Senator Joe Biden (D-Del.)."
26 | 
27 | 28 | h2. Grrr 29 | 30 | If you're on OS X, and have "growlnotify":http://growl.info/documentation/growlnotify.php installed, this gem includes a daemon that will notify you via Growl of new shows on your TiVo: 31 | 32 |
33 | $ tivo_growl 
34 | Please edit ~/.tivo to include your TiVo's IP and Media Access Key
35 | Textmate users: mate ~/.tivo
36 | $ mate .tivo
37 | $ tivo_growl
38 | $
39 | 
40 | 41 | h2. Hacking 42 | 43 | The SVN repo is svn://rubyforge.org/var/svn/votigoto/trunk. To chip in, "pastie":http://pastie.caboo.se your patches and email a link to "Jesse Newland":mailto:jnewland@gmail.com. 44 | 45 | h2. License 46 | 47 | This code is free to use under the terms of the MIT license. 48 | 49 | h2. Disclaimer 50 | 51 | This project is in no way associated with TiVo or TiVo, Inc. TiVo and the TiVo logo are registered trademarks of TiVo Inc. or its subsidiaries. 52 | 53 | h2. Contact 54 | 55 | Comments are welcome. Send an email to "Jesse Newland":mailto:jnewland@gmail.com. 56 | -------------------------------------------------------------------------------- /lib/votigoto/base.rb: -------------------------------------------------------------------------------- 1 | class Votigoto::Base 2 | 3 | def initialize(ip,mak) 4 | @ip = ip 5 | @mak = mak 6 | end 7 | 8 | attr_reader :ip, :mak, :doc 9 | 10 | def last_changed_date(reload=false) 11 | load(reload) 12 | Time.at(@doc.at("/tivocontainer/details/lastchangedate").inner_text.to_i(16)) 13 | end 14 | 15 | def shows(reload=false) 16 | load(reload) 17 | return @shows if @shows 18 | @shows = [] 19 | @doc.search("tivocontainer/item").each do |show| 20 | @shows << Votigoto::Show.new(show) 21 | end 22 | @shows 23 | end 24 | alias_method :to_a, :shows 25 | 26 | def show(program_id,reload=false) 27 | show = shows(reload).select { |show| show.program_id == program_id.to_s } 28 | show.length == 1 ? show[0] : nil 29 | end 30 | 31 | private 32 | 33 | def getxml(uri = "TiVoConnect?Command=QueryContainer&Container=%2FNowPlaying&Recurse=Yes") 34 | begin 35 | uri = URI.parse "https://tivo:#{@mak}@#{@ip}/#{uri}" 36 | rescue URI::InvalidURIError 37 | puts 'Invalid TiVo URI' 38 | end 39 | http = Net::HTTP.new(uri.host, uri.port) 40 | http.use_ssl = true 41 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 42 | xml = '' 43 | begin 44 | Timeout::timeout(Votigoto::TIMEOUT) do 45 | http.start do |http| 46 | response = http.head uri.request_uri 47 | authorization = DigestAuth.gen_auth_header uri, response['www-authenticate'] 48 | response = http.get uri.request_uri, 'Authorization' => authorization 49 | xml = response.body 50 | end 51 | end 52 | end 53 | Hpricot(xml) 54 | end 55 | 56 | def load(reload=false) 57 | if reload 58 | @doc = getxml() 59 | @shows = nil 60 | end 61 | @doc ||= getxml() 62 | end 63 | 64 | end -------------------------------------------------------------------------------- /lib/digest_auth.rb: -------------------------------------------------------------------------------- 1 | # Written by Eric Hodel 2 | ## 3 | # HTTP Digest Authentication 4 | 5 | module DigestAuth 6 | @@nonce_count = -1 7 | @md5 = Digest::MD5.new 8 | CNONCE = @md5.hexdigest("%x" % (Time.now.to_i + rand(65535))) 9 | 10 | def self.gen_auth_header(uri, auth_header, is_IIS = false) 11 | @@nonce_count += 1 12 | 13 | user = CGI.unescape uri.user 14 | password = CGI.unescape uri.password 15 | 16 | auth_header =~ /^(\w+) (.*)/ 17 | 18 | params = {} 19 | $2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 } 20 | 21 | a_1 = "#{user}:#{params['realm']}:#{password}" 22 | a_2 = "GET:#{uri.path}" 23 | request_digest = '' 24 | request_digest << @md5.hexdigest(a_1) 25 | request_digest << ':' << params['nonce'] 26 | request_digest << ':' << ('%08x' % @@nonce_count) 27 | request_digest << ':' << CNONCE 28 | request_digest << ':' << params['qop'] 29 | request_digest << ':' << @md5.hexdigest(a_2) 30 | 31 | header = '' 32 | header << "Digest username=\"#{user}\", " 33 | header << "realm=\"#{params['realm']}\", " 34 | if is_IIS then 35 | header << "qop=\"#{params['qop']}\", " 36 | else 37 | header << "qop=#{params['qop']}, " 38 | end 39 | header << "uri=\"#{uri.path}\", " 40 | header << "nonce=\"#{params['nonce']}\", " 41 | header << "nc=#{'%08x' % @@nonce_count}, " 42 | header << "cnonce=\"#{CNONCE}\", " 43 | header << "response=\"#{@md5.hexdigest(request_digest)}\"" 44 | 45 | return header 46 | end 47 | end 48 | 49 | # if __FILE__ == $0 then 50 | # uri = URI.parse "http://user:password@www.example.com/" 51 | # header = "Digest qop=\"auth\", realm=\"www.example.com\", nonce=\"4107baa081a592a6021660200000cd6c5686ff5f579324402b374d83e2c9\"" 52 | # 53 | # puts DigestAuth.gen_auth_header uri, header 54 | # end 55 | # 56 | -------------------------------------------------------------------------------- /bin/tivo_growl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # modified from code by Alex Payne and Matt Biddulph and Damien Tanner 4 | # requires growlnotify 5 | 6 | require 'rubygems' 7 | require 'votigoto' 8 | require 'fileutils' 9 | require 'daemons' 10 | 11 | template = < Time.parse(last.to_s) } 54 | 55 | new_shows.each do |show| 56 | system "#{growlnotifybin} -n votigoto -t \"#{show.source_station.capitalize}\" -m \"#{show.title}\"" 57 | end 58 | rescue 59 | # system "#{growlnotifybin} -n votigoto -t \"Votigo Error\" -m \"#{$!.to_s.gsub("`","'")}\"" 60 | end 61 | 62 | tf = open(timefile, "w") 63 | tf.write(Time.now.to_s) 64 | tf.close 65 | 66 | sleep(interval * 60) 67 | end -------------------------------------------------------------------------------- /scripts/txt2html: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'rubygems' 4 | require 'redcloth' 5 | require 'syntax/convertors/html' 6 | require 'erb' 7 | require File.dirname(__FILE__) + '/../lib/votigoto/version.rb' 8 | 9 | version = Votigoto::VERSION::STRING 10 | download = 'http://rubyforge.org/projects/votigoto' 11 | 12 | class Fixnum 13 | def ordinal 14 | # teens 15 | return 'th' if (10..19).include?(self % 100) 16 | # others 17 | case self % 10 18 | when 1: return 'st' 19 | when 2: return 'nd' 20 | when 3: return 'rd' 21 | else return 'th' 22 | end 23 | end 24 | end 25 | 26 | class Time 27 | def pretty 28 | return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}" 29 | end 30 | end 31 | 32 | def convert_syntax(syntax, source) 33 | return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^
|
$!,'') 34 | end 35 | 36 | if ARGV.length >= 1 37 | src, template = ARGV 38 | template ||= File.dirname(__FILE__) + '/../website/template.rhtml' 39 | 40 | else 41 | puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html") 42 | exit! 43 | end 44 | 45 | template = ERB.new(File.open(template).read) 46 | 47 | title = nil 48 | body = nil 49 | File.open(src) do |fsrc| 50 | title_text = fsrc.readline 51 | body_text = fsrc.read 52 | syntax_items = [] 53 | body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)!m){ 54 | ident = syntax_items.length 55 | element, syntax, source = $1, $2, $3 56 | syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}" 57 | "syntax-temp-#{ident}" 58 | } 59 | title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip 60 | body = RedCloth.new(body_text).to_html 61 | body.gsub!(%r!(?:
)?syntax-temp-(d+)(?:
)?!){ syntax_items[$1.to_i] } 62 | end 63 | stat = File.stat(src) 64 | created = stat.ctime 65 | modified = stat.mtime 66 | 67 | $stdout << template.result(binding) 68 | -------------------------------------------------------------------------------- /website/stylesheets/screen.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #fff; 3 | font-family: "Georgia", sans-serif; 4 | font-size: 16px; 5 | line-height: 1.6em; 6 | padding: 1.6em 0 0 0; 7 | color: #333; 8 | } 9 | h1, h2, h3, h4, h5, h6 { 10 | color: #444; 11 | } 12 | h1 { 13 | font-family: sans-serif; 14 | font-weight: normal; 15 | font-size: 4em; 16 | line-height: 0.8em; 17 | letter-spacing: -0.1ex; 18 | margin: 5px; 19 | } 20 | li { 21 | padding: 0; 22 | margin: 0; 23 | list-style-type: square; 24 | } 25 | a { 26 | color: #000; 27 | background-color: #ff8; 28 | font-weight: normal; 29 | text-decoration: underline; 30 | } 31 | a:hover { 32 | color: #ff8; 33 | background-color: #000; 34 | text-decoration:none; 35 | } 36 | blockquote { 37 | font-size: 90%; 38 | font-style: italic; 39 | border-left: 1px solid #111; 40 | padding-left: 1em; 41 | } 42 | .caps { 43 | font-size: 80%; 44 | } 45 | 46 | #main { 47 | width: 45em; 48 | padding: 0; 49 | margin: 0 auto; 50 | } 51 | .coda { 52 | text-align: right; 53 | color: #222; 54 | font-size: smaller; 55 | } 56 | 57 | table { 58 | font-size: 90%; 59 | line-height: 1.4em; 60 | color: #ff8; 61 | background-color: #111; 62 | padding: 2px 10px 2px 10px; 63 | border-style: dashed; 64 | } 65 | 66 | th { 67 | color: #fff; 68 | } 69 | 70 | td { 71 | padding: 2px 10px 2px 10px; 72 | } 73 | 74 | .success { 75 | color: #0CC52B; 76 | } 77 | 78 | .failed { 79 | color: #E90A1B; 80 | } 81 | 82 | .unknown { 83 | color: #995000; 84 | } 85 | pre, code { 86 | font-family: monospace; 87 | font-size: 90%; 88 | line-height: 1.4em; 89 | color: #ff8; 90 | background-color: #111; 91 | padding: 2px 10px 2px 10px; 92 | } 93 | .comment { color: #aaa; font-style: italic; } 94 | .keyword { color: #eff; font-weight: bold; } 95 | .punct { color: #eee; font-weight: bold; } 96 | .symbol { color: #0bb; } 97 | .string { color: #6b4; } 98 | .ident { color: #ff8; } 99 | .constant { color: #66f; } 100 | .regex { color: #ec6; } 101 | .number { color: #F99; } 102 | .expr { color: #227; } 103 | 104 | #version { 105 | float: right; 106 | text-align: right; 107 | font-family: sans-serif; 108 | font-weight: normal; 109 | background-color: #ff8; 110 | color: #141331; 111 | padding: 15px 20px 10px 20px; 112 | margin: 0 auto; 113 | margin-top: 15px; 114 | border: 3px solid #141331; 115 | } 116 | 117 | #version .numbers { 118 | display: block; 119 | font-size: 4em; 120 | line-height: 0.8em; 121 | letter-spacing: -0.1ex; 122 | margin-bottom: 15px; 123 | } 124 | 125 | #version p { 126 | text-decoration: none; 127 | color: #141331; 128 | background-color: #ff8; 129 | margin: 0; 130 | padding: 0; 131 | } 132 | 133 | #version a { 134 | text-decoration: none; 135 | color: #141331; 136 | background-color: #ff8; 137 | } 138 | 139 | .clickable { 140 | cursor: pointer; 141 | cursor: hand; 142 | } 143 | 144 | -------------------------------------------------------------------------------- /website/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | votigoto 9 | 10 | 11 | 14 | 29 | 30 | 31 |
32 | 33 |

votigoto

34 |
35 |

Get Version

36 | 0.2.0 37 |
38 |

Ruby can has TiVo®?

39 | 40 | 41 |

What?

42 | 43 | 44 |

Simple Ruby abstraction of the TiVoToGo™ protocol. Only works on Series2 TiVos.

45 | 46 | 47 |

Why?

48 | 49 | 50 |

Because my office is upstairs, my TiVo is downstairs, and the built in web interface is UGLY.

51 | 52 | 53 |

Installing

54 | 55 | 56 |
sudo gem install votigoto
57 | 58 |

Demonstration of usage

59 | 60 | 61 |
 62 | >> require 'votigoto'
 63 | => true
 64 | >> tivo = Votigoto::Base.new("10.0.0.148","SEKRET_MEDIA_ACCESS_KEY")
 65 | => #<Votigoto::Base:0x14095d8 @mak="SEKRET_MEDIA_ACCESS_KEY", @ip="10.0.0.148">
 66 | >> tivo.shows.first.to_s
 67 | => "The Daily Show With Jon Stewart - Senator Joe Biden (D-Del.)." 
 68 | 
69 | 70 |

Grrr

71 | 72 | 73 |

If you’re on OS X, and have growlnotify installed, this gem includes a daemon that will notify you via Growl of new shows on your TiVo:

74 | 75 | 76 |
 77 | $ tivo_growl 
 78 | Please edit ~/.tivo to include your TiVo's IP and Media Access Key
 79 | Textmate users: mate ~/.tivo
 80 | $ mate .tivo
 81 | $ tivo_growl
 82 | $
 83 | 
84 | 85 |

Hacking

86 | 87 | 88 |

The SVN repo is svn://rubyforge.org/var/svn/votigoto/trunk. To chip in, pastie your patches and email a link to Jesse Newland.

89 | 90 | 91 |

License

92 | 93 | 94 |

This code is free to use under the terms of the MIT license.

95 | 96 | 97 |

Disclaimer

98 | 99 | 100 |

This project is in no way associated with TiVo or TiVo, Inc. TiVo and the TiVo logo are registered trademarks of TiVo Inc. or its subsidiaries.

101 | 102 | 103 |

Contact

104 | 105 | 106 |

Comments are welcome. Send an email to Jesse Newland.

107 |

108 | Jesse Newland, 9th August 2007
109 | Theme extended from Paul Battley 110 |

111 |
112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | require 'rake/clean' 4 | require 'rake/testtask' 5 | require 'rake/packagetask' 6 | require 'rake/gempackagetask' 7 | require 'rake/rdoctask' 8 | require 'rake/contrib/rubyforgepublisher' 9 | require 'fileutils' 10 | require 'hoe' 11 | 12 | include FileUtils 13 | require File.join(File.dirname(__FILE__), 'lib', 'votigoto', 'version') 14 | 15 | AUTHOR = 'Jesse Newland' # can also be an array of Authors 16 | EMAIL = "jnewland@gmail.com" 17 | DESCRIPTION = "Ruby abstraction of the TiVoToGo protocol. Access your list of recorded shows and programs on your Tivo." 18 | GEM_NAME = 'votigoto' # what ppl will type to install your gem 19 | 20 | @config_file = "~/.rubyforge/user-config.yml" 21 | @config = nil 22 | def rubyforge_username 23 | unless @config 24 | begin 25 | @config = YAML.load(File.read(File.expand_path(@config_file))) 26 | rescue 27 | puts <<-EOS 28 | ERROR: No rubyforge config file found: #{@config_file}" 29 | Run 'rubyforge setup' to prepare your env for access to Rubyforge 30 | - See http://newgem.rubyforge.org/rubyforge.html for more details 31 | EOS 32 | exit 33 | end 34 | end 35 | @rubyforge_username ||= @config["username"] 36 | end 37 | 38 | RUBYFORGE_PROJECT = 'votigoto' # The unix name for your project 39 | HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org" 40 | DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}" 41 | 42 | NAME = "votigoto" 43 | REV = nil 44 | # UNCOMMENT IF REQUIRED: 45 | # REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil 46 | VERS = Votigoto::VERSION::STRING + (REV ? ".#{REV}" : "") 47 | CLEAN.include ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] 48 | RDOC_OPTS = ['--quiet', '--title', 'votigoto documentation', 49 | "--opname", "index.html", 50 | "--line-numbers", 51 | "--main", "README", 52 | "--inline-source"] 53 | 54 | class Hoe 55 | def extra_deps 56 | @extra_deps.reject { |x| Array(x).first == 'hoe' } 57 | end 58 | end 59 | 60 | # Generate all the Rake tasks 61 | # Run 'rake -T' to see list of generated tasks (from gem root directory) 62 | hoe = Hoe.new(GEM_NAME, VERS) do |p| 63 | p.author = AUTHOR 64 | p.description = DESCRIPTION 65 | p.email = EMAIL 66 | p.summary = DESCRIPTION 67 | p.url = HOMEPATH 68 | p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT 69 | p.test_globs = ["test/**/test_*.rb"] 70 | p.clean_globs |= CLEAN #An array of file patterns to delete on clean. 71 | 72 | # == Optional 73 | p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n") 74 | p.extra_deps = [ ['hpricot', '>= 0.5.145']] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ] 75 | #p.spec_extras = {} # A hash of extra values to set in the gemspec. 76 | end 77 | 78 | CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\n\n") 79 | PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}" 80 | hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc') 81 | 82 | desc 'Generate website files' 83 | task :website_generate do 84 | Dir['website/**/*.txt'].each do |txt| 85 | sh %{ ruby scripts/txt2html #{txt} > #{txt.gsub(/txt$/,'html')} } 86 | end 87 | end 88 | 89 | desc 'Upload website files to rubyforge' 90 | task :website_upload do 91 | host = "#{rubyforge_username}@rubyforge.org" 92 | remote_dir = "/var/www/gforge-projects/#{PATH}/" 93 | local_dir = 'website' 94 | sh %{rsync -aCv #{local_dir}/ #{host}:#{remote_dir}} 95 | end 96 | 97 | desc 'Generate and upload website files' 98 | task :website => [:website_generate, :website_upload, :publish_docs] 99 | 100 | desc 'Release the website and new gem version' 101 | task :deploy => [:check_version, :website, :release] do 102 | puts "Remember to create SVN tag:" 103 | puts "svn copy svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/trunk " + 104 | "svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/tags/REL-#{VERS} " 105 | puts "Suggested comment:" 106 | puts "Tagging release #{CHANGES}" 107 | end 108 | 109 | desc 'Runs tasks website_generate and install_gem as a local deployment of the gem' 110 | task :local_deploy => [:website_generate, :install_gem] 111 | 112 | task :check_version do 113 | unless ENV['VERSION'] 114 | puts 'Must pass a VERSION=x.y.z release version' 115 | exit 116 | end 117 | unless ENV['VERSION'] == VERS 118 | puts "Please update your version.rb to match the release version, currently #{VERS}" 119 | exit 120 | end 121 | end 122 | 123 | 124 | -------------------------------------------------------------------------------- /website/javascripts/rounded_corners_lite.inc.js: -------------------------------------------------------------------------------- 1 | 2 | /**************************************************************** 3 | * * 4 | * curvyCorners * 5 | * ------------ * 6 | * * 7 | * This script generates rounded corners for your divs. * 8 | * * 9 | * Version 1.2.9 * 10 | * Copyright (c) 2006 Cameron Cooke * 11 | * By: Cameron Cooke and Tim Hutchison. * 12 | * * 13 | * * 14 | * Website: http://www.curvycorners.net * 15 | * Email: info@totalinfinity.com * 16 | * Forum: http://www.curvycorners.net/forum/ * 17 | * * 18 | * * 19 | * This library is free software; you can redistribute * 20 | * it and/or modify it under the terms of the GNU * 21 | * Lesser General Public License as published by the * 22 | * Free Software Foundation; either version 2.1 of the * 23 | * License, or (at your option) any later version. * 24 | * * 25 | * This library is distributed in the hope that it will * 26 | * be useful, but WITHOUT ANY WARRANTY; without even the * 27 | * implied warranty of MERCHANTABILITY or FITNESS FOR A * 28 | * PARTICULAR PURPOSE. See the GNU Lesser General Public * 29 | * License for more details. * 30 | * * 31 | * You should have received a copy of the GNU Lesser * 32 | * General Public License along with this library; * 33 | * Inc., 59 Temple Place, Suite 330, Boston, * 34 | * MA 02111-1307 USA * 35 | * * 36 | ****************************************************************/ 37 | 38 | var isIE = navigator.userAgent.toLowerCase().indexOf("msie") > -1; var isMoz = document.implementation && document.implementation.createDocument; var isSafari = ((navigator.userAgent.toLowerCase().indexOf('safari')!=-1)&&(navigator.userAgent.toLowerCase().indexOf('mac')!=-1))?true:false; function curvyCorners() 39 | { if(typeof(arguments[0]) != "object") throw newCurvyError("First parameter of curvyCorners() must be an object."); if(typeof(arguments[1]) != "object" && typeof(arguments[1]) != "string") throw newCurvyError("Second parameter of curvyCorners() must be an object or a class name."); if(typeof(arguments[1]) == "string") 40 | { var startIndex = 0; var boxCol = getElementsByClass(arguments[1]);} 41 | else 42 | { var startIndex = 1; var boxCol = arguments;} 43 | var curvyCornersCol = new Array(); if(arguments[0].validTags) 44 | var validElements = arguments[0].validTags; else 45 | var validElements = ["div"]; for(var i = startIndex, j = boxCol.length; i < j; i++) 46 | { var currentTag = boxCol[i].tagName.toLowerCase(); if(inArray(validElements, currentTag) !== false) 47 | { curvyCornersCol[curvyCornersCol.length] = new curvyObject(arguments[0], boxCol[i]);} 48 | } 49 | this.objects = curvyCornersCol; this.applyCornersToAll = function() 50 | { for(var x = 0, k = this.objects.length; x < k; x++) 51 | { this.objects[x].applyCorners();} 52 | } 53 | } 54 | function curvyObject() 55 | { this.box = arguments[1]; this.settings = arguments[0]; this.topContainer = null; this.bottomContainer = null; this.masterCorners = new Array(); this.contentDIV = null; var boxHeight = get_style(this.box, "height", "height"); var boxWidth = get_style(this.box, "width", "width"); var borderWidth = get_style(this.box, "borderTopWidth", "border-top-width"); var borderColour = get_style(this.box, "borderTopColor", "border-top-color"); var boxColour = get_style(this.box, "backgroundColor", "background-color"); var backgroundImage = get_style(this.box, "backgroundImage", "background-image"); var boxPosition = get_style(this.box, "position", "position"); var boxPadding = get_style(this.box, "paddingTop", "padding-top"); this.boxHeight = parseInt(((boxHeight != "" && boxHeight != "auto" && boxHeight.indexOf("%") == -1)? boxHeight.substring(0, boxHeight.indexOf("px")) : this.box.scrollHeight)); this.boxWidth = parseInt(((boxWidth != "" && boxWidth != "auto" && boxWidth.indexOf("%") == -1)? boxWidth.substring(0, boxWidth.indexOf("px")) : this.box.scrollWidth)); this.borderWidth = parseInt(((borderWidth != "" && borderWidth.indexOf("px") !== -1)? borderWidth.slice(0, borderWidth.indexOf("px")) : 0)); this.boxColour = format_colour(boxColour); this.boxPadding = parseInt(((boxPadding != "" && boxPadding.indexOf("px") !== -1)? boxPadding.slice(0, boxPadding.indexOf("px")) : 0)); this.borderColour = format_colour(borderColour); this.borderString = this.borderWidth + "px" + " solid " + this.borderColour; this.backgroundImage = ((backgroundImage != "none")? backgroundImage : ""); this.boxContent = this.box.innerHTML; if(boxPosition != "absolute") this.box.style.position = "relative"; this.box.style.padding = "0px"; if(isIE && boxWidth == "auto" && boxHeight == "auto") this.box.style.width = "100%"; if(this.settings.autoPad == true && this.boxPadding > 0) 56 | this.box.innerHTML = ""; this.applyCorners = function() 57 | { for(var t = 0; t < 2; t++) 58 | { switch(t) 59 | { case 0: 60 | if(this.settings.tl || this.settings.tr) 61 | { var newMainContainer = document.createElement("DIV"); newMainContainer.style.width = "100%"; newMainContainer.style.fontSize = "1px"; newMainContainer.style.overflow = "hidden"; newMainContainer.style.position = "absolute"; newMainContainer.style.paddingLeft = this.borderWidth + "px"; newMainContainer.style.paddingRight = this.borderWidth + "px"; var topMaxRadius = Math.max(this.settings.tl ? this.settings.tl.radius : 0, this.settings.tr ? this.settings.tr.radius : 0); newMainContainer.style.height = topMaxRadius + "px"; newMainContainer.style.top = 0 - topMaxRadius + "px"; newMainContainer.style.left = 0 - this.borderWidth + "px"; this.topContainer = this.box.appendChild(newMainContainer);} 62 | break; case 1: 63 | if(this.settings.bl || this.settings.br) 64 | { var newMainContainer = document.createElement("DIV"); newMainContainer.style.width = "100%"; newMainContainer.style.fontSize = "1px"; newMainContainer.style.overflow = "hidden"; newMainContainer.style.position = "absolute"; newMainContainer.style.paddingLeft = this.borderWidth + "px"; newMainContainer.style.paddingRight = this.borderWidth + "px"; var botMaxRadius = Math.max(this.settings.bl ? this.settings.bl.radius : 0, this.settings.br ? this.settings.br.radius : 0); newMainContainer.style.height = botMaxRadius + "px"; newMainContainer.style.bottom = 0 - botMaxRadius + "px"; newMainContainer.style.left = 0 - this.borderWidth + "px"; this.bottomContainer = this.box.appendChild(newMainContainer);} 65 | break;} 66 | } 67 | if(this.topContainer) this.box.style.borderTopWidth = "0px"; if(this.bottomContainer) this.box.style.borderBottomWidth = "0px"; var corners = ["tr", "tl", "br", "bl"]; for(var i in corners) 68 | { if(i > -1 < 4) 69 | { var cc = corners[i]; if(!this.settings[cc]) 70 | { if(((cc == "tr" || cc == "tl") && this.topContainer != null) || ((cc == "br" || cc == "bl") && this.bottomContainer != null)) 71 | { var newCorner = document.createElement("DIV"); newCorner.style.position = "relative"; newCorner.style.fontSize = "1px"; newCorner.style.overflow = "hidden"; if(this.backgroundImage == "") 72 | newCorner.style.backgroundColor = this.boxColour; else 73 | newCorner.style.backgroundImage = this.backgroundImage; switch(cc) 74 | { case "tl": 75 | newCorner.style.height = topMaxRadius - this.borderWidth + "px"; newCorner.style.marginRight = this.settings.tr.radius - (this.borderWidth*2) + "px"; newCorner.style.borderLeft = this.borderString; newCorner.style.borderTop = this.borderString; newCorner.style.left = -this.borderWidth + "px"; break; case "tr": 76 | newCorner.style.height = topMaxRadius - this.borderWidth + "px"; newCorner.style.marginLeft = this.settings.tl.radius - (this.borderWidth*2) + "px"; newCorner.style.borderRight = this.borderString; newCorner.style.borderTop = this.borderString; newCorner.style.backgroundPosition = "-" + (topMaxRadius + this.borderWidth) + "px 0px"; newCorner.style.left = this.borderWidth + "px"; break; case "bl": 77 | newCorner.style.height = botMaxRadius - this.borderWidth + "px"; newCorner.style.marginRight = this.settings.br.radius - (this.borderWidth*2) + "px"; newCorner.style.borderLeft = this.borderString; newCorner.style.borderBottom = this.borderString; newCorner.style.left = -this.borderWidth + "px"; newCorner.style.backgroundPosition = "-" + (this.borderWidth) + "px -" + (this.boxHeight + (botMaxRadius + this.borderWidth)) + "px"; break; case "br": 78 | newCorner.style.height = botMaxRadius - this.borderWidth + "px"; newCorner.style.marginLeft = this.settings.bl.radius - (this.borderWidth*2) + "px"; newCorner.style.borderRight = this.borderString; newCorner.style.borderBottom = this.borderString; newCorner.style.left = this.borderWidth + "px" 79 | newCorner.style.backgroundPosition = "-" + (botMaxRadius + this.borderWidth) + "px -" + (this.boxHeight + (botMaxRadius + this.borderWidth)) + "px"; break;} 80 | } 81 | } 82 | else 83 | { if(this.masterCorners[this.settings[cc].radius]) 84 | { var newCorner = this.masterCorners[this.settings[cc].radius].cloneNode(true);} 85 | else 86 | { var newCorner = document.createElement("DIV"); newCorner.style.height = this.settings[cc].radius + "px"; newCorner.style.width = this.settings[cc].radius + "px"; newCorner.style.position = "absolute"; newCorner.style.fontSize = "1px"; newCorner.style.overflow = "hidden"; var borderRadius = parseInt(this.settings[cc].radius - this.borderWidth); for(var intx = 0, j = this.settings[cc].radius; intx < j; intx++) 87 | { if((intx +1) >= borderRadius) 88 | var y1 = -1; else 89 | var y1 = (Math.floor(Math.sqrt(Math.pow(borderRadius, 2) - Math.pow((intx+1), 2))) - 1); if(borderRadius != j) 90 | { if((intx) >= borderRadius) 91 | var y2 = -1; else 92 | var y2 = Math.ceil(Math.sqrt(Math.pow(borderRadius,2) - Math.pow(intx, 2))); if((intx+1) >= j) 93 | var y3 = -1; else 94 | var y3 = (Math.floor(Math.sqrt(Math.pow(j ,2) - Math.pow((intx+1), 2))) - 1);} 95 | if((intx) >= j) 96 | var y4 = -1; else 97 | var y4 = Math.ceil(Math.sqrt(Math.pow(j ,2) - Math.pow(intx, 2))); if(y1 > -1) this.drawPixel(intx, 0, this.boxColour, 100, (y1+1), newCorner, -1, this.settings[cc].radius); if(borderRadius != j) 98 | { for(var inty = (y1 + 1); inty < y2; inty++) 99 | { if(this.settings.antiAlias) 100 | { if(this.backgroundImage != "") 101 | { var borderFract = (pixelFraction(intx, inty, borderRadius) * 100); if(borderFract < 30) 102 | { this.drawPixel(intx, inty, this.borderColour, 100, 1, newCorner, 0, this.settings[cc].radius);} 103 | else 104 | { this.drawPixel(intx, inty, this.borderColour, 100, 1, newCorner, -1, this.settings[cc].radius);} 105 | } 106 | else 107 | { var pixelcolour = BlendColour(this.boxColour, this.borderColour, pixelFraction(intx, inty, borderRadius)); this.drawPixel(intx, inty, pixelcolour, 100, 1, newCorner, 0, this.settings[cc].radius, cc);} 108 | } 109 | } 110 | if(this.settings.antiAlias) 111 | { if(y3 >= y2) 112 | { if (y2 == -1) y2 = 0; this.drawPixel(intx, y2, this.borderColour, 100, (y3 - y2 + 1), newCorner, 0, 0);} 113 | } 114 | else 115 | { if(y3 >= y1) 116 | { this.drawPixel(intx, (y1 + 1), this.borderColour, 100, (y3 - y1), newCorner, 0, 0);} 117 | } 118 | var outsideColour = this.borderColour;} 119 | else 120 | { var outsideColour = this.boxColour; var y3 = y1;} 121 | if(this.settings.antiAlias) 122 | { for(var inty = (y3 + 1); inty < y4; inty++) 123 | { this.drawPixel(intx, inty, outsideColour, (pixelFraction(intx, inty , j) * 100), 1, newCorner, ((this.borderWidth > 0)? 0 : -1), this.settings[cc].radius);} 124 | } 125 | } 126 | this.masterCorners[this.settings[cc].radius] = newCorner.cloneNode(true);} 127 | if(cc != "br") 128 | { for(var t = 0, k = newCorner.childNodes.length; t < k; t++) 129 | { var pixelBar = newCorner.childNodes[t]; var pixelBarTop = parseInt(pixelBar.style.top.substring(0, pixelBar.style.top.indexOf("px"))); var pixelBarLeft = parseInt(pixelBar.style.left.substring(0, pixelBar.style.left.indexOf("px"))); var pixelBarHeight = parseInt(pixelBar.style.height.substring(0, pixelBar.style.height.indexOf("px"))); if(cc == "tl" || cc == "bl"){ pixelBar.style.left = this.settings[cc].radius -pixelBarLeft -1 + "px";} 130 | if(cc == "tr" || cc == "tl"){ pixelBar.style.top = this.settings[cc].radius -pixelBarHeight -pixelBarTop + "px";} 131 | switch(cc) 132 | { case "tr": 133 | pixelBar.style.backgroundPosition = "-" + Math.abs((this.boxWidth - this.settings[cc].radius + this.borderWidth) + pixelBarLeft) + "px -" + Math.abs(this.settings[cc].radius -pixelBarHeight -pixelBarTop - this.borderWidth) + "px"; break; case "tl": 134 | pixelBar.style.backgroundPosition = "-" + Math.abs((this.settings[cc].radius -pixelBarLeft -1) - this.borderWidth) + "px -" + Math.abs(this.settings[cc].radius -pixelBarHeight -pixelBarTop - this.borderWidth) + "px"; break; case "bl": 135 | pixelBar.style.backgroundPosition = "-" + Math.abs((this.settings[cc].radius -pixelBarLeft -1) - this.borderWidth) + "px -" + Math.abs((this.boxHeight + this.settings[cc].radius + pixelBarTop) -this.borderWidth) + "px"; break;} 136 | } 137 | } 138 | } 139 | if(newCorner) 140 | { switch(cc) 141 | { case "tl": 142 | if(newCorner.style.position == "absolute") newCorner.style.top = "0px"; if(newCorner.style.position == "absolute") newCorner.style.left = "0px"; if(this.topContainer) this.topContainer.appendChild(newCorner); break; case "tr": 143 | if(newCorner.style.position == "absolute") newCorner.style.top = "0px"; if(newCorner.style.position == "absolute") newCorner.style.right = "0px"; if(this.topContainer) this.topContainer.appendChild(newCorner); break; case "bl": 144 | if(newCorner.style.position == "absolute") newCorner.style.bottom = "0px"; if(newCorner.style.position == "absolute") newCorner.style.left = "0px"; if(this.bottomContainer) this.bottomContainer.appendChild(newCorner); break; case "br": 145 | if(newCorner.style.position == "absolute") newCorner.style.bottom = "0px"; if(newCorner.style.position == "absolute") newCorner.style.right = "0px"; if(this.bottomContainer) this.bottomContainer.appendChild(newCorner); break;} 146 | } 147 | } 148 | } 149 | var radiusDiff = new Array(); radiusDiff["t"] = Math.abs(this.settings.tl.radius - this.settings.tr.radius) 150 | radiusDiff["b"] = Math.abs(this.settings.bl.radius - this.settings.br.radius); for(z in radiusDiff) 151 | { if(z == "t" || z == "b") 152 | { if(radiusDiff[z]) 153 | { var smallerCornerType = ((this.settings[z + "l"].radius < this.settings[z + "r"].radius)? z +"l" : z +"r"); var newFiller = document.createElement("DIV"); newFiller.style.height = radiusDiff[z] + "px"; newFiller.style.width = this.settings[smallerCornerType].radius+ "px" 154 | newFiller.style.position = "absolute"; newFiller.style.fontSize = "1px"; newFiller.style.overflow = "hidden"; newFiller.style.backgroundColor = this.boxColour; switch(smallerCornerType) 155 | { case "tl": 156 | newFiller.style.bottom = "0px"; newFiller.style.left = "0px"; newFiller.style.borderLeft = this.borderString; this.topContainer.appendChild(newFiller); break; case "tr": 157 | newFiller.style.bottom = "0px"; newFiller.style.right = "0px"; newFiller.style.borderRight = this.borderString; this.topContainer.appendChild(newFiller); break; case "bl": 158 | newFiller.style.top = "0px"; newFiller.style.left = "0px"; newFiller.style.borderLeft = this.borderString; this.bottomContainer.appendChild(newFiller); break; case "br": 159 | newFiller.style.top = "0px"; newFiller.style.right = "0px"; newFiller.style.borderRight = this.borderString; this.bottomContainer.appendChild(newFiller); break;} 160 | } 161 | var newFillerBar = document.createElement("DIV"); newFillerBar.style.position = "relative"; newFillerBar.style.fontSize = "1px"; newFillerBar.style.overflow = "hidden"; newFillerBar.style.backgroundColor = this.boxColour; newFillerBar.style.backgroundImage = this.backgroundImage; switch(z) 162 | { case "t": 163 | if(this.topContainer) 164 | { if(this.settings.tl.radius && this.settings.tr.radius) 165 | { newFillerBar.style.height = topMaxRadius - this.borderWidth + "px"; newFillerBar.style.marginLeft = this.settings.tl.radius - this.borderWidth + "px"; newFillerBar.style.marginRight = this.settings.tr.radius - this.borderWidth + "px"; newFillerBar.style.borderTop = this.borderString; if(this.backgroundImage != "") 166 | newFillerBar.style.backgroundPosition = "-" + (topMaxRadius + this.borderWidth) + "px 0px"; this.topContainer.appendChild(newFillerBar);} 167 | this.box.style.backgroundPosition = "0px -" + (topMaxRadius - this.borderWidth) + "px";} 168 | break; case "b": 169 | if(this.bottomContainer) 170 | { if(this.settings.bl.radius && this.settings.br.radius) 171 | { newFillerBar.style.height = botMaxRadius - this.borderWidth + "px"; newFillerBar.style.marginLeft = this.settings.bl.radius - this.borderWidth + "px"; newFillerBar.style.marginRight = this.settings.br.radius - this.borderWidth + "px"; newFillerBar.style.borderBottom = this.borderString; if(this.backgroundImage != "") 172 | newFillerBar.style.backgroundPosition = "-" + (botMaxRadius + this.borderWidth) + "px -" + (this.boxHeight + (topMaxRadius + this.borderWidth)) + "px"; this.bottomContainer.appendChild(newFillerBar);} 173 | } 174 | break;} 175 | } 176 | } 177 | if(this.settings.autoPad == true && this.boxPadding > 0) 178 | { var contentContainer = document.createElement("DIV"); contentContainer.style.position = "relative"; contentContainer.innerHTML = this.boxContent; contentContainer.className = "autoPadDiv"; var topPadding = Math.abs(topMaxRadius - this.boxPadding); var botPadding = Math.abs(botMaxRadius - this.boxPadding); if(topMaxRadius < this.boxPadding) 179 | contentContainer.style.paddingTop = topPadding + "px"; if(botMaxRadius < this.boxPadding) 180 | contentContainer.style.paddingBottom = botMaxRadius + "px"; contentContainer.style.paddingLeft = this.boxPadding + "px"; contentContainer.style.paddingRight = this.boxPadding + "px"; this.contentDIV = this.box.appendChild(contentContainer);} 181 | } 182 | this.drawPixel = function(intx, inty, colour, transAmount, height, newCorner, image, cornerRadius) 183 | { var pixel = document.createElement("DIV"); pixel.style.height = height + "px"; pixel.style.width = "1px"; pixel.style.position = "absolute"; pixel.style.fontSize = "1px"; pixel.style.overflow = "hidden"; var topMaxRadius = Math.max(this.settings["tr"].radius, this.settings["tl"].radius); if(image == -1 && this.backgroundImage != "") 184 | { pixel.style.backgroundImage = this.backgroundImage; pixel.style.backgroundPosition = "-" + (this.boxWidth - (cornerRadius - intx) + this.borderWidth) + "px -" + ((this.boxHeight + topMaxRadius + inty) -this.borderWidth) + "px";} 185 | else 186 | { pixel.style.backgroundColor = colour;} 187 | if (transAmount != 100) 188 | setOpacity(pixel, transAmount); pixel.style.top = inty + "px"; pixel.style.left = intx + "px"; newCorner.appendChild(pixel);} 189 | } 190 | function insertAfter(parent, node, referenceNode) 191 | { parent.insertBefore(node, referenceNode.nextSibling);} 192 | function BlendColour(Col1, Col2, Col1Fraction) 193 | { var red1 = parseInt(Col1.substr(1,2),16); var green1 = parseInt(Col1.substr(3,2),16); var blue1 = parseInt(Col1.substr(5,2),16); var red2 = parseInt(Col2.substr(1,2),16); var green2 = parseInt(Col2.substr(3,2),16); var blue2 = parseInt(Col2.substr(5,2),16); if(Col1Fraction > 1 || Col1Fraction < 0) Col1Fraction = 1; var endRed = Math.round((red1 * Col1Fraction) + (red2 * (1 - Col1Fraction))); if(endRed > 255) endRed = 255; if(endRed < 0) endRed = 0; var endGreen = Math.round((green1 * Col1Fraction) + (green2 * (1 - Col1Fraction))); if(endGreen > 255) endGreen = 255; if(endGreen < 0) endGreen = 0; var endBlue = Math.round((blue1 * Col1Fraction) + (blue2 * (1 - Col1Fraction))); if(endBlue > 255) endBlue = 255; if(endBlue < 0) endBlue = 0; return "#" + IntToHex(endRed)+ IntToHex(endGreen)+ IntToHex(endBlue);} 194 | function IntToHex(strNum) 195 | { base = strNum / 16; rem = strNum % 16; base = base - (rem / 16); baseS = MakeHex(base); remS = MakeHex(rem); return baseS + '' + remS;} 196 | function MakeHex(x) 197 | { if((x >= 0) && (x <= 9)) 198 | { return x;} 199 | else 200 | { switch(x) 201 | { case 10: return "A"; case 11: return "B"; case 12: return "C"; case 13: return "D"; case 14: return "E"; case 15: return "F";} 202 | } 203 | } 204 | function pixelFraction(x, y, r) 205 | { var pixelfraction = 0; var xvalues = new Array(1); var yvalues = new Array(1); var point = 0; var whatsides = ""; var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(x,2))); if ((intersect >= y) && (intersect < (y+1))) 206 | { whatsides = "Left"; xvalues[point] = 0; yvalues[point] = intersect - y; point = point + 1;} 207 | var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(y+1,2))); if ((intersect >= x) && (intersect < (x+1))) 208 | { whatsides = whatsides + "Top"; xvalues[point] = intersect - x; yvalues[point] = 1; point = point + 1;} 209 | var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(x+1,2))); if ((intersect >= y) && (intersect < (y+1))) 210 | { whatsides = whatsides + "Right"; xvalues[point] = 1; yvalues[point] = intersect - y; point = point + 1;} 211 | var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(y,2))); if ((intersect >= x) && (intersect < (x+1))) 212 | { whatsides = whatsides + "Bottom"; xvalues[point] = intersect - x; yvalues[point] = 0;} 213 | switch (whatsides) 214 | { case "LeftRight": 215 | pixelfraction = Math.min(yvalues[0],yvalues[1]) + ((Math.max(yvalues[0],yvalues[1]) - Math.min(yvalues[0],yvalues[1]))/2); break; case "TopRight": 216 | pixelfraction = 1-(((1-xvalues[0])*(1-yvalues[1]))/2); break; case "TopBottom": 217 | pixelfraction = Math.min(xvalues[0],xvalues[1]) + ((Math.max(xvalues[0],xvalues[1]) - Math.min(xvalues[0],xvalues[1]))/2); break; case "LeftBottom": 218 | pixelfraction = (yvalues[0]*xvalues[1])/2; break; default: 219 | pixelfraction = 1;} 220 | return pixelfraction;} 221 | function rgb2Hex(rgbColour) 222 | { try{ var rgbArray = rgb2Array(rgbColour); var red = parseInt(rgbArray[0]); var green = parseInt(rgbArray[1]); var blue = parseInt(rgbArray[2]); var hexColour = "#" + IntToHex(red) + IntToHex(green) + IntToHex(blue);} 223 | catch(e){ alert("There was an error converting the RGB value to Hexadecimal in function rgb2Hex");} 224 | return hexColour;} 225 | function rgb2Array(rgbColour) 226 | { var rgbValues = rgbColour.substring(4, rgbColour.indexOf(")")); var rgbArray = rgbValues.split(", "); return rgbArray;} 227 | function setOpacity(obj, opacity) 228 | { opacity = (opacity == 100)?99.999:opacity; if(isSafari && obj.tagName != "IFRAME") 229 | { var rgbArray = rgb2Array(obj.style.backgroundColor); var red = parseInt(rgbArray[0]); var green = parseInt(rgbArray[1]); var blue = parseInt(rgbArray[2]); obj.style.backgroundColor = "rgba(" + red + ", " + green + ", " + blue + ", " + opacity/100 + ")";} 230 | else if(typeof(obj.style.opacity) != "undefined") 231 | { obj.style.opacity = opacity/100;} 232 | else if(typeof(obj.style.MozOpacity) != "undefined") 233 | { obj.style.MozOpacity = opacity/100;} 234 | else if(typeof(obj.style.filter) != "undefined") 235 | { obj.style.filter = "alpha(opacity:" + opacity + ")";} 236 | else if(typeof(obj.style.KHTMLOpacity) != "undefined") 237 | { obj.style.KHTMLOpacity = opacity/100;} 238 | } 239 | function inArray(array, value) 240 | { for(var i = 0; i < array.length; i++){ if (array[i] === value) return i;} 241 | return false;} 242 | function inArrayKey(array, value) 243 | { for(key in array){ if(key === value) return true;} 244 | return false;} 245 | function addEvent(elm, evType, fn, useCapture) { if (elm.addEventListener) { elm.addEventListener(evType, fn, useCapture); return true;} 246 | else if (elm.attachEvent) { var r = elm.attachEvent('on' + evType, fn); return r;} 247 | else { elm['on' + evType] = fn;} 248 | } 249 | function removeEvent(obj, evType, fn, useCapture){ if (obj.removeEventListener){ obj.removeEventListener(evType, fn, useCapture); return true;} else if (obj.detachEvent){ var r = obj.detachEvent("on"+evType, fn); return r;} else { alert("Handler could not be removed");} 250 | } 251 | function format_colour(colour) 252 | { var returnColour = "#ffffff"; if(colour != "" && colour != "transparent") 253 | { if(colour.substr(0, 3) == "rgb") 254 | { returnColour = rgb2Hex(colour);} 255 | else if(colour.length == 4) 256 | { returnColour = "#" + colour.substring(1, 2) + colour.substring(1, 2) + colour.substring(2, 3) + colour.substring(2, 3) + colour.substring(3, 4) + colour.substring(3, 4);} 257 | else 258 | { returnColour = colour;} 259 | } 260 | return returnColour;} 261 | function get_style(obj, property, propertyNS) 262 | { try 263 | { if(obj.currentStyle) 264 | { var returnVal = eval("obj.currentStyle." + property);} 265 | else 266 | { if(isSafari && obj.style.display == "none") 267 | { obj.style.display = ""; var wasHidden = true;} 268 | var returnVal = document.defaultView.getComputedStyle(obj, '').getPropertyValue(propertyNS); if(isSafari && wasHidden) 269 | { obj.style.display = "none";} 270 | } 271 | } 272 | catch(e) 273 | { } 274 | return returnVal;} 275 | function getElementsByClass(searchClass, node, tag) 276 | { var classElements = new Array(); if(node == null) 277 | node = document; if(tag == null) 278 | tag = '*'; var els = node.getElementsByTagName(tag); var elsLen = els.length; var pattern = new RegExp("(^|\s)"+searchClass+"(\s|$)"); for (i = 0, j = 0; i < elsLen; i++) 279 | { if(pattern.test(els[i].className)) 280 | { classElements[j] = els[i]; j++;} 281 | } 282 | return classElements;} 283 | function newCurvyError(errorMessage) 284 | { return new Error("curvyCorners Error:\n" + errorMessage) 285 | } 286 | -------------------------------------------------------------------------------- /setup.rb: -------------------------------------------------------------------------------- 1 | # 2 | # setup.rb 3 | # 4 | # Copyright (c) 2000-2005 Minero Aoki 5 | # 6 | # This program is free software. 7 | # You can distribute/modify this program under the terms of 8 | # the GNU LGPL, Lesser General Public License version 2.1. 9 | # 10 | 11 | unless Enumerable.method_defined?(:map) # Ruby 1.4.6 12 | module Enumerable 13 | alias map collect 14 | end 15 | end 16 | 17 | unless File.respond_to?(:read) # Ruby 1.6 18 | def File.read(fname) 19 | open(fname) {|f| 20 | return f.read 21 | } 22 | end 23 | end 24 | 25 | unless Errno.const_defined?(:ENOTEMPTY) # Windows? 26 | module Errno 27 | class ENOTEMPTY 28 | # We do not raise this exception, implementation is not needed. 29 | end 30 | end 31 | end 32 | 33 | def File.binread(fname) 34 | open(fname, 'rb') {|f| 35 | return f.read 36 | } 37 | end 38 | 39 | # for corrupted Windows' stat(2) 40 | def File.dir?(path) 41 | File.directory?((path[-1,1] == '/') ? path : path + '/') 42 | end 43 | 44 | 45 | class ConfigTable 46 | 47 | include Enumerable 48 | 49 | def initialize(rbconfig) 50 | @rbconfig = rbconfig 51 | @items = [] 52 | @table = {} 53 | # options 54 | @install_prefix = nil 55 | @config_opt = nil 56 | @verbose = true 57 | @no_harm = false 58 | end 59 | 60 | attr_accessor :install_prefix 61 | attr_accessor :config_opt 62 | 63 | attr_writer :verbose 64 | 65 | def verbose? 66 | @verbose 67 | end 68 | 69 | attr_writer :no_harm 70 | 71 | def no_harm? 72 | @no_harm 73 | end 74 | 75 | def [](key) 76 | lookup(key).resolve(self) 77 | end 78 | 79 | def []=(key, val) 80 | lookup(key).set val 81 | end 82 | 83 | def names 84 | @items.map {|i| i.name } 85 | end 86 | 87 | def each(&block) 88 | @items.each(&block) 89 | end 90 | 91 | def key?(name) 92 | @table.key?(name) 93 | end 94 | 95 | def lookup(name) 96 | @table[name] or setup_rb_error "no such config item: #{name}" 97 | end 98 | 99 | def add(item) 100 | @items.push item 101 | @table[item.name] = item 102 | end 103 | 104 | def remove(name) 105 | item = lookup(name) 106 | @items.delete_if {|i| i.name == name } 107 | @table.delete_if {|name, i| i.name == name } 108 | item 109 | end 110 | 111 | def load_script(path, inst = nil) 112 | if File.file?(path) 113 | MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path 114 | end 115 | end 116 | 117 | def savefile 118 | '.config' 119 | end 120 | 121 | def load_savefile 122 | begin 123 | File.foreach(savefile()) do |line| 124 | k, v = *line.split(/=/, 2) 125 | self[k] = v.strip 126 | end 127 | rescue Errno::ENOENT 128 | setup_rb_error $!.message + "\n#{File.basename($0)} config first" 129 | end 130 | end 131 | 132 | def save 133 | @items.each {|i| i.value } 134 | File.open(savefile(), 'w') {|f| 135 | @items.each do |i| 136 | f.printf "%s=%s\n", i.name, i.value if i.value? and i.value 137 | end 138 | } 139 | end 140 | 141 | def load_standard_entries 142 | standard_entries(@rbconfig).each do |ent| 143 | add ent 144 | end 145 | end 146 | 147 | def standard_entries(rbconfig) 148 | c = rbconfig 149 | 150 | rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT']) 151 | 152 | major = c['MAJOR'].to_i 153 | minor = c['MINOR'].to_i 154 | teeny = c['TEENY'].to_i 155 | version = "#{major}.#{minor}" 156 | 157 | # ruby ver. >= 1.4.4? 158 | newpath_p = ((major >= 2) or 159 | ((major == 1) and 160 | ((minor >= 5) or 161 | ((minor == 4) and (teeny >= 4))))) 162 | 163 | if c['rubylibdir'] 164 | # V > 1.6.3 165 | libruby = "#{c['prefix']}/lib/ruby" 166 | librubyver = c['rubylibdir'] 167 | librubyverarch = c['archdir'] 168 | siteruby = c['sitedir'] 169 | siterubyver = c['sitelibdir'] 170 | siterubyverarch = c['sitearchdir'] 171 | elsif newpath_p 172 | # 1.4.4 <= V <= 1.6.3 173 | libruby = "#{c['prefix']}/lib/ruby" 174 | librubyver = "#{c['prefix']}/lib/ruby/#{version}" 175 | librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" 176 | siteruby = c['sitedir'] 177 | siterubyver = "$siteruby/#{version}" 178 | siterubyverarch = "$siterubyver/#{c['arch']}" 179 | else 180 | # V < 1.4.4 181 | libruby = "#{c['prefix']}/lib/ruby" 182 | librubyver = "#{c['prefix']}/lib/ruby/#{version}" 183 | librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" 184 | siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby" 185 | siterubyver = siteruby 186 | siterubyverarch = "$siterubyver/#{c['arch']}" 187 | end 188 | parameterize = lambda {|path| 189 | path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix') 190 | } 191 | 192 | if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } 193 | makeprog = arg.sub(/'/, '').split(/=/, 2)[1] 194 | else 195 | makeprog = 'make' 196 | end 197 | 198 | [ 199 | ExecItem.new('installdirs', 'std/site/home', 200 | 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\ 201 | {|val, table| 202 | case val 203 | when 'std' 204 | table['rbdir'] = '$librubyver' 205 | table['sodir'] = '$librubyverarch' 206 | when 'site' 207 | table['rbdir'] = '$siterubyver' 208 | table['sodir'] = '$siterubyverarch' 209 | when 'home' 210 | setup_rb_error '$HOME was not set' unless ENV['HOME'] 211 | table['prefix'] = ENV['HOME'] 212 | table['rbdir'] = '$libdir/ruby' 213 | table['sodir'] = '$libdir/ruby' 214 | end 215 | }, 216 | PathItem.new('prefix', 'path', c['prefix'], 217 | 'path prefix of target environment'), 218 | PathItem.new('bindir', 'path', parameterize.call(c['bindir']), 219 | 'the directory for commands'), 220 | PathItem.new('libdir', 'path', parameterize.call(c['libdir']), 221 | 'the directory for libraries'), 222 | PathItem.new('datadir', 'path', parameterize.call(c['datadir']), 223 | 'the directory for shared data'), 224 | PathItem.new('mandir', 'path', parameterize.call(c['mandir']), 225 | 'the directory for man pages'), 226 | PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), 227 | 'the directory for system configuration files'), 228 | PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']), 229 | 'the directory for local state data'), 230 | PathItem.new('libruby', 'path', libruby, 231 | 'the directory for ruby libraries'), 232 | PathItem.new('librubyver', 'path', librubyver, 233 | 'the directory for standard ruby libraries'), 234 | PathItem.new('librubyverarch', 'path', librubyverarch, 235 | 'the directory for standard ruby extensions'), 236 | PathItem.new('siteruby', 'path', siteruby, 237 | 'the directory for version-independent aux ruby libraries'), 238 | PathItem.new('siterubyver', 'path', siterubyver, 239 | 'the directory for aux ruby libraries'), 240 | PathItem.new('siterubyverarch', 'path', siterubyverarch, 241 | 'the directory for aux ruby binaries'), 242 | PathItem.new('rbdir', 'path', '$siterubyver', 243 | 'the directory for ruby scripts'), 244 | PathItem.new('sodir', 'path', '$siterubyverarch', 245 | 'the directory for ruby extentions'), 246 | PathItem.new('rubypath', 'path', rubypath, 247 | 'the path to set to #! line'), 248 | ProgramItem.new('rubyprog', 'name', rubypath, 249 | 'the ruby program using for installation'), 250 | ProgramItem.new('makeprog', 'name', makeprog, 251 | 'the make program to compile ruby extentions'), 252 | SelectItem.new('shebang', 'all/ruby/never', 'ruby', 253 | 'shebang line (#!) editing mode'), 254 | BoolItem.new('without-ext', 'yes/no', 'no', 255 | 'does not compile/install ruby extentions') 256 | ] 257 | end 258 | private :standard_entries 259 | 260 | def load_multipackage_entries 261 | multipackage_entries().each do |ent| 262 | add ent 263 | end 264 | end 265 | 266 | def multipackage_entries 267 | [ 268 | PackageSelectionItem.new('with', 'name,name...', '', 'ALL', 269 | 'package names that you want to install'), 270 | PackageSelectionItem.new('without', 'name,name...', '', 'NONE', 271 | 'package names that you do not want to install') 272 | ] 273 | end 274 | private :multipackage_entries 275 | 276 | ALIASES = { 277 | 'std-ruby' => 'librubyver', 278 | 'stdruby' => 'librubyver', 279 | 'rubylibdir' => 'librubyver', 280 | 'archdir' => 'librubyverarch', 281 | 'site-ruby-common' => 'siteruby', # For backward compatibility 282 | 'site-ruby' => 'siterubyver', # For backward compatibility 283 | 'bin-dir' => 'bindir', 284 | 'bin-dir' => 'bindir', 285 | 'rb-dir' => 'rbdir', 286 | 'so-dir' => 'sodir', 287 | 'data-dir' => 'datadir', 288 | 'ruby-path' => 'rubypath', 289 | 'ruby-prog' => 'rubyprog', 290 | 'ruby' => 'rubyprog', 291 | 'make-prog' => 'makeprog', 292 | 'make' => 'makeprog' 293 | } 294 | 295 | def fixup 296 | ALIASES.each do |ali, name| 297 | @table[ali] = @table[name] 298 | end 299 | @items.freeze 300 | @table.freeze 301 | @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/ 302 | end 303 | 304 | def parse_opt(opt) 305 | m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}" 306 | m.to_a[1,2] 307 | end 308 | 309 | def dllext 310 | @rbconfig['DLEXT'] 311 | end 312 | 313 | def value_config?(name) 314 | lookup(name).value? 315 | end 316 | 317 | class Item 318 | def initialize(name, template, default, desc) 319 | @name = name.freeze 320 | @template = template 321 | @value = default 322 | @default = default 323 | @description = desc 324 | end 325 | 326 | attr_reader :name 327 | attr_reader :description 328 | 329 | attr_accessor :default 330 | alias help_default default 331 | 332 | def help_opt 333 | "--#{@name}=#{@template}" 334 | end 335 | 336 | def value? 337 | true 338 | end 339 | 340 | def value 341 | @value 342 | end 343 | 344 | def resolve(table) 345 | @value.gsub(%r<\$([^/]+)>) { table[$1] } 346 | end 347 | 348 | def set(val) 349 | @value = check(val) 350 | end 351 | 352 | private 353 | 354 | def check(val) 355 | setup_rb_error "config: --#{name} requires argument" unless val 356 | val 357 | end 358 | end 359 | 360 | class BoolItem < Item 361 | def config_type 362 | 'bool' 363 | end 364 | 365 | def help_opt 366 | "--#{@name}" 367 | end 368 | 369 | private 370 | 371 | def check(val) 372 | return 'yes' unless val 373 | case val 374 | when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes' 375 | when /\An(o)?\z/i, /\Af(alse)\z/i then 'no' 376 | else 377 | setup_rb_error "config: --#{@name} accepts only yes/no for argument" 378 | end 379 | end 380 | end 381 | 382 | class PathItem < Item 383 | def config_type 384 | 'path' 385 | end 386 | 387 | private 388 | 389 | def check(path) 390 | setup_rb_error "config: --#{@name} requires argument" unless path 391 | path[0,1] == '$' ? path : File.expand_path(path) 392 | end 393 | end 394 | 395 | class ProgramItem < Item 396 | def config_type 397 | 'program' 398 | end 399 | end 400 | 401 | class SelectItem < Item 402 | def initialize(name, selection, default, desc) 403 | super 404 | @ok = selection.split('/') 405 | end 406 | 407 | def config_type 408 | 'select' 409 | end 410 | 411 | private 412 | 413 | def check(val) 414 | unless @ok.include?(val.strip) 415 | setup_rb_error "config: use --#{@name}=#{@template} (#{val})" 416 | end 417 | val.strip 418 | end 419 | end 420 | 421 | class ExecItem < Item 422 | def initialize(name, selection, desc, &block) 423 | super name, selection, nil, desc 424 | @ok = selection.split('/') 425 | @action = block 426 | end 427 | 428 | def config_type 429 | 'exec' 430 | end 431 | 432 | def value? 433 | false 434 | end 435 | 436 | def resolve(table) 437 | setup_rb_error "$#{name()} wrongly used as option value" 438 | end 439 | 440 | undef set 441 | 442 | def evaluate(val, table) 443 | v = val.strip.downcase 444 | unless @ok.include?(v) 445 | setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})" 446 | end 447 | @action.call v, table 448 | end 449 | end 450 | 451 | class PackageSelectionItem < Item 452 | def initialize(name, template, default, help_default, desc) 453 | super name, template, default, desc 454 | @help_default = help_default 455 | end 456 | 457 | attr_reader :help_default 458 | 459 | def config_type 460 | 'package' 461 | end 462 | 463 | private 464 | 465 | def check(val) 466 | unless File.dir?("packages/#{val}") 467 | setup_rb_error "config: no such package: #{val}" 468 | end 469 | val 470 | end 471 | end 472 | 473 | class MetaConfigEnvironment 474 | def initialize(config, installer) 475 | @config = config 476 | @installer = installer 477 | end 478 | 479 | def config_names 480 | @config.names 481 | end 482 | 483 | def config?(name) 484 | @config.key?(name) 485 | end 486 | 487 | def bool_config?(name) 488 | @config.lookup(name).config_type == 'bool' 489 | end 490 | 491 | def path_config?(name) 492 | @config.lookup(name).config_type == 'path' 493 | end 494 | 495 | def value_config?(name) 496 | @config.lookup(name).config_type != 'exec' 497 | end 498 | 499 | def add_config(item) 500 | @config.add item 501 | end 502 | 503 | def add_bool_config(name, default, desc) 504 | @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) 505 | end 506 | 507 | def add_path_config(name, default, desc) 508 | @config.add PathItem.new(name, 'path', default, desc) 509 | end 510 | 511 | def set_config_default(name, default) 512 | @config.lookup(name).default = default 513 | end 514 | 515 | def remove_config(name) 516 | @config.remove(name) 517 | end 518 | 519 | # For only multipackage 520 | def packages 521 | raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer 522 | @installer.packages 523 | end 524 | 525 | # For only multipackage 526 | def declare_packages(list) 527 | raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer 528 | @installer.packages = list 529 | end 530 | end 531 | 532 | end # class ConfigTable 533 | 534 | 535 | # This module requires: #verbose?, #no_harm? 536 | module FileOperations 537 | 538 | def mkdir_p(dirname, prefix = nil) 539 | dirname = prefix + File.expand_path(dirname) if prefix 540 | $stderr.puts "mkdir -p #{dirname}" if verbose? 541 | return if no_harm? 542 | 543 | # Does not check '/', it's too abnormal. 544 | dirs = File.expand_path(dirname).split(%r<(?=/)>) 545 | if /\A[a-z]:\z/i =~ dirs[0] 546 | disk = dirs.shift 547 | dirs[0] = disk + dirs[0] 548 | end 549 | dirs.each_index do |idx| 550 | path = dirs[0..idx].join('') 551 | Dir.mkdir path unless File.dir?(path) 552 | end 553 | end 554 | 555 | def rm_f(path) 556 | $stderr.puts "rm -f #{path}" if verbose? 557 | return if no_harm? 558 | force_remove_file path 559 | end 560 | 561 | def rm_rf(path) 562 | $stderr.puts "rm -rf #{path}" if verbose? 563 | return if no_harm? 564 | remove_tree path 565 | end 566 | 567 | def remove_tree(path) 568 | if File.symlink?(path) 569 | remove_file path 570 | elsif File.dir?(path) 571 | remove_tree0 path 572 | else 573 | force_remove_file path 574 | end 575 | end 576 | 577 | def remove_tree0(path) 578 | Dir.foreach(path) do |ent| 579 | next if ent == '.' 580 | next if ent == '..' 581 | entpath = "#{path}/#{ent}" 582 | if File.symlink?(entpath) 583 | remove_file entpath 584 | elsif File.dir?(entpath) 585 | remove_tree0 entpath 586 | else 587 | force_remove_file entpath 588 | end 589 | end 590 | begin 591 | Dir.rmdir path 592 | rescue Errno::ENOTEMPTY 593 | # directory may not be empty 594 | end 595 | end 596 | 597 | def move_file(src, dest) 598 | force_remove_file dest 599 | begin 600 | File.rename src, dest 601 | rescue 602 | File.open(dest, 'wb') {|f| 603 | f.write File.binread(src) 604 | } 605 | File.chmod File.stat(src).mode, dest 606 | File.unlink src 607 | end 608 | end 609 | 610 | def force_remove_file(path) 611 | begin 612 | remove_file path 613 | rescue 614 | end 615 | end 616 | 617 | def remove_file(path) 618 | File.chmod 0777, path 619 | File.unlink path 620 | end 621 | 622 | def install(from, dest, mode, prefix = nil) 623 | $stderr.puts "install #{from} #{dest}" if verbose? 624 | return if no_harm? 625 | 626 | realdest = prefix ? prefix + File.expand_path(dest) : dest 627 | realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) 628 | str = File.binread(from) 629 | if diff?(str, realdest) 630 | verbose_off { 631 | rm_f realdest if File.exist?(realdest) 632 | } 633 | File.open(realdest, 'wb') {|f| 634 | f.write str 635 | } 636 | File.chmod mode, realdest 637 | 638 | File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| 639 | if prefix 640 | f.puts realdest.sub(prefix, '') 641 | else 642 | f.puts realdest 643 | end 644 | } 645 | end 646 | end 647 | 648 | def diff?(new_content, path) 649 | return true unless File.exist?(path) 650 | new_content != File.binread(path) 651 | end 652 | 653 | def command(*args) 654 | $stderr.puts args.join(' ') if verbose? 655 | system(*args) or raise RuntimeError, 656 | "system(#{args.map{|a| a.inspect }.join(' ')}) failed" 657 | end 658 | 659 | def ruby(*args) 660 | command config('rubyprog'), *args 661 | end 662 | 663 | def make(task = nil) 664 | command(*[config('makeprog'), task].compact) 665 | end 666 | 667 | def extdir?(dir) 668 | File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb") 669 | end 670 | 671 | def files_of(dir) 672 | Dir.open(dir) {|d| 673 | return d.select {|ent| File.file?("#{dir}/#{ent}") } 674 | } 675 | end 676 | 677 | DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn ) 678 | 679 | def directories_of(dir) 680 | Dir.open(dir) {|d| 681 | return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT 682 | } 683 | end 684 | 685 | end 686 | 687 | 688 | # This module requires: #srcdir_root, #objdir_root, #relpath 689 | module HookScriptAPI 690 | 691 | def get_config(key) 692 | @config[key] 693 | end 694 | 695 | alias config get_config 696 | 697 | # obsolete: use metaconfig to change configuration 698 | def set_config(key, val) 699 | @config[key] = val 700 | end 701 | 702 | # 703 | # srcdir/objdir (works only in the package directory) 704 | # 705 | 706 | def curr_srcdir 707 | "#{srcdir_root()}/#{relpath()}" 708 | end 709 | 710 | def curr_objdir 711 | "#{objdir_root()}/#{relpath()}" 712 | end 713 | 714 | def srcfile(path) 715 | "#{curr_srcdir()}/#{path}" 716 | end 717 | 718 | def srcexist?(path) 719 | File.exist?(srcfile(path)) 720 | end 721 | 722 | def srcdirectory?(path) 723 | File.dir?(srcfile(path)) 724 | end 725 | 726 | def srcfile?(path) 727 | File.file?(srcfile(path)) 728 | end 729 | 730 | def srcentries(path = '.') 731 | Dir.open("#{curr_srcdir()}/#{path}") {|d| 732 | return d.to_a - %w(. ..) 733 | } 734 | end 735 | 736 | def srcfiles(path = '.') 737 | srcentries(path).select {|fname| 738 | File.file?(File.join(curr_srcdir(), path, fname)) 739 | } 740 | end 741 | 742 | def srcdirectories(path = '.') 743 | srcentries(path).select {|fname| 744 | File.dir?(File.join(curr_srcdir(), path, fname)) 745 | } 746 | end 747 | 748 | end 749 | 750 | 751 | class ToplevelInstaller 752 | 753 | Version = '3.4.1' 754 | Copyright = 'Copyright (c) 2000-2005 Minero Aoki' 755 | 756 | TASKS = [ 757 | [ 'all', 'do config, setup, then install' ], 758 | [ 'config', 'saves your configurations' ], 759 | [ 'show', 'shows current configuration' ], 760 | [ 'setup', 'compiles ruby extentions and others' ], 761 | [ 'install', 'installs files' ], 762 | [ 'test', 'run all tests in test/' ], 763 | [ 'clean', "does `make clean' for each extention" ], 764 | [ 'distclean',"does `make distclean' for each extention" ] 765 | ] 766 | 767 | def ToplevelInstaller.invoke 768 | config = ConfigTable.new(load_rbconfig()) 769 | config.load_standard_entries 770 | config.load_multipackage_entries if multipackage? 771 | config.fixup 772 | klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller) 773 | klass.new(File.dirname($0), config).invoke 774 | end 775 | 776 | def ToplevelInstaller.multipackage? 777 | File.dir?(File.dirname($0) + '/packages') 778 | end 779 | 780 | def ToplevelInstaller.load_rbconfig 781 | if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } 782 | ARGV.delete(arg) 783 | load File.expand_path(arg.split(/=/, 2)[1]) 784 | $".push 'rbconfig.rb' 785 | else 786 | require 'rbconfig' 787 | end 788 | ::Config::CONFIG 789 | end 790 | 791 | def initialize(ardir_root, config) 792 | @ardir = File.expand_path(ardir_root) 793 | @config = config 794 | # cache 795 | @valid_task_re = nil 796 | end 797 | 798 | def config(key) 799 | @config[key] 800 | end 801 | 802 | def inspect 803 | "#<#{self.class} #{__id__()}>" 804 | end 805 | 806 | def invoke 807 | run_metaconfigs 808 | case task = parsearg_global() 809 | when nil, 'all' 810 | parsearg_config 811 | init_installers 812 | exec_config 813 | exec_setup 814 | exec_install 815 | else 816 | case task 817 | when 'config', 'test' 818 | ; 819 | when 'clean', 'distclean' 820 | @config.load_savefile if File.exist?(@config.savefile) 821 | else 822 | @config.load_savefile 823 | end 824 | __send__ "parsearg_#{task}" 825 | init_installers 826 | __send__ "exec_#{task}" 827 | end 828 | end 829 | 830 | def run_metaconfigs 831 | @config.load_script "#{@ardir}/metaconfig" 832 | end 833 | 834 | def init_installers 835 | @installer = Installer.new(@config, @ardir, File.expand_path('.')) 836 | end 837 | 838 | # 839 | # Hook Script API bases 840 | # 841 | 842 | def srcdir_root 843 | @ardir 844 | end 845 | 846 | def objdir_root 847 | '.' 848 | end 849 | 850 | def relpath 851 | '.' 852 | end 853 | 854 | # 855 | # Option Parsing 856 | # 857 | 858 | def parsearg_global 859 | while arg = ARGV.shift 860 | case arg 861 | when /\A\w+\z/ 862 | setup_rb_error "invalid task: #{arg}" unless valid_task?(arg) 863 | return arg 864 | when '-q', '--quiet' 865 | @config.verbose = false 866 | when '--verbose' 867 | @config.verbose = true 868 | when '--help' 869 | print_usage $stdout 870 | exit 0 871 | when '--version' 872 | puts "#{File.basename($0)} version #{Version}" 873 | exit 0 874 | when '--copyright' 875 | puts Copyright 876 | exit 0 877 | else 878 | setup_rb_error "unknown global option '#{arg}'" 879 | end 880 | end 881 | nil 882 | end 883 | 884 | def valid_task?(t) 885 | valid_task_re() =~ t 886 | end 887 | 888 | def valid_task_re 889 | @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/ 890 | end 891 | 892 | def parsearg_no_options 893 | unless ARGV.empty? 894 | task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1) 895 | setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}" 896 | end 897 | end 898 | 899 | alias parsearg_show parsearg_no_options 900 | alias parsearg_setup parsearg_no_options 901 | alias parsearg_test parsearg_no_options 902 | alias parsearg_clean parsearg_no_options 903 | alias parsearg_distclean parsearg_no_options 904 | 905 | def parsearg_config 906 | evalopt = [] 907 | set = [] 908 | @config.config_opt = [] 909 | while i = ARGV.shift 910 | if /\A--?\z/ =~ i 911 | @config.config_opt = ARGV.dup 912 | break 913 | end 914 | name, value = *@config.parse_opt(i) 915 | if @config.value_config?(name) 916 | @config[name] = value 917 | else 918 | evalopt.push [name, value] 919 | end 920 | set.push name 921 | end 922 | evalopt.each do |name, value| 923 | @config.lookup(name).evaluate value, @config 924 | end 925 | # Check if configuration is valid 926 | set.each do |n| 927 | @config[n] if @config.value_config?(n) 928 | end 929 | end 930 | 931 | def parsearg_install 932 | @config.no_harm = false 933 | @config.install_prefix = '' 934 | while a = ARGV.shift 935 | case a 936 | when '--no-harm' 937 | @config.no_harm = true 938 | when /\A--prefix=/ 939 | path = a.split(/=/, 2)[1] 940 | path = File.expand_path(path) unless path[0,1] == '/' 941 | @config.install_prefix = path 942 | else 943 | setup_rb_error "install: unknown option #{a}" 944 | end 945 | end 946 | end 947 | 948 | def print_usage(out) 949 | out.puts 'Typical Installation Procedure:' 950 | out.puts " $ ruby #{File.basename $0} config" 951 | out.puts " $ ruby #{File.basename $0} setup" 952 | out.puts " # ruby #{File.basename $0} install (may require root privilege)" 953 | out.puts 954 | out.puts 'Detailed Usage:' 955 | out.puts " ruby #{File.basename $0} " 956 | out.puts " ruby #{File.basename $0} [] []" 957 | 958 | fmt = " %-24s %s\n" 959 | out.puts 960 | out.puts 'Global options:' 961 | out.printf fmt, '-q,--quiet', 'suppress message outputs' 962 | out.printf fmt, ' --verbose', 'output messages verbosely' 963 | out.printf fmt, ' --help', 'print this message' 964 | out.printf fmt, ' --version', 'print version and quit' 965 | out.printf fmt, ' --copyright', 'print copyright and quit' 966 | out.puts 967 | out.puts 'Tasks:' 968 | TASKS.each do |name, desc| 969 | out.printf fmt, name, desc 970 | end 971 | 972 | fmt = " %-24s %s [%s]\n" 973 | out.puts 974 | out.puts 'Options for CONFIG or ALL:' 975 | @config.each do |item| 976 | out.printf fmt, item.help_opt, item.description, item.help_default 977 | end 978 | out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" 979 | out.puts 980 | out.puts 'Options for INSTALL:' 981 | out.printf fmt, '--no-harm', 'only display what to do if given', 'off' 982 | out.printf fmt, '--prefix=path', 'install path prefix', '' 983 | out.puts 984 | end 985 | 986 | # 987 | # Task Handlers 988 | # 989 | 990 | def exec_config 991 | @installer.exec_config 992 | @config.save # must be final 993 | end 994 | 995 | def exec_setup 996 | @installer.exec_setup 997 | end 998 | 999 | def exec_install 1000 | @installer.exec_install 1001 | end 1002 | 1003 | def exec_test 1004 | @installer.exec_test 1005 | end 1006 | 1007 | def exec_show 1008 | @config.each do |i| 1009 | printf "%-20s %s\n", i.name, i.value if i.value? 1010 | end 1011 | end 1012 | 1013 | def exec_clean 1014 | @installer.exec_clean 1015 | end 1016 | 1017 | def exec_distclean 1018 | @installer.exec_distclean 1019 | end 1020 | 1021 | end # class ToplevelInstaller 1022 | 1023 | 1024 | class ToplevelInstallerMulti < ToplevelInstaller 1025 | 1026 | include FileOperations 1027 | 1028 | def initialize(ardir_root, config) 1029 | super 1030 | @packages = directories_of("#{@ardir}/packages") 1031 | raise 'no package exists' if @packages.empty? 1032 | @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) 1033 | end 1034 | 1035 | def run_metaconfigs 1036 | @config.load_script "#{@ardir}/metaconfig", self 1037 | @packages.each do |name| 1038 | @config.load_script "#{@ardir}/packages/#{name}/metaconfig" 1039 | end 1040 | end 1041 | 1042 | attr_reader :packages 1043 | 1044 | def packages=(list) 1045 | raise 'package list is empty' if list.empty? 1046 | list.each do |name| 1047 | raise "directory packages/#{name} does not exist"\ 1048 | unless File.dir?("#{@ardir}/packages/#{name}") 1049 | end 1050 | @packages = list 1051 | end 1052 | 1053 | def init_installers 1054 | @installers = {} 1055 | @packages.each do |pack| 1056 | @installers[pack] = Installer.new(@config, 1057 | "#{@ardir}/packages/#{pack}", 1058 | "packages/#{pack}") 1059 | end 1060 | with = extract_selection(config('with')) 1061 | without = extract_selection(config('without')) 1062 | @selected = @installers.keys.select {|name| 1063 | (with.empty? or with.include?(name)) \ 1064 | and not without.include?(name) 1065 | } 1066 | end 1067 | 1068 | def extract_selection(list) 1069 | a = list.split(/,/) 1070 | a.each do |name| 1071 | setup_rb_error "no such package: #{name}" unless @installers.key?(name) 1072 | end 1073 | a 1074 | end 1075 | 1076 | def print_usage(f) 1077 | super 1078 | f.puts 'Inluded packages:' 1079 | f.puts ' ' + @packages.sort.join(' ') 1080 | f.puts 1081 | end 1082 | 1083 | # 1084 | # Task Handlers 1085 | # 1086 | 1087 | def exec_config 1088 | run_hook 'pre-config' 1089 | each_selected_installers {|inst| inst.exec_config } 1090 | run_hook 'post-config' 1091 | @config.save # must be final 1092 | end 1093 | 1094 | def exec_setup 1095 | run_hook 'pre-setup' 1096 | each_selected_installers {|inst| inst.exec_setup } 1097 | run_hook 'post-setup' 1098 | end 1099 | 1100 | def exec_install 1101 | run_hook 'pre-install' 1102 | each_selected_installers {|inst| inst.exec_install } 1103 | run_hook 'post-install' 1104 | end 1105 | 1106 | def exec_test 1107 | run_hook 'pre-test' 1108 | each_selected_installers {|inst| inst.exec_test } 1109 | run_hook 'post-test' 1110 | end 1111 | 1112 | def exec_clean 1113 | rm_f @config.savefile 1114 | run_hook 'pre-clean' 1115 | each_selected_installers {|inst| inst.exec_clean } 1116 | run_hook 'post-clean' 1117 | end 1118 | 1119 | def exec_distclean 1120 | rm_f @config.savefile 1121 | run_hook 'pre-distclean' 1122 | each_selected_installers {|inst| inst.exec_distclean } 1123 | run_hook 'post-distclean' 1124 | end 1125 | 1126 | # 1127 | # lib 1128 | # 1129 | 1130 | def each_selected_installers 1131 | Dir.mkdir 'packages' unless File.dir?('packages') 1132 | @selected.each do |pack| 1133 | $stderr.puts "Processing the package `#{pack}' ..." if verbose? 1134 | Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") 1135 | Dir.chdir "packages/#{pack}" 1136 | yield @installers[pack] 1137 | Dir.chdir '../..' 1138 | end 1139 | end 1140 | 1141 | def run_hook(id) 1142 | @root_installer.run_hook id 1143 | end 1144 | 1145 | # module FileOperations requires this 1146 | def verbose? 1147 | @config.verbose? 1148 | end 1149 | 1150 | # module FileOperations requires this 1151 | def no_harm? 1152 | @config.no_harm? 1153 | end 1154 | 1155 | end # class ToplevelInstallerMulti 1156 | 1157 | 1158 | class Installer 1159 | 1160 | FILETYPES = %w( bin lib ext data conf man ) 1161 | 1162 | include FileOperations 1163 | include HookScriptAPI 1164 | 1165 | def initialize(config, srcroot, objroot) 1166 | @config = config 1167 | @srcdir = File.expand_path(srcroot) 1168 | @objdir = File.expand_path(objroot) 1169 | @currdir = '.' 1170 | end 1171 | 1172 | def inspect 1173 | "#<#{self.class} #{File.basename(@srcdir)}>" 1174 | end 1175 | 1176 | def noop(rel) 1177 | end 1178 | 1179 | # 1180 | # Hook Script API base methods 1181 | # 1182 | 1183 | def srcdir_root 1184 | @srcdir 1185 | end 1186 | 1187 | def objdir_root 1188 | @objdir 1189 | end 1190 | 1191 | def relpath 1192 | @currdir 1193 | end 1194 | 1195 | # 1196 | # Config Access 1197 | # 1198 | 1199 | # module FileOperations requires this 1200 | def verbose? 1201 | @config.verbose? 1202 | end 1203 | 1204 | # module FileOperations requires this 1205 | def no_harm? 1206 | @config.no_harm? 1207 | end 1208 | 1209 | def verbose_off 1210 | begin 1211 | save, @config.verbose = @config.verbose?, false 1212 | yield 1213 | ensure 1214 | @config.verbose = save 1215 | end 1216 | end 1217 | 1218 | # 1219 | # TASK config 1220 | # 1221 | 1222 | def exec_config 1223 | exec_task_traverse 'config' 1224 | end 1225 | 1226 | alias config_dir_bin noop 1227 | alias config_dir_lib noop 1228 | 1229 | def config_dir_ext(rel) 1230 | extconf if extdir?(curr_srcdir()) 1231 | end 1232 | 1233 | alias config_dir_data noop 1234 | alias config_dir_conf noop 1235 | alias config_dir_man noop 1236 | 1237 | def extconf 1238 | ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt 1239 | end 1240 | 1241 | # 1242 | # TASK setup 1243 | # 1244 | 1245 | def exec_setup 1246 | exec_task_traverse 'setup' 1247 | end 1248 | 1249 | def setup_dir_bin(rel) 1250 | files_of(curr_srcdir()).each do |fname| 1251 | update_shebang_line "#{curr_srcdir()}/#{fname}" 1252 | end 1253 | end 1254 | 1255 | alias setup_dir_lib noop 1256 | 1257 | def setup_dir_ext(rel) 1258 | make if extdir?(curr_srcdir()) 1259 | end 1260 | 1261 | alias setup_dir_data noop 1262 | alias setup_dir_conf noop 1263 | alias setup_dir_man noop 1264 | 1265 | def update_shebang_line(path) 1266 | return if no_harm? 1267 | return if config('shebang') == 'never' 1268 | old = Shebang.load(path) 1269 | if old 1270 | $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1 1271 | new = new_shebang(old) 1272 | return if new.to_s == old.to_s 1273 | else 1274 | return unless config('shebang') == 'all' 1275 | new = Shebang.new(config('rubypath')) 1276 | end 1277 | $stderr.puts "updating shebang: #{File.basename(path)}" if verbose? 1278 | open_atomic_writer(path) {|output| 1279 | File.open(path, 'rb') {|f| 1280 | f.gets if old # discard 1281 | output.puts new.to_s 1282 | output.print f.read 1283 | } 1284 | } 1285 | end 1286 | 1287 | def new_shebang(old) 1288 | if /\Aruby/ =~ File.basename(old.cmd) 1289 | Shebang.new(config('rubypath'), old.args) 1290 | elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby' 1291 | Shebang.new(config('rubypath'), old.args[1..-1]) 1292 | else 1293 | return old unless config('shebang') == 'all' 1294 | Shebang.new(config('rubypath')) 1295 | end 1296 | end 1297 | 1298 | def open_atomic_writer(path, &block) 1299 | tmpfile = File.basename(path) + '.tmp' 1300 | begin 1301 | File.open(tmpfile, 'wb', &block) 1302 | File.rename tmpfile, File.basename(path) 1303 | ensure 1304 | File.unlink tmpfile if File.exist?(tmpfile) 1305 | end 1306 | end 1307 | 1308 | class Shebang 1309 | def Shebang.load(path) 1310 | line = nil 1311 | File.open(path) {|f| 1312 | line = f.gets 1313 | } 1314 | return nil unless /\A#!/ =~ line 1315 | parse(line) 1316 | end 1317 | 1318 | def Shebang.parse(line) 1319 | cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ') 1320 | new(cmd, args) 1321 | end 1322 | 1323 | def initialize(cmd, args = []) 1324 | @cmd = cmd 1325 | @args = args 1326 | end 1327 | 1328 | attr_reader :cmd 1329 | attr_reader :args 1330 | 1331 | def to_s 1332 | "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}") 1333 | end 1334 | end 1335 | 1336 | # 1337 | # TASK install 1338 | # 1339 | 1340 | def exec_install 1341 | rm_f 'InstalledFiles' 1342 | exec_task_traverse 'install' 1343 | end 1344 | 1345 | def install_dir_bin(rel) 1346 | install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755 1347 | end 1348 | 1349 | def install_dir_lib(rel) 1350 | install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644 1351 | end 1352 | 1353 | def install_dir_ext(rel) 1354 | return unless extdir?(curr_srcdir()) 1355 | install_files rubyextentions('.'), 1356 | "#{config('sodir')}/#{File.dirname(rel)}", 1357 | 0555 1358 | end 1359 | 1360 | def install_dir_data(rel) 1361 | install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644 1362 | end 1363 | 1364 | def install_dir_conf(rel) 1365 | # FIXME: should not remove current config files 1366 | # (rename previous file to .old/.org) 1367 | install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644 1368 | end 1369 | 1370 | def install_dir_man(rel) 1371 | install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644 1372 | end 1373 | 1374 | def install_files(list, dest, mode) 1375 | mkdir_p dest, @config.install_prefix 1376 | list.each do |fname| 1377 | install fname, dest, mode, @config.install_prefix 1378 | end 1379 | end 1380 | 1381 | def libfiles 1382 | glob_reject(%w(*.y *.output), targetfiles()) 1383 | end 1384 | 1385 | def rubyextentions(dir) 1386 | ents = glob_select("*.#{@config.dllext}", targetfiles()) 1387 | if ents.empty? 1388 | setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" 1389 | end 1390 | ents 1391 | end 1392 | 1393 | def targetfiles 1394 | mapdir(existfiles() - hookfiles()) 1395 | end 1396 | 1397 | def mapdir(ents) 1398 | ents.map {|ent| 1399 | if File.exist?(ent) 1400 | then ent # objdir 1401 | else "#{curr_srcdir()}/#{ent}" # srcdir 1402 | end 1403 | } 1404 | end 1405 | 1406 | # picked up many entries from cvs-1.11.1/src/ignore.c 1407 | JUNK_FILES = %w( 1408 | core RCSLOG tags TAGS .make.state 1409 | .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb 1410 | *~ *.old *.bak *.BAK *.orig *.rej _$* *$ 1411 | 1412 | *.org *.in .* 1413 | ) 1414 | 1415 | def existfiles 1416 | glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.'))) 1417 | end 1418 | 1419 | def hookfiles 1420 | %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| 1421 | %w( config setup install clean ).map {|t| sprintf(fmt, t) } 1422 | }.flatten 1423 | end 1424 | 1425 | def glob_select(pat, ents) 1426 | re = globs2re([pat]) 1427 | ents.select {|ent| re =~ ent } 1428 | end 1429 | 1430 | def glob_reject(pats, ents) 1431 | re = globs2re(pats) 1432 | ents.reject {|ent| re =~ ent } 1433 | end 1434 | 1435 | GLOB2REGEX = { 1436 | '.' => '\.', 1437 | '$' => '\$', 1438 | '#' => '\#', 1439 | '*' => '.*' 1440 | } 1441 | 1442 | def globs2re(pats) 1443 | /\A(?:#{ 1444 | pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|') 1445 | })\z/ 1446 | end 1447 | 1448 | # 1449 | # TASK test 1450 | # 1451 | 1452 | TESTDIR = 'test' 1453 | 1454 | def exec_test 1455 | unless File.directory?('test') 1456 | $stderr.puts 'no test in this package' if verbose? 1457 | return 1458 | end 1459 | $stderr.puts 'Running tests...' if verbose? 1460 | begin 1461 | require 'test/unit' 1462 | rescue LoadError 1463 | setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.' 1464 | end 1465 | runner = Test::Unit::AutoRunner.new(true) 1466 | runner.to_run << TESTDIR 1467 | runner.run 1468 | end 1469 | 1470 | # 1471 | # TASK clean 1472 | # 1473 | 1474 | def exec_clean 1475 | exec_task_traverse 'clean' 1476 | rm_f @config.savefile 1477 | rm_f 'InstalledFiles' 1478 | end 1479 | 1480 | alias clean_dir_bin noop 1481 | alias clean_dir_lib noop 1482 | alias clean_dir_data noop 1483 | alias clean_dir_conf noop 1484 | alias clean_dir_man noop 1485 | 1486 | def clean_dir_ext(rel) 1487 | return unless extdir?(curr_srcdir()) 1488 | make 'clean' if File.file?('Makefile') 1489 | end 1490 | 1491 | # 1492 | # TASK distclean 1493 | # 1494 | 1495 | def exec_distclean 1496 | exec_task_traverse 'distclean' 1497 | rm_f @config.savefile 1498 | rm_f 'InstalledFiles' 1499 | end 1500 | 1501 | alias distclean_dir_bin noop 1502 | alias distclean_dir_lib noop 1503 | 1504 | def distclean_dir_ext(rel) 1505 | return unless extdir?(curr_srcdir()) 1506 | make 'distclean' if File.file?('Makefile') 1507 | end 1508 | 1509 | alias distclean_dir_data noop 1510 | alias distclean_dir_conf noop 1511 | alias distclean_dir_man noop 1512 | 1513 | # 1514 | # Traversing 1515 | # 1516 | 1517 | def exec_task_traverse(task) 1518 | run_hook "pre-#{task}" 1519 | FILETYPES.each do |type| 1520 | if type == 'ext' and config('without-ext') == 'yes' 1521 | $stderr.puts 'skipping ext/* by user option' if verbose? 1522 | next 1523 | end 1524 | traverse task, type, "#{task}_dir_#{type}" 1525 | end 1526 | run_hook "post-#{task}" 1527 | end 1528 | 1529 | def traverse(task, rel, mid) 1530 | dive_into(rel) { 1531 | run_hook "pre-#{task}" 1532 | __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') 1533 | directories_of(curr_srcdir()).each do |d| 1534 | traverse task, "#{rel}/#{d}", mid 1535 | end 1536 | run_hook "post-#{task}" 1537 | } 1538 | end 1539 | 1540 | def dive_into(rel) 1541 | return unless File.dir?("#{@srcdir}/#{rel}") 1542 | 1543 | dir = File.basename(rel) 1544 | Dir.mkdir dir unless File.dir?(dir) 1545 | prevdir = Dir.pwd 1546 | Dir.chdir dir 1547 | $stderr.puts '---> ' + rel if verbose? 1548 | @currdir = rel 1549 | yield 1550 | Dir.chdir prevdir 1551 | $stderr.puts '<--- ' + rel if verbose? 1552 | @currdir = File.dirname(rel) 1553 | end 1554 | 1555 | def run_hook(id) 1556 | path = [ "#{curr_srcdir()}/#{id}", 1557 | "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) } 1558 | return unless path 1559 | begin 1560 | instance_eval File.read(path), path, 1 1561 | rescue 1562 | raise if $DEBUG 1563 | setup_rb_error "hook #{path} failed:\n" + $!.message 1564 | end 1565 | end 1566 | 1567 | end # class Installer 1568 | 1569 | 1570 | class SetupError < StandardError; end 1571 | 1572 | def setup_rb_error(msg) 1573 | raise SetupError, msg 1574 | end 1575 | 1576 | if $0 == __FILE__ 1577 | begin 1578 | ToplevelInstaller.invoke 1579 | rescue SetupError 1580 | raise if $DEBUG 1581 | $stderr.puts $!.message 1582 | $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." 1583 | exit 1 1584 | end 1585 | end 1586 | --------------------------------------------------------------------------------