textmate 0.9.2 → 0.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. data/Rakefile +3 -2
  2. data/bin/textmate +137 -56
  3. metadata +8 -5
data/Rakefile CHANGED
@@ -3,7 +3,7 @@ require 'rake/gempackagetask'
3
3
  require 'date'
4
4
 
5
5
  GEM = "textmate"
6
- GEM_VERSION = "0.9.2"
6
+ GEM_VERSION = "0.9.6"
7
7
  AUTHOR = "Yehuda Katz"
8
8
  EMAIL = "wycats@gmail.com"
9
9
  HOMEPAGE = "http://yehudakatz.com"
@@ -22,7 +22,8 @@ spec = Gem::Specification.new do |s|
22
22
  s.homepage = HOMEPAGE
23
23
 
24
24
  s.add_dependency "thor", ">= 0.9.2"
25
-
25
+
26
+ s.require_path = 'bin' # Yes, it's a hack, but otherwise gem complains on install
26
27
  s.autorequire = GEM
27
28
  s.files = %w(LICENSE README.markdown Rakefile) + Dir.glob("{bin,specs}/**/*")
28
29
  s.bindir = "bin"
@@ -5,88 +5,133 @@ require "rubygems"
5
5
  require "thor"
6
6
  require "open-uri"
7
7
  require "yaml"
8
+ require "cgi"
8
9
 
9
10
  class TextmateInstaller < Thor
10
-
11
+
11
12
  # CHANGED: renamed list to remote. Could there be a better name?
12
- desc "remote [SEARCH]", "Lists all the matching remote bundles"
13
- def remote(search_term = "")
13
+ desc "search [SEARCH]", "Lists all the matching remote bundles"
14
+ def search(search_term = "")
14
15
  search_term = Regexp.new(".*#{search_term}.*", "i")
15
-
16
- remote_bundle_locations.each do |name,location|
16
+
17
+ REMOTE_LOCATIONS.each do |name,location|
17
18
  puts "\n" << name.to_s << " Remote Bundles\n" << name.to_s.gsub(/./,'-') << '---------------'
18
-
19
+
19
20
  results = case location[:scm]
20
21
  when :svn
21
22
  %x[svn list #{e_sh location[:url]}].map {|x| x.split(".")[0]}.select {|x| x =~ search_term}.join("\n")
22
23
  when :git
23
24
  'git remotes not implemented yet'
24
25
  when :github
25
- find_github_bundles(search_term).map{|result| normalize_github_repo_name(result['name']).split('.').first}
26
+ find_github_bundles(search_term).map {|result|
27
+ "%s (by %s)" %
28
+ [
29
+ normalize_github_repo_name(result['name']).split('.').first,
30
+ result['username']
31
+ ]
32
+ }
26
33
  end
27
-
34
+
28
35
  puts results
29
36
  end
30
37
  end
31
-
38
+
32
39
  desc "list [SEARCH]", "lists all the bundles installed locally"
33
40
  def list(search_term = "")
34
41
  search_term = Regexp.new(".*#{search_term}.*", "i")
35
-
42
+
36
43
  local_bundle_paths.each do |name,bundles_path|
37
44
  puts "\n" << name.to_s << " Bundles\n" << name.to_s.gsub(/./,'-') << '--------'
38
45
  puts Dir["#{e_sh bundles_path}/*.tmbundle"].map {|x| x.split("/").last.split(".").first}.
39
46
  select {|x| x =~ search_term}.join("\n")
40
47
  end
41
48
  end
42
-
43
- desc "install NAME [SOURCE]", "install a bundle"
44
- def install(bundle_name, remote_bundle_location_name=nil)
49
+
50
+ desc "install NAME", "Install a bundle. Source must be one of trunk, review, or github. \n" \
51
+ "If multiple gems with the same name exist, you will be prompted to \n" \
52
+ "choose from the available list."
53
+ method_option :source
54
+ def install(bundle_name)
45
55
  FileUtils.mkdir_p install_bundles_path
46
56
  puts "Checking out #{bundle_name}..."
47
-
57
+
48
58
  # CHANGED: It's faster to just try and fail for each repo than to search them all first
49
59
  installed=false
50
- remote_bundle_locations.each do |remote_name,location|
51
- next unless remote_name.to_s.downcase.include? remote_bundle_location_name.to_s.downcase if remote_bundle_location_name
52
-
60
+ REMOTE_LOCATIONS.each do |remote_name,location|
61
+ next unless remote_bundle(options["source"]) == location if options.key?("source")
62
+
53
63
  cmd = case location[:scm]
54
64
  when :git
55
65
  'echo "git remotes not implemented yet"'
56
66
  when :svn
57
- %[svn co #{e_sh location[:url]}/#{e_sh bundle_name}.tmbundle #{e_sh install_bundles_path}/#{e_sh bundle_name}.tmbundle 2>&1]
67
+ %[svn co "#{location[:url]}/#{url_escape bundle_name}.tmbundle" #{e_sh install_bundles_path}/#{e_sh bundle_name}.tmbundle 2>&1]
58
68
  when :github
59
- repo = find_github_bundles(denormalize_github_repo_name(bundle_name)).first
60
- %[git clone #{e_sh repo['url'].sub('http', 'git') + '.git'} #{e_sh install_bundles_path}/#{e_sh bundle_name}.tmbundle 2>&1]
69
+ repos = find_github_bundles(denormalize_github_repo_name(bundle_name))
70
+
71
+ # Handle possible multiple Repos with the same name
72
+ case repos.size
73
+ when 0
74
+ 'echo "Sorry, no such bundle found"'
75
+ when 1
76
+ git_clone(repos.first["username"], repos.first["name"])
77
+ else
78
+ puts "Multiple bundles with that name found. Please choose which one you want to install:"
79
+ repos.each_with_index {|repo, idx|
80
+ puts "%d: %s by %s" %
81
+ [
82
+ idx + 1,
83
+ normalize_github_repo_name(repo['name']),
84
+ repo['username']
85
+ ]
86
+ }
87
+ print "Your choice: "
88
+
89
+ # Since to_i defaults to 0, we have to use Integer
90
+ choice = Integer(STDIN.gets.chomp) rescue nil
91
+ until choice && (0...repos.size).include?( choice - 1 ) do
92
+ print "Sorry, invalid choice. Please enter a valid number or Ctrl+C to stop: "
93
+ choice = Integer(STDIN.gets.chomp) rescue nil
94
+ end
95
+
96
+ git_clone(repos[choice - 1]["username"], repos[choice - 1]["name"])
97
+ end
61
98
  end
62
-
99
+
63
100
  res = %x{#{cmd}}
64
-
101
+
65
102
  puts cmd, res.gsub(/^/,' ')
66
-
103
+
67
104
  installed=true and break if res =~ /Checked out revision|Initialized empty Git repository/
68
105
  end
69
106
  abort 'Not Installed' unless installed
70
-
107
+
71
108
  reload :verbose => true
72
109
  end
73
110
 
74
111
  desc "uninstall NAME", "uninstall a bundle"
75
112
  def uninstall(bundle_name)
113
+ removed = false
114
+
76
115
  puts "Removing bundle..."
77
- # When moving to the trash, maybe move the bundle into a trash/disabled_bundles subfolder
78
- # named as the bundles_path key. Just in case there are multiple versions of
116
+ # When moving to the trash, maybe move the bundle into a trash/disabled_bundles subfolder
117
+ # named as the bundles_path key. Just in case there are multiple versions of
79
118
  # the same bundle in multiple bundle paths
80
119
  local_bundle_paths.each do |name,bundles_path|
81
120
  bundle_path = "#{bundles_path}/#{bundle_name}.tmbundle"
82
121
  if File.exist? bundle_path
122
+ removed = true
83
123
  %x[osascript -e 'tell application "Finder" to move the POSIX file "#{bundle_path}" to trash']
84
124
  end
85
125
  end
86
-
87
- reload :verbose => true
126
+
127
+ unless removed
128
+ say "There is no bundle by that name in the system", :red
129
+ exit
130
+ else
131
+ reload :verbose => true
132
+ end
88
133
  end
89
-
134
+
90
135
  desc "reload", "Reloads TextMate Bundles"
91
136
  method_options :verbose => :boolean
92
137
  def reload(opts = {})
@@ -94,18 +139,24 @@ class TextmateInstaller < Thor
94
139
  %x[osascript -e 'tell app "TextMate" to reload bundles']
95
140
  puts "Done." if opts[:verbose]
96
141
  end
97
-
142
+
98
143
  private
99
- def remote_bundle_locations
100
- { :'Macromates Trunk' => {:scm => :svn, :url => 'http://macromates.com/svn/Bundles/trunk/Bundles'},
101
- :'Macromates Review' => {:scm => :svn, :url => 'http://macromates.com/svn/Bundles/trunk/Review/Bundles'},
102
-
144
+ REMOTE_LOCATIONS =
145
+ { :'Macromates Trunk' => {:scm => :svn, :url => 'http://svn.textmate.org/trunk/Bundles'},
146
+ :'Macromates Review' => {:scm => :svn, :url => 'http://svn.textmate.org/trunk/Review/Bundles'},
147
+
103
148
  # :'Bunch of Git Bundles' => {:scm => :git, :url => 'git://NotImplemented'},
104
-
149
+
105
150
  :'GitHub' => {:scm => :github, :url => 'http://github.com/search?q=tmbundle'},
106
151
  }
152
+
153
+ SHORT_LOCATIONS = {:trunk => :"Macromates Trunk", :review => :"Macromates Review", :github => :"GitHub"}
154
+
155
+ def remote_bundle(name)
156
+ name = name.to_sym
157
+ REMOTE_LOCATIONS[name] || REMOTE_LOCATIONS[SHORT_LOCATIONS[name]]
107
158
  end
108
-
159
+
109
160
  def local_bundle_paths
110
161
  { :Application => '/Applications/TextMate.app/Contents/SharedSupport/Bundles',
111
162
  :User => "#{ENV["HOME"]}/Library/Application Support/TextMate/Bundles",
@@ -114,46 +165,76 @@ class TextmateInstaller < Thor
114
165
  :'System Pristine' => '/Library/Application Support/TextMate/Pristine Copy/Bundles',
115
166
  }
116
167
  end
117
-
168
+
118
169
  def install_bundles_path
119
170
  local_bundle_paths[:'User Pristine']
120
171
  end
121
-
172
+
122
173
  # Copied from http://macromates.com/svn/Bundles/trunk/Support/lib/escape.rb
123
174
  # escape text to make it useable in a shell script as one “word” (string)
124
175
  def e_sh(str)
125
176
  str.to_s.gsub(/(?=[^a-zA-Z0-9_.\/\-\x7F-\xFF\n])/, '\\').gsub(/\n/, "'\n'").sub(/^$/, "''")
126
177
  end
127
-
178
+
179
+ def url_escape(str)
180
+ chars = ((33...47).to_a + (94...96).to_a + (123...126).to_a).map {|c| c.chr }.join + "\\[\\]\\\\"
181
+ str = str.to_s.gsub(%r{[#{chars}]}) {|m| CGI.escape(m) }
182
+ end
183
+
128
184
  CAPITALIZATION_EXCEPTIONS = %w[tmbundle on]
129
185
  # Convert a GitHub repo name into a "normal" TM bundle name
130
186
  # e.g. ruby-on-rails-tmbundle => Ruby on Rails.tmbundle
131
187
  def normalize_github_repo_name(name)
132
- name = name.gsub("-", " ").split.each{|part| part.capitalize! unless CAPITALIZATION_EXCEPTIONS.include? part}.join(" ")
188
+ name = name.gsub(/[\-\.]/, " ").split.each{|part| part.capitalize! unless CAPITALIZATION_EXCEPTIONS.include? part}.join(" ")
133
189
  name[-9] = ?. if name =~ / tmbundle$/
134
190
  name
135
191
  end
136
-
192
+
137
193
  # Does the opposite of normalize_github_repo_name
138
194
  def denormalize_github_repo_name(name)
139
- name += " tmbundle" unless name =~ / tmbundle$/
140
- name.split(' ').each{|part| part.downcase!}.join(' ').gsub(' ', '-')
195
+ name = name.split(' ').each{|part| part.downcase!}.join(' ').gsub(' ', '-')
196
+ name += ".tmbundle" unless name =~ /.tmbundle$/
197
+ name
141
198
  end
142
-
199
+
143
200
  def find_github_bundles(search_term)
144
- YAML.load(open('http://github.com/api/v1/yaml/search/tmbundle'))['repositories'].find_all{|result| result['name'].match(search_term)}
201
+ # Until GitHub fixes http://support.github.com/discussions/feature-requests/11-api-search-results,
202
+ # we need to account for multiple pages of results:
203
+ page = 1
204
+ repositories = YAML.load(open("http://github.com/api/v1/yaml/search/tmbundle?start_value=#{page}"))['repositories']
205
+ results = []
206
+ until repositories.empty?
207
+ results += repositories.find_all{|result| result['name'].match(search_term)}
208
+ page += 1
209
+ repositories = YAML.load(open("http://github.com/api/v1/yaml/search/tmbundle?start_value=#{page}"))['repositories']
210
+ end
211
+ results.sort{|a,b| a['name'] <=> b['name']}
145
212
  end
146
-
213
+
214
+ def git_clone(repo, name)
215
+ bundle_name = normalize_github_repo_name(name)
216
+
217
+ path = "#{install_bundles_path}/#{bundle_name}"
218
+ escaped_path = "#{e_sh(install_bundles_path)}/#{e_sh(bundle_name)}"
219
+
220
+ if File.directory?(path)
221
+ say "Sorry, that bundle is already installed. Please uninstall it first.", :red
222
+ exit
223
+ end
224
+
225
+ %[git clone "git://github.com/#{url_escape(repo)}/#{url_escape(name)}.git" #{escaped_path} 2>&1]
226
+ end
227
+
147
228
  end
148
229
 
149
230
  # TODO: create a "monument to personal cleverness" by class-izing everything?
150
231
  # class TextMateBundle
151
232
  # def self.find_local(bundle_name)
152
- #
233
+ #
153
234
  # end
154
- #
235
+ #
155
236
  # def self.find_remote(bundle_name)
156
- #
237
+ #
157
238
  # end
158
239
  # attr_reader :name
159
240
  # attr_reader :location
@@ -163,20 +244,20 @@ end
163
244
  # @location = location
164
245
  # @scm = scm
165
246
  # end
166
- #
247
+ #
167
248
  # def install!
168
- #
249
+ #
169
250
  # end
170
- #
251
+ #
171
252
  # def uninstall!
172
- #
253
+ #
173
254
  # end
174
- #
175
- #
255
+ #
256
+ #
176
257
  # def installed?
177
258
  # # List all the installed versions, and where they're at
178
259
  # end
179
- #
260
+ #
180
261
  # # TODO: dirty? method to show if there are any deltas
181
262
  # end
182
263
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: textmate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: 0.9.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yehuda Katz
@@ -9,11 +9,12 @@ autorequire: textmate
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-05-28 00:00:00 +02:00
12
+ date: 2009-09-19 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: thor
17
+ type: :runtime
17
18
  version_requirement:
18
19
  version_requirements: !ruby/object:Gem::Requirement
19
20
  requirements:
@@ -37,11 +38,13 @@ files:
37
38
  - bin/textmate
38
39
  has_rdoc: true
39
40
  homepage: http://yehudakatz.com
41
+ licenses: []
42
+
40
43
  post_install_message:
41
44
  rdoc_options: []
42
45
 
43
46
  require_paths:
44
- - lib
47
+ - bin
45
48
  required_ruby_version: !ruby/object:Gem::Requirement
46
49
  requirements:
47
50
  - - ">="
@@ -57,9 +60,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
57
60
  requirements: []
58
61
 
59
62
  rubyforge_project:
60
- rubygems_version: 1.1.1
63
+ rubygems_version: 1.3.5
61
64
  signing_key:
62
- specification_version: 2
65
+ specification_version: 3
63
66
  summary: Command-line textmate package manager
64
67
  test_files: []
65
68