hub 1.8.4 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of hub might be problematic. Click here for more details.

data/README.md CHANGED
@@ -302,24 +302,10 @@ superpowers:
302
302
  Configuration
303
303
  -------------
304
304
 
305
- ### GitHub username & token
305
+ ### GitHub OAuth authentication
306
306
 
307
- To get the most out of hub, you'll want to ensure your GitHub login
308
- is stored locally in your Git config or environment variables.
309
-
310
- To test it run this:
311
-
312
- $ git config --global github.user
313
-
314
- If you see nothing, you need to set the config setting:
315
-
316
- $ git config --global github.user YOUR_USER
317
-
318
- For commands that require write access to GitHub (such as `fork`), you'll want to
319
- setup "github.token" as well. See [GitHub config guide][2] for more information.
320
-
321
- If present, environment variables `GITHUB_USER` and `GITHUB_TOKEN` override the
322
- values of "github.user" and "github.token".
307
+ Hub will prompt for GitHub username & password the first time it needs to access
308
+ the API and exchange it for an OAuth token, which it saves in "~/.config/hub".
323
309
 
324
310
  ### HTTPS insted of git protocol
325
311
 
@@ -381,5 +367,4 @@ GitHub simpler:
381
367
 
382
368
 
383
369
  [speed]: http://gist.github.com/284823
384
- [2]: http://help.github.com/set-your-user-name-email-and-github-token/
385
370
  [gc]: https://twitter.com/brynary/status/49560668994674688
data/Rakefile CHANGED
@@ -27,7 +27,7 @@ end
27
27
  # Tests
28
28
  #
29
29
 
30
- task :default => :test
30
+ task :default => [:test, :features]
31
31
 
32
32
  Rake::TestTask.new do |t|
33
33
  t.libs << 'test'
@@ -36,6 +36,10 @@ Rake::TestTask.new do |t|
36
36
  t.verbose = false
37
37
  end
38
38
 
39
+ task :features do
40
+ sh 'RUBYLIB=lib cucumber -f progress -t ~@wip features'
41
+ end
42
+
39
43
  #
40
44
  # Manual
41
45
  #
@@ -91,7 +95,7 @@ end
91
95
  # Build
92
96
  #
93
97
 
94
- file "hub" => FileList.new("lib/hub/*.rb", "man/hub.1") do |task|
98
+ file "hub" => FileList.new("lib/hub.rb", "lib/hub/*.rb", "man/hub.1") do |task|
95
99
  Rake::Task[:load_path].invoke
96
100
  require 'hub/standalone'
97
101
  Hub::Standalone.save(task.name)
data/lib/hub.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require 'hub/version'
2
2
  require 'hub/args'
3
+ require 'hub/ssh_config'
4
+ require 'hub/github_api'
3
5
  require 'hub/context'
4
6
  require 'hub/json'
5
7
  require 'hub/commands'
data/lib/hub/commands.rb CHANGED
@@ -66,6 +66,8 @@ module Hub
66
66
  else
67
67
  raise
68
68
  end
69
+ rescue Context::FatalError => err
70
+ abort "fatal: #{err.message}"
69
71
  end
70
72
 
71
73
  # $ hub pull-request
@@ -131,7 +133,7 @@ module Hub
131
133
  options[:head] ||= (tracked_branch || current_branch).short_name
132
134
 
133
135
  # when no tracking, assume remote branch is published under active user's fork
134
- user = github_user(true, head_project.host)
136
+ user = github_user(head_project.host)
135
137
  if head_project.owner != user and !tracked_branch and !explicit_owner
136
138
  head_project = head_project.owned_by(user)
137
139
  end
@@ -178,12 +180,12 @@ module Hub
178
180
  }
179
181
  end
180
182
 
181
- pull = create_pullrequest(options)
183
+ pull = api_client.create_pullrequest(options)
182
184
 
183
185
  args.executable = 'echo'
184
186
  args.replace [pull['html_url']]
185
- rescue HTTPExceptions
186
- display_http_exception("creating pull request", $!.response)
187
+ rescue GitHubAPI::Exceptions
188
+ display_api_exception("creating pull request", $!.response)
187
189
  exit 1
188
190
  end
189
191
 
@@ -214,9 +216,9 @@ module Hub
214
216
  # FIXME: this logic shouldn't be duplicated here!
215
217
  name, owner = arg, nil
216
218
  owner, name = name.split('/', 2) if name.index('/')
217
- host = ENV['GITHUB_HOST']
218
- project = Context::GithubProject.new(nil, owner || github_user(true, host), name, host || 'github.com')
219
- ssh ||= args[0] != 'submodule' && project.owner == github_user(false, host) || host
219
+ host = ENV['GITHUB_HOST'] || 'github.com'
220
+ project = Context::GithubProject.new(nil, owner || github_user(host), name, host)
221
+ ssh ||= args[0] != 'submodule' && project.owner == github_user(host) || host != 'github.com'
220
222
  args[idx] = project.git_url(:private => ssh, :https => https_protocol?)
221
223
  end
222
224
  break
@@ -319,7 +321,7 @@ module Hub
319
321
  projects = names.map { |name|
320
322
  unless name =~ /\W/ or remotes.include?(name) or remotes_group(name)
321
323
  project = github_project(nil, name)
322
- project if repo_exists?(project)
324
+ project if api_client.repo_exists?(project)
323
325
  end
324
326
  }.compact
325
327
 
@@ -337,21 +339,18 @@ module Hub
337
339
  _, url_arg, new_branch_name = args.words
338
340
  if url = resolve_github_url(url_arg) and url.project_path =~ /^pull\/(\d+)/
339
341
  pull_id = $1
340
-
341
- load_net_http
342
- response = http_request(url.project.api_pullrequest_url(pull_id, 'json'))
343
- pull_data = JSON.parse(response.body)['pull']
342
+ pull_data = api_client.pullrequest_info(url.project, pull_id)
344
343
 
345
344
  args.delete new_branch_name
346
345
  user, branch = pull_data['head']['label'].split(':', 2)
347
- abort "Error: #{user}'s fork is not available anymore" unless pull_data['head']['repository']
346
+ abort "Error: #{user}'s fork is not available anymore" unless pull_data['head']['repo']
348
347
  new_branch_name ||= "#{user}-#{branch}"
349
348
 
350
349
  if remotes.include? user
351
350
  args.before ['remote', 'set-branches', '--add', user, branch]
352
351
  args.before ['fetch', user, "+refs/heads/#{branch}:refs/remotes/#{user}/#{branch}"]
353
352
  else
354
- url = github_project(url.project_name, user).git_url(:private => pull_data['head']['repository']['private'],
353
+ url = github_project(url.project_name, user).git_url(:private => pull_data['head']['repo']['private'],
355
354
  :https => https_protocol?)
356
355
  args.before ['remote', 'add', '-f', '-t', branch, user, url]
357
356
  end
@@ -426,8 +425,8 @@ module Hub
426
425
  if args.delete('-g')
427
426
  # can't use default_host because there is no local_repo yet
428
427
  # FIXME: this shouldn't be here!
429
- host = ENV['GITHUB_HOST']
430
- project = Context::GithubProject.new(nil, github_user(true, host), File.basename(current_dir), host || 'github.com')
428
+ host = ENV['GITHUB_HOST'] || 'github.com'
429
+ project = Context::GithubProject.new(nil, github_user(host), File.basename(current_dir), host)
431
430
  url = project.git_url(:private => true, :https => https_protocol?)
432
431
  args.after ['remote', 'add', 'origin', url]
433
432
  end
@@ -440,13 +439,13 @@ module Hub
440
439
  unless project = local_repo.main_project
441
440
  abort "Error: repository under 'origin' remote is not a GitHub project"
442
441
  end
443
- forked_project = project.owned_by(github_user(true, project.host))
442
+ forked_project = project.owned_by(github_user(project.host))
444
443
 
445
- if repo_exists?(forked_project)
444
+ if api_client.repo_exists?(forked_project)
446
445
  abort "Error creating fork: %s already exists on %s" %
447
446
  [ forked_project.name_with_owner, forked_project.host ]
448
447
  else
449
- fork_repo(project) unless args.noop?
448
+ api_client.fork_repo(project) unless args.noop?
450
449
  end
451
450
 
452
451
  if args.include?('--no-remote')
@@ -456,8 +455,8 @@ module Hub
456
455
  args.replace %W"remote add -f #{forked_project.owner} #{url}"
457
456
  args.after 'echo', ['new remote:', forked_project.owner]
458
457
  end
459
- rescue HTTPExceptions
460
- display_http_exception("creating fork", $!.response)
458
+ rescue GitHubAPI::Exceptions
459
+ display_api_exception("creating fork", $!.response)
461
460
  exit 1
462
461
  end
463
462
 
@@ -467,7 +466,7 @@ module Hub
467
466
  def create(args)
468
467
  if !is_repo?
469
468
  abort "'create' must be run from inside a git repository"
470
- elsif owner = github_user and github_token
469
+ elsif owner = github_user
471
470
  args.shift
472
471
  options = {}
473
472
  options[:private] = true if args.delete('-p')
@@ -491,12 +490,12 @@ module Hub
491
490
  new_repo_name ||= repo_name
492
491
  new_project = github_project(new_repo_name, owner)
493
492
 
494
- if repo_exists?(new_project)
493
+ if api_client.repo_exists?(new_project)
495
494
  warn "#{new_project.name_with_owner} already exists on #{new_project.host}"
496
495
  action = "set remote origin"
497
496
  else
498
497
  action = "created repository"
499
- create_repo(new_project, options) unless args.noop?
498
+ api_client.create_repo(new_project, options) unless args.noop?
500
499
  end
501
500
 
502
501
  url = new_project.git_url(:private => true, :https => https_protocol?)
@@ -509,8 +508,8 @@ module Hub
509
508
 
510
509
  args.after 'echo', ["#{action}:", new_project.name_with_owner]
511
510
  end
512
- rescue HTTPExceptions
513
- display_http_exception("creating repository", $!.response)
511
+ rescue GitHubAPI::Exceptions
512
+ display_api_exception("creating repository", $!.response)
514
513
  exit 1
515
514
  end
516
515
 
@@ -618,10 +617,12 @@ module Hub
618
617
  def hub(args)
619
618
  return help(args) unless args[1] == 'standalone'
620
619
  require 'hub/standalone'
621
- $stdout.puts Hub::Standalone.build
620
+ Hub::Standalone.build $stdout
622
621
  exit
623
622
  rescue LoadError
624
- abort "hub is running in standalone mode."
623
+ abort "hub is already running in standalone mode."
624
+ rescue Errno::EPIPE
625
+ exit # ignore broken pipe
625
626
  end
626
627
 
627
628
  def alias(args)
@@ -692,6 +693,21 @@ module Hub
692
693
  # from the command line.
693
694
  #
694
695
 
696
+ def api_client
697
+ @api_client ||= begin
698
+ config_file = ENV['HUB_CONFIG'] || '~/.config/hub'
699
+ file_store = GitHubAPI::FileStore.new File.expand_path(config_file)
700
+ file_config = GitHubAPI::Configuration.new file_store
701
+ GitHubAPI.new file_config, :app_url => 'http://defunkt.io/hub/'
702
+ end
703
+ end
704
+
705
+ def github_user host = nil
706
+ host ||= local_repo(false) && local_repo.main_host
707
+ return nil if host.nil?
708
+ api_client.config.username(host) { }
709
+ end
710
+
695
711
  def custom_command? cmd
696
712
  CUSTOM_COMMANDS.include? cmd
697
713
  end
@@ -898,53 +914,6 @@ help
898
914
  end
899
915
  end
900
916
 
901
- # Determines whether a user has a fork of the current repo on GitHub.
902
- def repo_exists?(project)
903
- load_net_http
904
- Net::HTTPSuccess === http_request(project.api_show_url('yaml'))
905
- end
906
-
907
- # Forks the current repo using the GitHub API.
908
- #
909
- # Returns nothing.
910
- def fork_repo(project)
911
- load_net_http
912
- response = http_post project.api_fork_url('yaml')
913
- response.error! unless Net::HTTPSuccess === response
914
- end
915
-
916
- # Creates a new repo using the GitHub API.
917
- #
918
- # Returns nothing.
919
- def create_repo(project, options = {})
920
- is_org = project.owner != github_user(true, project.host)
921
- params = {'name' => is_org ? project.name_with_owner : project.name}
922
- params['public'] = '0' if options[:private]
923
- params['description'] = options[:description] if options[:description]
924
- params['homepage'] = options[:homepage] if options[:homepage]
925
-
926
- load_net_http
927
- response = http_post(project.api_create_url('json'), params)
928
- response.error! unless Net::HTTPSuccess === response
929
- end
930
-
931
- # Returns parsed data from the new pull request.
932
- def create_pullrequest(options)
933
- project = options.fetch(:project)
934
- params = {
935
- 'pull[base]' => options.fetch(:base),
936
- 'pull[head]' => options.fetch(:head)
937
- }
938
- params['pull[issue]'] = options[:issue] if options[:issue]
939
- params['pull[title]'] = options[:title] if options[:title]
940
- params['pull[body]'] = options[:body] if options[:body]
941
-
942
- load_net_http
943
- response = http_post(project.api_create_pullrequest_url('json'), params)
944
- response.error! unless Net::HTTPSuccess === response
945
- JSON.parse(response.body)['pull']
946
- end
947
-
948
917
  def pullrequest_editmsg(changes)
949
918
  message_file = File.join(git_dir, 'PULLREQ_EDITMSG')
950
919
  File.open(message_file, 'w') { |msg|
@@ -988,86 +957,13 @@ help
988
957
  end
989
958
  end
990
959
 
991
- def http_request(url, type = :Get)
992
- url = URI(url) unless url.respond_to? :host
993
- user, token = github_user(type != :Get, url.host), github_token(type != :Get, url.host)
994
-
995
- req = Net::HTTP.const_get(type).new(url.request_uri)
996
- req.basic_auth "#{user}/token", token if user and token
997
-
998
- http = setup_http(url)
999
-
1000
- yield req if block_given?
1001
- http.start { http.request(req) }
1002
- end
1003
-
1004
- def http_post(url, params = nil)
1005
- http_request(url, :Post) do |req|
1006
- req.set_form_data params if params
1007
- req['Content-Length'] = req.body ? req.body.length : 0
1008
- end
1009
- end
1010
-
1011
- def setup_http(url)
1012
- port = url.port
1013
- if use_ssl = 'https' == url.scheme and not use_ssl?
1014
- # ruby compiled without openssl
1015
- use_ssl = false
1016
- port = 80
1017
- end
1018
-
1019
- http_args = [url.host, port]
1020
- if proxy = proxy_url(use_ssl)
1021
- http_args.concat proxy.select(:host, :port)
1022
- if proxy.userinfo
1023
- require 'cgi'
1024
- http_args.concat proxy.userinfo.split(':', 2).map {|a| CGI.unescape a }
1025
- end
1026
- end
1027
-
1028
- http = Net::HTTP.new(*http_args)
1029
-
1030
- if http.use_ssl = use_ssl
1031
- # TODO: SSL peer verification
1032
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
1033
- end
1034
- return http
1035
- end
1036
-
1037
- def load_net_http
1038
- require 'net/https'
1039
- rescue LoadError
1040
- require 'net/http'
1041
- end
1042
-
1043
- def use_ssl?
1044
- defined? ::OpenSSL
1045
- end
1046
-
1047
- def proxy_url(use_ssl)
1048
- env_name = "HTTP#{use_ssl ? 'S' : ''}_PROXY"
1049
- if proxy = ENV[env_name] || ENV[env_name.downcase]
1050
- proxy = "http://#{proxy}" unless proxy.include? '://'
1051
- URI.parse(proxy)
1052
- end
1053
- end
1054
-
1055
- # Fake exception type for net/http exception handling.
1056
- # Necessary because net/http may or may not be loaded at the time.
1057
- module HTTPExceptions
1058
- def self.===(exception)
1059
- exception.class.ancestors.map {|a| a.to_s }.include? 'Net::HTTPExceptions'
1060
- end
1061
- end
1062
-
1063
- def display_http_exception(action, response)
1064
- $stderr.puts "Error #{action}: #{response.message} (HTTP #{response.code})"
1065
- case response.code.to_i
1066
- when 401 then warn "Check your token configuration (`git config github.token`)"
1067
- when 422
1068
- if response.content_type =~ /\bjson\b/ and data = JSON.parse(response.body) and data["error"]
1069
- $stderr.puts data["error"]
1070
- end
960
+ def display_api_exception(action, response)
961
+ $stderr.puts "Error #{action}: #{response.message} (HTTP #{response.status})"
962
+ if 422 == response.status and response.error_message?
963
+ # display validation errors
964
+ msg = response.error_message
965
+ msg = msg.join("\n") if msg.respond_to? :join
966
+ warn msg
1071
967
  end
1072
968
  end
1073
969
 
data/lib/hub/context.rb CHANGED
@@ -3,8 +3,8 @@ require 'forwardable'
3
3
  require 'uri'
4
4
 
5
5
  module Hub
6
- # Provides methods for inspecting the environment, such as GitHub user/token
7
- # settings, repository info, and similar.
6
+ # Methods for inspecting the environment, such as reading git config,
7
+ # repository info, and other.
8
8
  module Context
9
9
  extend Forwardable
10
10
 
@@ -75,6 +75,9 @@ module Hub
75
75
  end
76
76
  end
77
77
 
78
+ class Error < RuntimeError; end
79
+ class FatalError < Error; end
80
+
78
81
  private
79
82
 
80
83
  def git_reader
@@ -89,7 +92,7 @@ module Hub
89
92
  if is_repo?
90
93
  LocalRepo.new git_reader, current_dir
91
94
  elsif fatal
92
- abort "fatal: Not a git repository"
95
+ raise FatalError, "Not a git repository"
93
96
  end
94
97
  end
95
98
  end
@@ -243,30 +246,6 @@ module Hub
243
246
  else "git://#{host}/"
244
247
  end + name_with_owner + '.git'
245
248
  end
246
-
247
- def api_url(type, resource, action)
248
- URI("https://#{host}/api/v2/#{type}/#{resource}/#{action}")
249
- end
250
-
251
- def api_show_url(type)
252
- api_url(type, 'repos', "show/#{owner}/#{name}")
253
- end
254
-
255
- def api_fork_url(type)
256
- api_url(type, 'repos', "fork/#{owner}/#{name}")
257
- end
258
-
259
- def api_create_url(type)
260
- api_url(type, 'repos', 'create')
261
- end
262
-
263
- def api_pullrequest_url(id, type)
264
- api_url(type, 'pulls', "#{owner}/#{name}/#{id}")
265
- end
266
-
267
- def api_create_pullrequest_url(type)
268
- api_url(type, 'pulls', "#{owner}/#{name}")
269
- end
270
249
  end
271
250
 
272
251
  class GithubURL < URI::HTTPS
@@ -320,7 +299,7 @@ module Hub
320
299
 
321
300
  def remote_name
322
301
  name =~ %r{^refs/remotes/([^/]+)} and $1 or
323
- raise "can't get remote name from #{name.inspect}"
302
+ raise Error, "can't get remote name from #{name.inspect}"
324
303
  end
325
304
  end
326
305
 
@@ -391,44 +370,6 @@ module Hub
391
370
  GithubURL.resolve(url, local_repo) if url =~ /^https?:/
392
371
  end
393
372
 
394
- LGHCONF = "http://help.github.com/set-your-user-name-email-and-github-token/"
395
-
396
- # Either returns the GitHub user as set by git-config(1) or aborts
397
- # with an error message.
398
- def github_user(fatal = true, host = nil)
399
- if local = local_repo(false)
400
- host ||= local.default_host
401
- host = nil if host == local.main_host
402
- end
403
- host = %(."#{host}") if host
404
- if user = ENV['GITHUB_USER'] || git_config("github#{host}.user")
405
- user
406
- elsif fatal
407
- if host.nil?
408
- abort("** No GitHub user set. See #{LGHCONF}")
409
- else
410
- abort("** No user set for github#{host}")
411
- end
412
- end
413
- end
414
-
415
- def github_token(fatal = true, host = nil)
416
- if local = local_repo(false)
417
- host ||= local.default_host
418
- host = nil if host == local.main_host
419
- end
420
- host = %(."#{host}") if host
421
- if token = ENV['GITHUB_TOKEN'] || git_config("github#{host}.token")
422
- token
423
- elsif fatal
424
- if host.nil?
425
- abort("** No GitHub token set. See #{LGHCONF}")
426
- else
427
- abort("** No token set for github#{host}")
428
- end
429
- end
430
- end
431
-
432
373
  # legacy setting
433
374
  def http_clone?
434
375
  git_config('--bool hub.http-clone') == 'true'
@@ -468,136 +409,55 @@ module Hub
468
409
  editor.shellsplit
469
410
  end
470
411
 
471
- # Cross-platform web browser command; respects the value set in $BROWSER.
472
- #
473
- # Returns an array, e.g.: ['open']
474
- def browser_launcher
475
- browser = ENV['BROWSER'] || (
476
- osx? ? 'open' : windows? ? 'start' :
477
- %w[xdg-open cygstart x-www-browser firefox opera mozilla netscape].find { |comm| which comm }
478
- )
479
-
480
- abort "Please set $BROWSER to a web launcher to use this command." unless browser
481
- Array(browser)
482
- end
483
-
484
- def osx?
485
- require 'rbconfig'
486
- RbConfig::CONFIG['host_os'].to_s.include?('darwin')
487
- end
412
+ module System
413
+ # Cross-platform web browser command; respects the value set in $BROWSER.
414
+ #
415
+ # Returns an array, e.g.: ['open']
416
+ def browser_launcher
417
+ browser = ENV['BROWSER'] || (
418
+ osx? ? 'open' : windows? ? 'start' :
419
+ %w[xdg-open cygstart x-www-browser firefox opera mozilla netscape].find { |comm| which comm }
420
+ )
488
421
 
489
- def windows?
490
- require 'rbconfig'
491
- RbConfig::CONFIG['host_os'] =~ /msdos|mswin|djgpp|mingw|windows/
492
- end
493
-
494
- # Cross-platform way of finding an executable in the $PATH.
495
- #
496
- # which('ruby') #=> /usr/bin/ruby
497
- def which(cmd)
498
- exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
499
- ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
500
- exts.each { |ext|
501
- exe = "#{path}/#{cmd}#{ext}"
502
- return exe if File.executable? exe
503
- }
422
+ abort "Please set $BROWSER to a web launcher to use this command." unless browser
423
+ Array(browser)
504
424
  end
505
- return nil
506
- end
507
425
 
508
- # Checks whether a command exists on this system in the $PATH.
509
- #
510
- # name - The String name of the command to check for.
511
- #
512
- # Returns a Boolean.
513
- def command?(name)
514
- !which(name).nil?
515
- end
516
-
517
- class SshConfig
518
- CONFIG_FILES = %w(~/.ssh/config /etc/ssh_config /etc/ssh/ssh_config)
519
-
520
- def initialize files = nil
521
- @settings = Hash.new {|h,k| h[k] = {} }
522
- Array(files || CONFIG_FILES).each do |path|
523
- file = File.expand_path path
524
- parse_file file if File.exist? file
525
- end
426
+ def osx?
427
+ require 'rbconfig'
428
+ RbConfig::CONFIG['host_os'].to_s.include?('darwin')
526
429
  end
527
430
 
528
- # yields if not found
529
- def get_value hostname, key
530
- key = key.to_s.downcase
531
- @settings.each do |pattern, settings|
532
- if pattern.match? hostname and found = settings[key]
533
- return found
534
- end
535
- end
536
- yield
537
- end
538
-
539
- class HostPattern
540
- def initialize pattern
541
- @pattern = pattern.to_s.downcase
542
- end
543
-
544
- def to_s() @pattern end
545
- def ==(other) other.to_s == self.to_s end
546
-
547
- def matcher
548
- @matcher ||=
549
- if '*' == @pattern
550
- Proc.new { true }
551
- elsif @pattern !~ /[?*]/
552
- lambda { |hostname| hostname.to_s.downcase == @pattern }
553
- else
554
- re = self.class.pattern_to_regexp @pattern
555
- lambda { |hostname| re =~ hostname }
556
- end
557
- end
558
-
559
- def match? hostname
560
- matcher.call hostname
561
- end
562
-
563
- def self.pattern_to_regexp pattern
564
- escaped = Regexp.escape(pattern)
565
- escaped.gsub!('\*', '.*')
566
- escaped.gsub!('\?', '.')
567
- /^#{escaped}$/i
568
- end
431
+ def windows?
432
+ require 'rbconfig'
433
+ RbConfig::CONFIG['host_os'] =~ /msdos|mswin|djgpp|mingw|windows/
569
434
  end
570
435
 
571
- def parse_file file
572
- host_patterns = [HostPattern.new('*')]
573
-
574
- IO.foreach(file) do |line|
575
- case line
576
- when /^\s*(#|$)/ then next
577
- when /^\s*(\S+)\s*=/
578
- key, value = $1, $'
579
- else
580
- key, value = line.strip.split(/\s+/, 2)
581
- end
582
-
583
- next if value.nil?
584
- key.downcase!
585
- value = $1 if value =~ /^"(.*)"$/
586
- value.chomp!
587
-
588
- if 'host' == key
589
- host_patterns = value.split(/\s+/).map {|p| HostPattern.new p }
590
- else
591
- record_setting key, value, host_patterns
592
- end
436
+ # Cross-platform way of finding an executable in the $PATH.
437
+ #
438
+ # which('ruby') #=> /usr/bin/ruby
439
+ def which(cmd)
440
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
441
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
442
+ exts.each { |ext|
443
+ exe = "#{path}/#{cmd}#{ext}"
444
+ return exe if File.executable? exe
445
+ }
593
446
  end
447
+ return nil
594
448
  end
595
449
 
596
- def record_setting key, value, patterns
597
- patterns.each do |pattern|
598
- @settings[pattern][key] ||= value
599
- end
450
+ # Checks whether a command exists on this system in the $PATH.
451
+ #
452
+ # name - The String name of the command to check for.
453
+ #
454
+ # Returns a Boolean.
455
+ def command?(name)
456
+ !which(name).nil?
600
457
  end
601
458
  end
459
+
460
+ include System
461
+ extend System
602
462
  end
603
463
  end