gist 4.5.0 → 4.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 687ff483f835c421449de703f4b33a8ec8341ed7
4
- data.tar.gz: f5f46207b5e3cf3aa7d05253eb7f0f946e5429b3
3
+ metadata.gz: 306aea3698bda142b6130a3160a10c7cfc816142
4
+ data.tar.gz: b5cc50975bfa626326ac4c865f2d1849d3b95d50
5
5
  SHA512:
6
- metadata.gz: 903fd99ebdfc68873b338965fc03cc2f7bb1aa08acf22d6f16c8c12aeb736df1fec1d798419f1a734b2b26177869514f4c7125610419d66347f3e8338a1afdcc
7
- data.tar.gz: 8261a612326028a57aad653ba0889f004c20d6183658f4068736fdfa6f30466cac978912fe96e443b20a1ca99b8e9aeb3e9693913234650934006a9c0e2012f5
6
+ metadata.gz: 4f65b5b05ce628788db1fc1324191fa51c56f39827474efb44c0cbcef690001a89be5626b490a0111989ccea4a882fcb2e1d360cc75742d58fb2846e285a2129
7
+ data.tar.gz: 339e1387cb0f267a8a49e37963fc01fc353d6e77c7e1546ad029234c21672580ffac2ce0366e9443d22a020b9f92e5f4ac7c9cb30decc18ac3bb5ff5436fe3d6
data/README.md CHANGED
@@ -70,6 +70,11 @@ upload content to https://gist.github.com/.
70
70
  gist -l : all gists for authed user
71
71
  gist -l defunkt : list defunkt's public gists
72
72
 
73
+ To read a gist and print it to STDOUT
74
+
75
+ gist -r GIST_ID
76
+ gist -r 374130
77
+
73
78
  ‌See `gist --help` for more detail.
74
79
 
75
80
  ## Login
@@ -83,18 +88,42 @@ an OAuth2 token (with the "gist" permission).
83
88
  GitHub username: ConradIrwin
84
89
  GitHub password:
85
90
  2-factor auth code:
86
- Success! https://github.com/settings/applications
91
+ Success! https://github.com/settings/tokens
87
92
 
88
93
  This token is stored in `~/.gist` and used for all future gisting. If you need to
89
- you can revoke it from https://github.com/settings/applications, or just delete the
90
- file. If you need to store tokens for both github.com and a Github Enterprise instance
91
- you can save your Github Enterprise token in `~/.gist.github.example.com` where
92
- "github.example.com" is the URL for your Github Enterprise instance.
94
+ you can revoke it from https://github.com/settings/tokens, or just delete the
95
+ file.
93
96
 
94
97
  ‌After you've done this, you can still upload gists anonymously with `-a`.
95
98
 
96
99
  gist -a a.rb
97
100
 
101
+ ### GitHub Enterprise
102
+
103
+ If you'd like `gist` to use your locally installed [GitHub Enterprise](https://enterprise.github.com/),
104
+ you need to export the `GITHUB_URL` environment variable (usually done in your `~/.bashrc`).
105
+
106
+ export GITHUB_URL=http://github.internal.example.com/
107
+
108
+ Once you've done this and restarted your terminal (or run `source ~/.bashrc`), gist will
109
+ automatically use github enterprise instead of the public github.com
110
+
111
+ Your token for GitHub Enterprise will be stored in `.gist.<protocol>.<server.name>[.<port>]` (e.g.
112
+ `~.gist.http.github.internal.example.com` for the GITHUB_URL example above) instead of `~/.gist`.
113
+
114
+ If you have multiple servers or use Enterprise and public GitHub often, you can work around this by creating scripts
115
+ that set the env var and then run `gist`. Keep in mind that to use the public GitHub you must unset the env var. Just
116
+ setting it to the public URL will not work. Use `unset GITHUB_URL`
117
+
118
+ ### Token file format
119
+
120
+ If you cannot use passwords, as most Enterprise installations do, you can generate the token via the web interface
121
+ and then simply save the string in the correct file. Avoid line breaks or you might see:
122
+ ```
123
+ $ gist -l
124
+ Error: Bad credentials
125
+ ```
126
+
98
127
  # Library
99
128
 
100
129
  ‌You can also use Gist as a library from inside your ruby code:
@@ -126,16 +155,6 @@ NOTE: The access_token must have the "gist" scope.
126
155
  ‌This will take them through the process of obtaining an OAuth2 token, and storing it
127
156
  in `~/.gist`, where it can later be read by `Gist.gist`
128
157
 
129
- ## GitHub enterprise
130
-
131
- ‌If you'd like `gist` to use your locally installed [GitHub Enterprise](https://enterprise.github.com/),
132
- you need to export the `GITHUB_URL` environment variable in your `~/.bashrc`.
133
-
134
- export GITHUB_URL=http://github.internal.example.com/
135
-
136
- ‌Once you've done this and restarted your terminal (or run `source ~/.bashrc`), gist will
137
- automatically use github enterprise instead of the public github.com
138
-
139
158
  ## Configuration
140
159
 
141
160
  ‌If you'd like `-o` or `-c` to be the default when you use the gist executable, add an
data/Rakefile CHANGED
@@ -23,7 +23,9 @@ task :standalone do
23
23
  f.puts "#!/usr/bin/env ruby"
24
24
  f.puts "# This is generated from https://github.com/defunkt/gist using 'rake standalone'"
25
25
  f.puts "# any changes will be overwritten."
26
- f.puts File.read("lib/gist.rb").split("require 'json'\n").join(File.read("vendor/json.rb"))
26
+ f.puts File.read("lib/gist.rb")
27
+ .split("require 'json'\n").join(File.read("vendor/json.rb"))
28
+ .split("require 'netrc'\n").join(File.read("vendor/netrc.rb"))
27
29
 
28
30
  f.puts File.read("bin/gist").gsub(/^require.*gist.*\n/, '');
29
31
  end
data/bin/gist CHANGED
@@ -123,6 +123,10 @@ Usage: #{executable_name} [-o|-c|-e] [-p] [-s] [-R] [-d DESC] [-a] [-u URL] [-P]
123
123
  options[:read] = id
124
124
  end
125
125
 
126
+ opts.on("--delete [ URL | ID ]", "Delete a gist") do |id|
127
+ options[:delete] = id
128
+ end
129
+
126
130
  opts.on_tail("-h","--help", "Show this message.") do
127
131
  puts opts
128
132
  exit
@@ -167,6 +171,11 @@ begin
167
171
  exit
168
172
  end
169
173
 
174
+ if options.key? :delete
175
+ Gist.delete_gist(options[:delete])
176
+ exit
177
+ end
178
+
170
179
  if options[:paste]
171
180
  puts Gist.gist(Gist.paste, options)
172
181
  else
data/build/gist CHANGED
@@ -1314,22 +1314,313 @@ rescue LoadError
1314
1314
  require File.join File.dirname(File.dirname(__FILE__)), 'vendor', 'json.rb'
1315
1315
  end
1316
1316
 
1317
+ begin
1318
+
1319
+ # The netrc library (https://github.com/heroku/netrc) is reproduced below, along
1320
+ # with its copyright and license:
1321
+ # - https://raw.githubusercontent.com/heroku/netrc/262ef111/LICENSE.md
1322
+ # - https://raw.githubusercontent.com/heroku/netrc/262ef111/lib/netrc.rb
1323
+
1324
+ # The MIT License (MIT)
1325
+ #
1326
+ # Copyright (c) 2011-2014 [CONTRIBUTORS.md](https://github.com/geemus/netrc/blob/master/CONTRIBUTORS.md)
1327
+ #
1328
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
1329
+ # this software and associated documentation files (the "Software"), to deal in
1330
+ # the Software without restriction, including without limitation the rights to
1331
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
1332
+ # the Software, and to permit persons to whom the Software is furnished to do so,
1333
+ # subject to the following conditions:
1334
+ #
1335
+ # The above copyright notice and this permission notice shall be included in all
1336
+ # copies or substantial portions of the Software.
1337
+ #
1338
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1339
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
1340
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
1341
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
1342
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
1343
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1344
+
1345
+ require 'rbconfig'
1346
+ require 'io/console'
1347
+
1348
+ class Netrc
1349
+ VERSION = "0.11.0"
1350
+
1351
+ # see http://stackoverflow.com/questions/4871309/what-is-the-correct-way-to-detect-if-ruby-is-running-on-windows
1352
+ WINDOWS = RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/
1353
+ CYGWIN = RbConfig::CONFIG["host_os"] =~ /cygwin/
1354
+
1355
+ def self.default_path
1356
+ File.join(ENV['NETRC'] || home_path, netrc_filename)
1357
+ end
1358
+
1359
+ def self.home_path
1360
+ home = Dir.respond_to?(:home) ? Dir.home : ENV['HOME']
1361
+
1362
+ if WINDOWS && !CYGWIN
1363
+ home ||= File.join(ENV['HOMEDRIVE'], ENV['HOMEPATH']) if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
1364
+ home ||= ENV['USERPROFILE']
1365
+ # XXX: old stuff; most likely unnecessary
1366
+ home = home.tr("\\", "/") unless home.nil?
1367
+ end
1368
+
1369
+ (home && File.readable?(home)) ? home : Dir.pwd
1370
+ rescue ArgumentError
1371
+ return Dir.pwd
1372
+ end
1373
+
1374
+ def self.netrc_filename
1375
+ WINDOWS && !CYGWIN ? "_netrc" : ".netrc"
1376
+ end
1377
+
1378
+ def self.config
1379
+ @config ||= {}
1380
+ end
1381
+
1382
+ def self.configure
1383
+ yield(self.config) if block_given?
1384
+ self.config
1385
+ end
1386
+
1387
+ def self.check_permissions(path)
1388
+ perm = File.stat(path).mode & 0777
1389
+ if perm != 0600 && !(WINDOWS) && !(Netrc.config[:allow_permissive_netrc_file])
1390
+ raise Error, "Permission bits for '#{path}' should be 0600, but are "+perm.to_s(8)
1391
+ end
1392
+ end
1393
+
1394
+ # Reads path and parses it as a .netrc file. If path doesn't
1395
+ # exist, returns an empty object. Decrypt paths ending in .gpg.
1396
+ def self.read(path=default_path)
1397
+ check_permissions(path)
1398
+ data = if path =~ /\.gpg$/
1399
+ decrypted = if ENV['GPG_AGENT_INFO']
1400
+ `gpg --batch --quiet --decrypt #{path}`
1401
+ else
1402
+ print "Enter passphrase for #{path}: "
1403
+ STDIN.noecho do
1404
+ `gpg --batch --passphrase-fd 0 --quiet --decrypt #{path}`
1405
+ end
1406
+ end
1407
+ if $?.success?
1408
+ decrypted
1409
+ else
1410
+ raise Error.new("Decrypting #{path} failed.") unless $?.success?
1411
+ end
1412
+ else
1413
+ File.read(path)
1414
+ end
1415
+ new(path, parse(lex(data.lines.to_a)))
1416
+ rescue Errno::ENOENT
1417
+ new(path, parse(lex([])))
1418
+ end
1419
+
1420
+ class TokenArray < Array
1421
+ def take
1422
+ if length < 1
1423
+ raise Error, "unexpected EOF"
1424
+ end
1425
+ shift
1426
+ end
1427
+
1428
+ def readto
1429
+ l = []
1430
+ while length > 0 && ! yield(self[0])
1431
+ l << shift
1432
+ end
1433
+ return l.join
1434
+ end
1435
+ end
1436
+
1437
+ def self.lex(lines)
1438
+ tokens = TokenArray.new
1439
+ for line in lines
1440
+ content, comment = line.split(/(\s*#.*)/m)
1441
+ content.each_char do |char|
1442
+ case char
1443
+ when /\s/
1444
+ if tokens.last && tokens.last[-1..-1] =~ /\s/
1445
+ tokens.last << char
1446
+ else
1447
+ tokens << char
1448
+ end
1449
+ else
1450
+ if tokens.last && tokens.last[-1..-1] =~ /\S/
1451
+ tokens.last << char
1452
+ else
1453
+ tokens << char
1454
+ end
1455
+ end
1456
+ end
1457
+ if comment
1458
+ tokens << comment
1459
+ end
1460
+ end
1461
+ tokens
1462
+ end
1463
+
1464
+ def self.skip?(s)
1465
+ s =~ /^\s/
1466
+ end
1467
+
1468
+
1469
+
1470
+ # Returns two values, a header and a list of items.
1471
+ # Each item is a tuple, containing some or all of:
1472
+ # - machine keyword (including trailing whitespace+comments)
1473
+ # - machine name
1474
+ # - login keyword (including surrounding whitespace+comments)
1475
+ # - login
1476
+ # - password keyword (including surrounding whitespace+comments)
1477
+ # - password
1478
+ # - trailing chars
1479
+ # This lets us change individual fields, then write out the file
1480
+ # with all its original formatting.
1481
+ def self.parse(ts)
1482
+ cur, item = [], []
1483
+
1484
+ unless ts.is_a?(TokenArray)
1485
+ ts = TokenArray.new(ts)
1486
+ end
1487
+
1488
+ pre = ts.readto{|t| t == "machine" || t == "default"}
1489
+
1490
+ while ts.length > 0
1491
+ if ts[0] == 'default'
1492
+ cur << ts.take.to_sym
1493
+ cur << ''
1494
+ else
1495
+ cur << ts.take + ts.readto{|t| ! skip?(t)}
1496
+ cur << ts.take
1497
+ end
1498
+
1499
+ if ts.include?('login')
1500
+ cur << ts.readto{|t| t == "login"} + ts.take + ts.readto{|t| ! skip?(t)}
1501
+ cur << ts.take
1502
+ end
1503
+
1504
+ if ts.include?('password')
1505
+ cur << ts.readto{|t| t == "password"} + ts.take + ts.readto{|t| ! skip?(t)}
1506
+ cur << ts.take
1507
+ end
1508
+
1509
+ cur << ts.readto{|t| t == "machine" || t == "default"}
1510
+
1511
+ item << cur
1512
+ cur = []
1513
+ end
1514
+
1515
+ [pre, item]
1516
+ end
1517
+
1518
+ def initialize(path, data)
1519
+ @new_item_prefix = ''
1520
+ @path = path
1521
+ @pre, @data = data
1522
+
1523
+ if @data && @data.last && :default == @data.last[0]
1524
+ @default = @data.pop
1525
+ else
1526
+ @default = nil
1527
+ end
1528
+ end
1529
+
1530
+ attr_accessor :new_item_prefix
1531
+
1532
+ def [](k)
1533
+ if item = @data.detect {|datum| datum[1] == k}
1534
+ Entry.new(item[3], item[5])
1535
+ elsif @default
1536
+ Entry.new(@default[3], @default[5])
1537
+ end
1538
+ end
1539
+
1540
+ def []=(k, info)
1541
+ if item = @data.detect {|datum| datum[1] == k}
1542
+ item[3], item[5] = info
1543
+ else
1544
+ @data << new_item(k, info[0], info[1])
1545
+ end
1546
+ end
1547
+
1548
+ def length
1549
+ @data.length
1550
+ end
1551
+
1552
+ def delete(key)
1553
+ datum = nil
1554
+ for value in @data
1555
+ if value[1] == key
1556
+ datum = value
1557
+ break
1558
+ end
1559
+ end
1560
+ @data.delete(datum)
1561
+ end
1562
+
1563
+ def each(&block)
1564
+ @data.each(&block)
1565
+ end
1566
+
1567
+ def new_item(m, l, p)
1568
+ [new_item_prefix+"machine ", m, "\n login ", l, "\n password ", p, "\n"]
1569
+ end
1570
+
1571
+ def save
1572
+ if @path =~ /\.gpg$/
1573
+ e = IO.popen("gpg -a --batch --default-recipient-self -e", "r+") do |gpg|
1574
+ gpg.puts(unparse)
1575
+ gpg.close_write
1576
+ gpg.read
1577
+ end
1578
+ raise Error.new("Encrypting #{@path} failed.") unless $?.success?
1579
+ File.open(@path, 'w', 0600) {|file| file.print(e)}
1580
+ else
1581
+ File.open(@path, 'w', 0600) {|file| file.print(unparse)}
1582
+ end
1583
+ end
1584
+
1585
+ def unparse
1586
+ @pre + @data.map do |datum|
1587
+ datum = datum.join
1588
+ unless datum[-1..-1] == "\n"
1589
+ datum << "\n"
1590
+ else
1591
+ datum
1592
+ end
1593
+ end.join
1594
+ end
1595
+
1596
+ Entry = Struct.new(:login, :password) do
1597
+ alias to_ary to_a
1598
+ end
1599
+
1600
+ end
1601
+
1602
+ class Netrc::Error < ::StandardError
1603
+ end
1604
+ rescue LoadError
1605
+ require File.join File.dirname(File.dirname(__FILE__)), 'vendor', 'netrc.rb'
1606
+ end
1607
+
1317
1608
  # It just gists.
1318
1609
  module Gist
1319
1610
  extend self
1320
1611
 
1321
- VERSION = '4.5.0'
1612
+ VERSION = '4.6.0'
1322
1613
 
1323
1614
  # A list of clipboard commands with copy and paste support.
1324
1615
  CLIPBOARD_COMMANDS = {
1616
+ 'pbcopy' => 'pbpaste',
1325
1617
  'xclip' => 'xclip -o',
1326
1618
  'xsel -i' => 'xsel -o',
1327
- 'pbcopy' => 'pbpaste',
1328
- 'putclip' => 'getclip'
1619
+ 'putclip' => 'getclip',
1329
1620
  }
1330
1621
 
1331
1622
  GITHUB_API_URL = URI("https://api.github.com/")
1332
- GIT_IO_URL = URI("http://git.io")
1623
+ GIT_IO_URL = URI("https://git.io")
1333
1624
 
1334
1625
  GITHUB_BASE_PATH = ""
1335
1626
  GHE_BASE_PATH = "/api/v3"
@@ -1350,7 +1641,7 @@ module Gist
1350
1641
  module AuthTokenFile
1351
1642
  def self.filename
1352
1643
  if ENV.key?(URL_ENV_NAME)
1353
- File.expand_path "~/.gist.#{ENV[URL_ENV_NAME].gsub(/[^a-z.]/, '')}"
1644
+ File.expand_path "~/.gist.#{ENV[URL_ENV_NAME].gsub(/:/, '.').gsub(/[^a-z0-9.]/, '')}"
1354
1645
  else
1355
1646
  File.expand_path "~/.gist"
1356
1647
  end
@@ -1371,7 +1662,7 @@ module Gist
1371
1662
  #
1372
1663
  # @return [String] string value of access token or `nil`, if not found
1373
1664
  def auth_token
1374
- @token ||= AuthTokenFile.read rescue nil
1665
+ @token ||= AuthTokenFile.read rescue nil || Netrc.read[self.api_url.host][1]
1375
1666
  end
1376
1667
 
1377
1668
  # Upload a gist to https://gist.github.com
@@ -1529,6 +1820,27 @@ module Gist
1529
1820
  end
1530
1821
  end
1531
1822
 
1823
+ def delete_gist(id)
1824
+ id = id.split("/").last
1825
+ url = "#{base_path}/gists/#{id}"
1826
+
1827
+ access_token = auth_token()
1828
+ if access_token.to_s != ''
1829
+ url << "?access_token=" << CGI.escape(access_token)
1830
+
1831
+ request = Net::HTTP::Delete.new(url)
1832
+ response = http(api_url, request)
1833
+ else
1834
+ raise Error, "Not authenticated. Use 'gist --login' to login."
1835
+ end
1836
+
1837
+ if response.code == '204'
1838
+ puts "Deleted!"
1839
+ else
1840
+ raise Error, "Gist with id of #{id} does not exist."
1841
+ end
1842
+ end
1843
+
1532
1844
  def get_gist_pages(url)
1533
1845
 
1534
1846
  request = Net::HTTP::Get.new(url)
@@ -1569,10 +1881,12 @@ module Gist
1569
1881
  # @param [String] url
1570
1882
  # @return [String] shortened url, or long url if shortening fails
1571
1883
  def shorten(url)
1572
- request = Net::HTTP::Post.new("/")
1884
+ request = Net::HTTP::Post.new("/create")
1573
1885
  request.set_form_data(:url => url)
1574
1886
  response = http(GIT_IO_URL, request)
1575
1887
  case response.code
1888
+ when "200"
1889
+ URI.join(GIT_IO_URL, response.body).to_s
1576
1890
  when "201"
1577
1891
  response['Location']
1578
1892
  else
@@ -1645,7 +1959,7 @@ module Gist
1645
1959
 
1646
1960
  if Net::HTTPCreated === response
1647
1961
  AuthTokenFile.write JSON.parse(response.body)['token']
1648
- puts "Success! #{ENV[URL_ENV_NAME] || "https://github.com/"}settings/applications"
1962
+ puts "Success! #{ENV[URL_ENV_NAME] || "https://github.com/"}settings/tokens"
1649
1963
  return
1650
1964
  elsif Net::HTTPUnauthorized === response
1651
1965
  puts "Error: #{JSON.parse(response.body)['message']}"
@@ -1962,6 +2276,10 @@ Usage: #{executable_name} [-o|-c|-e] [-p] [-s] [-R] [-d DESC] [-a] [-u URL] [-P]
1962
2276
  options[:read] = id
1963
2277
  end
1964
2278
 
2279
+ opts.on("--delete [ URL | ID ]", "Delete a gist") do |id|
2280
+ options[:delete] = id
2281
+ end
2282
+
1965
2283
  opts.on_tail("-h","--help", "Show this message.") do
1966
2284
  puts opts
1967
2285
  exit
@@ -2006,6 +2324,11 @@ begin
2006
2324
  exit
2007
2325
  end
2008
2326
 
2327
+ if options.key? :delete
2328
+ Gist.delete_gist(options[:delete])
2329
+ exit
2330
+ end
2331
+
2009
2332
  if options[:paste]
2010
2333
  puts Gist.gist(Gist.paste, options)
2011
2334
  else
@@ -1,7 +1,7 @@
1
1
  .\" generated with Ronn/v0.7.3
2
2
  .\" http://github.com/rtomayko/ronn/tree/0.7.3
3
3
  .
4
- .TH "GIST" "1" "November 2015" "" "Gist manual"
4
+ .TH "GIST" "1" "May 2017" "" "Gist manual"
5
5
  .
6
6
  .SH "NAME"
7
7
  \fBgist\fR \- upload code to https://gist\.github\.com
@@ -99,6 +99,22 @@ To list (public gists or all gists for authed user) gists for user
99
99
  .IP
100
100
  gist \-l : all gists for authed user gist \-l defunkt : list defunkt\'s public gists
101
101
  .
102
+ .IP "" 0
103
+ .
104
+ .P
105
+ To read a gist and print it to STDOUT
106
+ .
107
+ .IP "" 4
108
+ .
109
+ .nf
110
+
111
+ gist \-r GIST_ID
112
+ gist \-r 374130
113
+ .
114
+ .fi
115
+ .
116
+ .IP "" 0
117
+ .
102
118
  .IP "\(bu" 4
103
119
  See \fBgist \-\-help\fR for more detail\.
104
120
  .
@@ -116,14 +132,14 @@ Obtaining OAuth2 access_token from github\.
116
132
  GitHub username: ConradIrwin
117
133
  GitHub password:
118
134
  2\-factor auth code:
119
- Success! https://github\.com/settings/applications
135
+ Success! https://github\.com/settings/tokens
120
136
  .
121
137
  .fi
122
138
  .
123
139
  .IP "" 0
124
140
  .
125
141
  .P
126
- This token is stored in \fB~/\.gist\fR and used for all future gisting\. If you need to you can revoke it from https://github\.com/settings/applications, or just delete the file\. If you need to store tokens for both github\.com and a Github Enterprise instance you can save your Github Enterprise token in \fB~/\.gist\.github\.example\.com\fR where "github\.example\.com" is the URL for your Github Enterprise instance\.
142
+ This token is stored in \fB~/\.gist\fR and used for all future gisting\. If you need to you can revoke it from https://github\.com/settings/tokens, or just delete the file\.
127
143
  .
128
144
  .IP "\(bu" 4
129
145
  After you\'ve done this, you can still upload gists anonymously with \fB\-a\fR\.
@@ -133,6 +149,31 @@ gist \-a a\.rb
133
149
  .
134
150
  .IP "" 0
135
151
  .
152
+ .SS "GitHub Enterprise"
153
+ If you\'d like \fBgist\fR to use your locally installed GitHub Enterprise \fIhttps://enterprise\.github\.com/\fR, you need to export the \fBGITHUB_URL\fR environment variable (usually done in your \fB~/\.bashrc\fR)\.
154
+ .
155
+ .IP "" 4
156
+ .
157
+ .nf
158
+
159
+ export GITHUB_URL=http://github\.internal\.example\.com/
160
+ .
161
+ .fi
162
+ .
163
+ .IP "" 0
164
+ .
165
+ .P
166
+ Once you\'ve done this and restarted your terminal (or run \fBsource ~/\.bashrc\fR), gist will automatically use github enterprise instead of the public github\.com
167
+ .
168
+ .P
169
+ Your token for GitHub Enterprise will be stored in \fB\.gist\.<protocol>\.<server\.name>[\.<port>]\fR (e\.g\. \fB~\.gist\.http\.github\.internal\.example\.com\fR for the GITHUB_URL example above) instead of \fB~/\.gist\fR\.
170
+ .
171
+ .P
172
+ If you have multiple servers or use Enterprise and public GitHub often, you can work around this by creating scripts that set the env var and then run \fBgist\fR\. Keep in mind that to use the public GitHub you must unset the env var\. Just setting it to the public URL will not work\. Use \fBunset GITHUB_URL\fR
173
+ .
174
+ .SS "Token file format"
175
+ If you cannot use passwords, as most Enterprise installations do, you can generate the token via the web interface and then simply save the string in the correct file\. Avoid line breaks or you might see: \fB$ gist \-l Error: Bad credentials\fR
176
+ .
136
177
  .TP
137
178
  You can also use Gist as a library from inside your ruby code:
138
179
  .
@@ -188,19 +229,6 @@ This will take them through the process of obtaining an OAuth2 token, and storin
188
229
  .
189
230
  .IP "" 0
190
231
  .
191
- .SH "GitHub enterprise"
192
- .
193
- .IP "\(bu" 4
194
- If you\'d like \fBgist\fR to use your locally installed GitHub Enterprise \fIhttps://enterprise\.github\.com/\fR, you need to export the \fBGITHUB_URL\fR environment variable in your \fB~/\.bashrc\fR\.
195
- .
196
- .IP
197
- export GITHUB_URL=http://github\.internal\.example\.com/
198
- .
199
- .IP "\(bu" 4
200
- Once you\'ve done this and restarted your terminal (or run \fBsource ~/\.bashrc\fR), gist will automatically use github enterprise instead of the public github\.com
201
- .
202
- .IP "" 0
203
- .
204
232
  .SH "Configuration"
205
233
  .
206
234
  .IP "\(bu" 4
@@ -8,22 +8,28 @@ rescue LoadError
8
8
  require File.join File.dirname(File.dirname(__FILE__)), 'vendor', 'json.rb'
9
9
  end
10
10
 
11
+ begin
12
+ require 'netrc'
13
+ rescue LoadError
14
+ require File.join File.dirname(File.dirname(__FILE__)), 'vendor', 'netrc.rb'
15
+ end
16
+
11
17
  # It just gists.
12
18
  module Gist
13
19
  extend self
14
20
 
15
- VERSION = '4.5.0'
21
+ VERSION = '4.6.0'
16
22
 
17
23
  # A list of clipboard commands with copy and paste support.
18
24
  CLIPBOARD_COMMANDS = {
25
+ 'pbcopy' => 'pbpaste',
19
26
  'xclip' => 'xclip -o',
20
27
  'xsel -i' => 'xsel -o',
21
- 'pbcopy' => 'pbpaste',
22
- 'putclip' => 'getclip'
28
+ 'putclip' => 'getclip',
23
29
  }
24
30
 
25
31
  GITHUB_API_URL = URI("https://api.github.com/")
26
- GIT_IO_URL = URI("http://git.io")
32
+ GIT_IO_URL = URI("https://git.io")
27
33
 
28
34
  GITHUB_BASE_PATH = ""
29
35
  GHE_BASE_PATH = "/api/v3"
@@ -44,7 +50,7 @@ module Gist
44
50
  module AuthTokenFile
45
51
  def self.filename
46
52
  if ENV.key?(URL_ENV_NAME)
47
- File.expand_path "~/.gist.#{ENV[URL_ENV_NAME].gsub(/[^a-z.]/, '')}"
53
+ File.expand_path "~/.gist.#{ENV[URL_ENV_NAME].gsub(/:/, '.').gsub(/[^a-z0-9.]/, '')}"
48
54
  else
49
55
  File.expand_path "~/.gist"
50
56
  end
@@ -65,7 +71,7 @@ module Gist
65
71
  #
66
72
  # @return [String] string value of access token or `nil`, if not found
67
73
  def auth_token
68
- @token ||= AuthTokenFile.read rescue nil
74
+ @token ||= AuthTokenFile.read rescue nil || Netrc.read[self.api_url.host][1]
69
75
  end
70
76
 
71
77
  # Upload a gist to https://gist.github.com
@@ -223,6 +229,27 @@ module Gist
223
229
  end
224
230
  end
225
231
 
232
+ def delete_gist(id)
233
+ id = id.split("/").last
234
+ url = "#{base_path}/gists/#{id}"
235
+
236
+ access_token = auth_token()
237
+ if access_token.to_s != ''
238
+ url << "?access_token=" << CGI.escape(access_token)
239
+
240
+ request = Net::HTTP::Delete.new(url)
241
+ response = http(api_url, request)
242
+ else
243
+ raise Error, "Not authenticated. Use 'gist --login' to login."
244
+ end
245
+
246
+ if response.code == '204'
247
+ puts "Deleted!"
248
+ else
249
+ raise Error, "Gist with id of #{id} does not exist."
250
+ end
251
+ end
252
+
226
253
  def get_gist_pages(url)
227
254
 
228
255
  request = Net::HTTP::Get.new(url)
@@ -263,10 +290,12 @@ module Gist
263
290
  # @param [String] url
264
291
  # @return [String] shortened url, or long url if shortening fails
265
292
  def shorten(url)
266
- request = Net::HTTP::Post.new("/")
293
+ request = Net::HTTP::Post.new("/create")
267
294
  request.set_form_data(:url => url)
268
295
  response = http(GIT_IO_URL, request)
269
296
  case response.code
297
+ when "200"
298
+ URI.join(GIT_IO_URL, response.body).to_s
270
299
  when "201"
271
300
  response['Location']
272
301
  else
@@ -339,7 +368,7 @@ module Gist
339
368
 
340
369
  if Net::HTTPCreated === response
341
370
  AuthTokenFile.write JSON.parse(response.body)['token']
342
- puts "Success! #{ENV[URL_ENV_NAME] || "https://github.com/"}settings/applications"
371
+ puts "Success! #{ENV[URL_ENV_NAME] || "https://github.com/"}settings/tokens"
343
372
  return
344
373
  elsif Net::HTTPUnauthorized === response
345
374
  puts "Error: #{JSON.parse(response.body)['message']}"
@@ -19,10 +19,10 @@ describe Gist::AuthTokenFile do
19
19
  before do
20
20
  ENV[Gist::URL_ENV_NAME] = github_url
21
21
  end
22
- let(:github_url) { "gh.custom.org" }
22
+ let(:github_url) { "http://gh.custom.org:442/" }
23
23
 
24
24
  it "is ~/.gist.{custom_github_url}" do
25
- File.should_receive(:expand_path).with("~/.gist.#{github_url}").and_return(filename)
25
+ File.should_receive(:expand_path).with("~/.gist.http.gh.custom.org.442").and_return(filename)
26
26
  subject.filename.should be filename
27
27
  end
28
28
  end
@@ -1,11 +1,15 @@
1
1
  describe '...' do
2
2
  before do
3
3
  stub_request(:post, /api\.github.com\/gists/).to_return(:body => '{"html_url": "http://github.com/"}')
4
- stub_request(:post, "http://git.io/").to_return(:status => 201, :headers => { 'Location' => 'http://git.io/XXXXXX' })
5
4
  end
6
5
 
7
- it "should return a shortened version of the URL" do
8
- Gist.gist("Test gist", :output => :short_url, :anonymous => true).should == "http://git.io/XXXXXX"
6
+ it "should return a shortened version of the URL when response is 200" do
7
+ stub_request(:post, "https://git.io/create").to_return(:status => 200, :body => 'XXXXXX')
8
+ Gist.gist("Test gist", :output => :short_url, :anonymous => true).should == "https://git.io/XXXXXX"
9
9
  end
10
- end
11
10
 
11
+ it "should return a shortened version of the URL when response is 201" do
12
+ stub_request(:post, "https://git.io/create").to_return(:status => 201, :headers => { 'Location' => 'https://git.io/XXXXXX' })
13
+ Gist.gist("Test gist", :output => :short_url, :anonymous => true).should == "https://git.io/XXXXXX"
14
+ end
15
+ end
@@ -0,0 +1,286 @@
1
+
2
+ # The netrc library (https://github.com/heroku/netrc) is reproduced below, along
3
+ # with its copyright and license:
4
+ # - https://raw.githubusercontent.com/heroku/netrc/262ef111/LICENSE.md
5
+ # - https://raw.githubusercontent.com/heroku/netrc/262ef111/lib/netrc.rb
6
+
7
+ # The MIT License (MIT)
8
+ #
9
+ # Copyright (c) 2011-2014 [CONTRIBUTORS.md](https://github.com/geemus/netrc/blob/master/CONTRIBUTORS.md)
10
+ #
11
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
12
+ # this software and associated documentation files (the "Software"), to deal in
13
+ # the Software without restriction, including without limitation the rights to
14
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
15
+ # the Software, and to permit persons to whom the Software is furnished to do so,
16
+ # subject to the following conditions:
17
+ #
18
+ # The above copyright notice and this permission notice shall be included in all
19
+ # copies or substantial portions of the Software.
20
+ #
21
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
23
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
24
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
25
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
26
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+
28
+ require 'rbconfig'
29
+ require 'io/console'
30
+
31
+ class Netrc
32
+ VERSION = "0.11.0"
33
+
34
+ # see http://stackoverflow.com/questions/4871309/what-is-the-correct-way-to-detect-if-ruby-is-running-on-windows
35
+ WINDOWS = RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/
36
+ CYGWIN = RbConfig::CONFIG["host_os"] =~ /cygwin/
37
+
38
+ def self.default_path
39
+ File.join(ENV['NETRC'] || home_path, netrc_filename)
40
+ end
41
+
42
+ def self.home_path
43
+ home = Dir.respond_to?(:home) ? Dir.home : ENV['HOME']
44
+
45
+ if WINDOWS && !CYGWIN
46
+ home ||= File.join(ENV['HOMEDRIVE'], ENV['HOMEPATH']) if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
47
+ home ||= ENV['USERPROFILE']
48
+ # XXX: old stuff; most likely unnecessary
49
+ home = home.tr("\\", "/") unless home.nil?
50
+ end
51
+
52
+ (home && File.readable?(home)) ? home : Dir.pwd
53
+ rescue ArgumentError
54
+ return Dir.pwd
55
+ end
56
+
57
+ def self.netrc_filename
58
+ WINDOWS && !CYGWIN ? "_netrc" : ".netrc"
59
+ end
60
+
61
+ def self.config
62
+ @config ||= {}
63
+ end
64
+
65
+ def self.configure
66
+ yield(self.config) if block_given?
67
+ self.config
68
+ end
69
+
70
+ def self.check_permissions(path)
71
+ perm = File.stat(path).mode & 0777
72
+ if perm != 0600 && !(WINDOWS) && !(Netrc.config[:allow_permissive_netrc_file])
73
+ raise Error, "Permission bits for '#{path}' should be 0600, but are "+perm.to_s(8)
74
+ end
75
+ end
76
+
77
+ # Reads path and parses it as a .netrc file. If path doesn't
78
+ # exist, returns an empty object. Decrypt paths ending in .gpg.
79
+ def self.read(path=default_path)
80
+ check_permissions(path)
81
+ data = if path =~ /\.gpg$/
82
+ decrypted = if ENV['GPG_AGENT_INFO']
83
+ `gpg --batch --quiet --decrypt #{path}`
84
+ else
85
+ print "Enter passphrase for #{path}: "
86
+ STDIN.noecho do
87
+ `gpg --batch --passphrase-fd 0 --quiet --decrypt #{path}`
88
+ end
89
+ end
90
+ if $?.success?
91
+ decrypted
92
+ else
93
+ raise Error.new("Decrypting #{path} failed.") unless $?.success?
94
+ end
95
+ else
96
+ File.read(path)
97
+ end
98
+ new(path, parse(lex(data.lines.to_a)))
99
+ rescue Errno::ENOENT
100
+ new(path, parse(lex([])))
101
+ end
102
+
103
+ class TokenArray < Array
104
+ def take
105
+ if length < 1
106
+ raise Error, "unexpected EOF"
107
+ end
108
+ shift
109
+ end
110
+
111
+ def readto
112
+ l = []
113
+ while length > 0 && ! yield(self[0])
114
+ l << shift
115
+ end
116
+ return l.join
117
+ end
118
+ end
119
+
120
+ def self.lex(lines)
121
+ tokens = TokenArray.new
122
+ for line in lines
123
+ content, comment = line.split(/(\s*#.*)/m)
124
+ content.each_char do |char|
125
+ case char
126
+ when /\s/
127
+ if tokens.last && tokens.last[-1..-1] =~ /\s/
128
+ tokens.last << char
129
+ else
130
+ tokens << char
131
+ end
132
+ else
133
+ if tokens.last && tokens.last[-1..-1] =~ /\S/
134
+ tokens.last << char
135
+ else
136
+ tokens << char
137
+ end
138
+ end
139
+ end
140
+ if comment
141
+ tokens << comment
142
+ end
143
+ end
144
+ tokens
145
+ end
146
+
147
+ def self.skip?(s)
148
+ s =~ /^\s/
149
+ end
150
+
151
+
152
+
153
+ # Returns two values, a header and a list of items.
154
+ # Each item is a tuple, containing some or all of:
155
+ # - machine keyword (including trailing whitespace+comments)
156
+ # - machine name
157
+ # - login keyword (including surrounding whitespace+comments)
158
+ # - login
159
+ # - password keyword (including surrounding whitespace+comments)
160
+ # - password
161
+ # - trailing chars
162
+ # This lets us change individual fields, then write out the file
163
+ # with all its original formatting.
164
+ def self.parse(ts)
165
+ cur, item = [], []
166
+
167
+ unless ts.is_a?(TokenArray)
168
+ ts = TokenArray.new(ts)
169
+ end
170
+
171
+ pre = ts.readto{|t| t == "machine" || t == "default"}
172
+
173
+ while ts.length > 0
174
+ if ts[0] == 'default'
175
+ cur << ts.take.to_sym
176
+ cur << ''
177
+ else
178
+ cur << ts.take + ts.readto{|t| ! skip?(t)}
179
+ cur << ts.take
180
+ end
181
+
182
+ if ts.include?('login')
183
+ cur << ts.readto{|t| t == "login"} + ts.take + ts.readto{|t| ! skip?(t)}
184
+ cur << ts.take
185
+ end
186
+
187
+ if ts.include?('password')
188
+ cur << ts.readto{|t| t == "password"} + ts.take + ts.readto{|t| ! skip?(t)}
189
+ cur << ts.take
190
+ end
191
+
192
+ cur << ts.readto{|t| t == "machine" || t == "default"}
193
+
194
+ item << cur
195
+ cur = []
196
+ end
197
+
198
+ [pre, item]
199
+ end
200
+
201
+ def initialize(path, data)
202
+ @new_item_prefix = ''
203
+ @path = path
204
+ @pre, @data = data
205
+
206
+ if @data && @data.last && :default == @data.last[0]
207
+ @default = @data.pop
208
+ else
209
+ @default = nil
210
+ end
211
+ end
212
+
213
+ attr_accessor :new_item_prefix
214
+
215
+ def [](k)
216
+ if item = @data.detect {|datum| datum[1] == k}
217
+ Entry.new(item[3], item[5])
218
+ elsif @default
219
+ Entry.new(@default[3], @default[5])
220
+ end
221
+ end
222
+
223
+ def []=(k, info)
224
+ if item = @data.detect {|datum| datum[1] == k}
225
+ item[3], item[5] = info
226
+ else
227
+ @data << new_item(k, info[0], info[1])
228
+ end
229
+ end
230
+
231
+ def length
232
+ @data.length
233
+ end
234
+
235
+ def delete(key)
236
+ datum = nil
237
+ for value in @data
238
+ if value[1] == key
239
+ datum = value
240
+ break
241
+ end
242
+ end
243
+ @data.delete(datum)
244
+ end
245
+
246
+ def each(&block)
247
+ @data.each(&block)
248
+ end
249
+
250
+ def new_item(m, l, p)
251
+ [new_item_prefix+"machine ", m, "\n login ", l, "\n password ", p, "\n"]
252
+ end
253
+
254
+ def save
255
+ if @path =~ /\.gpg$/
256
+ e = IO.popen("gpg -a --batch --default-recipient-self -e", "r+") do |gpg|
257
+ gpg.puts(unparse)
258
+ gpg.close_write
259
+ gpg.read
260
+ end
261
+ raise Error.new("Encrypting #{@path} failed.") unless $?.success?
262
+ File.open(@path, 'w', 0600) {|file| file.print(e)}
263
+ else
264
+ File.open(@path, 'w', 0600) {|file| file.print(unparse)}
265
+ end
266
+ end
267
+
268
+ def unparse
269
+ @pre + @data.map do |datum|
270
+ datum = datum.join
271
+ unless datum[-1..-1] == "\n"
272
+ datum << "\n"
273
+ else
274
+ datum
275
+ end
276
+ end.join
277
+ end
278
+
279
+ Entry = Struct.new(:login, :password) do
280
+ alias to_ary to_a
281
+ end
282
+
283
+ end
284
+
285
+ class Netrc::Error < ::StandardError
286
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gist
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.5.0
4
+ version: 4.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Conrad Irwin
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-11-19 00:00:00.000000000 Z
12
+ date: 2017-05-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -96,6 +96,7 @@ files:
96
96
  - spec/shorten_spec.rb
97
97
  - spec/spec_helper.rb
98
98
  - vendor/json.rb
99
+ - vendor/netrc.rb
99
100
  homepage: https://github.com/defunkt/gist
100
101
  licenses:
101
102
  - MIT
@@ -116,9 +117,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
117
  version: '0'
117
118
  requirements: []
118
119
  rubyforge_project:
119
- rubygems_version: 2.4.5
120
+ rubygems_version: 2.6.11
120
121
  signing_key:
121
122
  specification_version: 4
122
123
  summary: Just allows you to upload gists
123
124
  test_files: []
124
- has_rdoc: