class Gist::ClipboardError

Public Instance Methods

access_token_login!(credentials={}) click to toggle source

Logs the user into gist.

This method asks the user for a username and password, and tries to obtain and OAuth2 access token, which is then stored in ~/.gist

@raise [Gist::Error] if something went wrong @see developer.github.com/v3/oauth/

# File lib/gist.rb, line 400
def access_token_login!(credentials={})
  puts "Obtaining OAuth2 access_token from GitHub."
  loop do
    print "GitHub username: "
    username = credentials[:username] || $stdin.gets.strip
    print "GitHub password: "
    password = credentials[:password] || begin
      `stty -echo` rescue nil
      $stdin.gets.strip
    ensure
      `stty echo` rescue nil
    end
    puts ""

    request = Net::HTTP::Post.new("#{base_path}/authorizations")
    request.body = JSON.dump({
      :scopes => [:gist],
      :note => "The gist gem (#{Time.now})",
      :note_url => "https://github.com/ConradIrwin/gist"
    })
    request.content_type = 'application/json'
    request.basic_auth(username, password)

    response = http(api_url, request)

    if Net::HTTPUnauthorized === response && response['X-GitHub-OTP']
      print "2-factor auth code: "
      twofa_code = $stdin.gets.strip
      puts ""

      request['X-GitHub-OTP'] = twofa_code
      response = http(api_url, request)
    end

    if Net::HTTPCreated === response
      AuthTokenFile.write JSON.parse(response.body)['token']
      puts "Success! #{ENV[URL_ENV_NAME] || "https://github.com/"}settings/tokens"
      return
    elsif Net::HTTPUnauthorized === response
      puts "Error: #{JSON.parse(response.body)['message']}"
      next
    else
      raise "Got #{response.class} from gist: #{response.body}"
    end
  end
rescue => e
  raise e.extend Error
end
api_url() click to toggle source

Get the API URL

# File lib/gist.rb, line 620
def api_url
  ENV.key?(URL_ENV_NAME) ? URI(ENV[URL_ENV_NAME]) : GITHUB_API_URL
end
auth_token() click to toggle source

auth token for authentication

@return [String] string value of access token or ‘nil`, if not found

# File lib/gist.rb, line 71
def auth_token
  @token ||= AuthTokenFile.read rescue nil
end
base_path() click to toggle source

Get the API base path

# File lib/gist.rb, line 611
def base_path
  ENV.key?(URL_ENV_NAME) ? GHE_BASE_PATH : GITHUB_BASE_PATH
end
client_id() click to toggle source
# File lib/gist.rb, line 624
def client_id
  ENV.key?(CLIENT_ID_ENV_NAME) ? URI(ENV[CLIENT_ID_ENV_NAME]) : GITHUB_CLIENT_ID
end
clipboard_command(action) click to toggle source

Get the command to use for the clipboard action.

@param [Symbol] action either :copy or :paste @return [String] the command to run @raise [Gist::ClipboardError] if no clipboard integration could be found

# File lib/gist.rb, line 569
  def clipboard_command(action)
    command = CLIPBOARD_COMMANDS.keys.detect do |cmd|
      which cmd
    end
    raise ClipboardError, <<-EOT unless command
Could not find copy command, tried:
    #{CLIPBOARD_COMMANDS.values.join(' || ')}
    EOT
    action == :copy ? command : CLIPBOARD_COMMANDS[command]
  end
copy(content) click to toggle source

Copy a string to the clipboard.

@param [String] content @raise [Gist::Error] if no clipboard integration could be found

# File lib/gist.rb, line 523
def copy(content)
  IO.popen(clipboard_command(:copy), 'r+') { |clip| clip.print content }

  unless paste == content
    message = 'Copying to clipboard failed.'

    if ENV["TMUX"] && clipboard_command(:copy) == 'pbcopy'
      message << "\nIf you're running tmux on a mac, try http://robots.thoughtbot.com/post/19398560514/how-to-copy-and-paste-with-tmux-on-mac-os-x"
    end

    raise Error, message
  end
rescue Error => e
  raise ClipboardError, e.message + "\nAttempted to copy: #{content}"
end
default_filename() click to toggle source
# File lib/gist.rb, line 87
def default_filename
  "gistfile1.txt"
end
delete_gist(id) click to toggle source
# File lib/gist.rb, line 241
def delete_gist(id)
  id = id.split("/").last
  url = "#{base_path}/gists/#{id}"

  access_token = auth_token()
  if access_token.to_s != ''
    request = Net::HTTP::Delete.new(url)
    request["Authorization"] = "token #{access_token}"
    response = http(api_url, request)
  else
    raise Error, "Not authenticated. Use 'gist --login' to login."
  end

  if response.code == '204'
    puts "Deleted!"
  else
    raise Error, "Gist with id of #{id} does not exist."
  end
end
device_flow_login!() click to toggle source
# File lib/gist.rb, line 344
def device_flow_login!
  puts "Requesting login parameters..."
  request = Net::HTTP::Post.new("/login/device/code")
  request.body = JSON.dump({
    :scope => 'gist',
    :client_id => client_id,
  })
  request.content_type = 'application/json'
  request['accept'] = "application/json"
  response = http(login_url, request)

  if response.code != '200'
    raise Error, "HTTP #{response.code}: #{response.body}"
  end

  body = JSON.parse(response.body)

  puts "Please sign in at #{body['verification_uri']}"
  puts "  and enter code: #{body['user_code']}"
  device_code = body['device_code']
  interval = body['interval']

  loop do
    sleep(interval.to_i)
    request = Net::HTTP::Post.new("/login/oauth/access_token")
    request.body = JSON.dump({
      :client_id => client_id,
      :grant_type => 'urn:ietf:params:oauth:grant-type:device_code',
      :device_code => device_code
    })
    request.content_type = 'application/json'
    request['Accept'] = 'application/json'
    response = http(login_url, request)
    if response.code != '200'
      raise Error, "HTTP #{response.code}: #{response.body}"
    end
    body = JSON.parse(response.body)
    break unless body['error'] == 'authorization_pending'
  end

  if body['error']
    raise Error, body['error_description']
  end

  AuthTokenFile.write JSON.parse(response.body)['access_token']

  puts "Success! #{ENV[URL_ENV_NAME] || "https://github.com/"}settings/connections/applications/#{client_id}"
end
get_gist_pages(url, access_token = "") click to toggle source
# File lib/gist.rb, line 261
def get_gist_pages(url, access_token = "")

  request = Net::HTTP::Get.new(url)
  request['Authorization'] = "token #{access_token}" if access_token.to_s != ''
  response = http(api_url, request)
  pretty_gist(response)

  link_header = response.header['link']

  if link_header
    links = Hash[ link_header.gsub(/(<|>|")/, "").split(',').map { |link| link.split('; rel=') } ].invert
    get_gist_pages(links['next'], access_token) if links['next']
  end

end
gist(content, options = {}) click to toggle source

Upload a gist to gist.github.com

@param [String] content the code you’d like to gist @param [Hash] options more detailed options, see

the documentation for {multi_gist}

@see developer.github.com/v3/gists/

# File lib/gist.rb, line 82
def gist(content, options = {})
  filename = options[:filename] || default_filename
  multi_gist({filename => content}, options)
end
http(url, request) click to toggle source

Run an HTTP operation

@param [URI::HTTP] The URI to which to connect @param [Net::HTTPRequest] The request to make @return [Net::HTTPResponse]

# File lib/gist.rb, line 479
def http(url, request)
  request['User-Agent'] = USER_AGENT

  http_connection(url).start do |http|
    http.request request
  end
rescue Timeout::Error
  raise "Could not connect to #{api_url}"
end
http_connection(uri) click to toggle source

Return HTTP connection

@param [URI::HTTP] The URI to which to connect @return [Net::HTTP]

# File lib/gist.rb, line 453
def http_connection(uri)
  env = ENV['http_proxy'] || ENV['HTTP_PROXY']
  connection = if env
                 proxy = URI(env)
                 if proxy.user
                   Net::HTTP::Proxy(proxy.host, proxy.port, proxy.user, proxy.password).new(uri.host, uri.port)
                 else
                   Net::HTTP::Proxy(proxy.host, proxy.port).new(uri.host, uri.port)
                 end
               else
                 Net::HTTP.new(uri.host, uri.port)
               end
  if uri.scheme == "https"
    connection.use_ssl = true
    connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
  end
  connection.open_timeout = 10
  connection.read_timeout = 10
  connection
end
legacy_private_gister?() click to toggle source
# File lib/gist.rb, line 628
def legacy_private_gister?
  return unless which('git')
  `git config --global gist.private` =~ /\Ayes|1|true|on\z/i
end
list_all_gists(user = "") click to toggle source
# File lib/gist.rb, line 203
def list_all_gists(user = "")
  url = "#{base_path}"

  if user == ""
    url << "/gists?per_page=100"
  else
    url << "/users/#{user}/gists?per_page=100"
  end

  get_gist_pages(url, auth_token())
end
list_gists(user = "") click to toggle source

List all gists(private also) for authenticated user otherwise list public gists for given username (optional argument)

@param [String] user @deprecated

see developer.github.com/v3/gists/#list-gists

# File lib/gist.rb, line 175
def list_gists(user = "")
  url = "#{base_path}"

  if user == ""
    access_token = auth_token()
    if access_token.to_s != ''
      url << "/gists"

      request = Net::HTTP::Get.new(url)
      request['Authorization'] = "token #{access_token}"
      response = http(api_url, request)

      pretty_gist(response)

    else
      raise Error, "Not authenticated. Use 'gist --login' to login or 'gist -l username' to view public gists."
    end

  else
    url << "/users/#{user}/gists"

    request = Net::HTTP::Get.new(url)
    response = http(api_url, request)

    pretty_gist(response)
  end
end
login!(credentials={}) click to toggle source

Log the user into gist.

# File lib/gist.rb, line 336
def login!(credentials={})
  if (login_url == GITHUB_URL || ENV.key?(CLIENT_ID_ENV_NAME)) && credentials.empty? && !ENV.key?('GIST_USE_USERNAME_AND_PASSWORD')
    device_flow_login!
  else
    access_token_login!(credentials)
  end
end
login_url() click to toggle source
# File lib/gist.rb, line 615
def login_url
  ENV.key?(URL_ENV_NAME) ? URI(ENV[URL_ENV_NAME]) : GITHUB_URL
end
multi_gist(files, options={}) click to toggle source

Upload a gist to gist.github.com

@param [Hash] files the code you’d like to gist: filename => content @param [Hash] options more detailed options

@option options [String] :description the description @option options [Boolean] :public (false) is this gist public @option options [Boolean] :anonymous (false) is this gist anonymous @option options [String] :access_token (‘File.read(“~/.gist”)`) The OAuth2 access token. @option options [String] :update the URL or id of a gist to update @option options [Boolean] :copy (false) Copy resulting URL to clipboard, if successful. @option options [Boolean] :open (false) Open the resulting URL in a browser. @option options [Boolean] :skip_empty (false) Skip gisting empty files. @option options [Symbol] :output (:all) The type of return value you’d like:

:html_url gives a String containing the url to the gist in a browser
:short_url gives a String contianing a  git.io url that redirects to html_url
:javascript gives a String containing a script tag suitable for embedding the gist
:all gives a Hash containing the parsed json response from the server

@return [String, Hash] the return value as configured by options @raise [Gist::Error] if something went wrong

@see developer.github.com/v3/gists/

# File lib/gist.rb, line 114
def multi_gist(files, options={})
  if options[:anonymous]
    raise 'Anonymous gists are no longer supported. Please log in with `gist --login`. ' \
      '(GitHub now requires credentials to gist https://bit.ly/2GBBxKw)'
  else
    access_token = (options[:access_token] || auth_token())
  end

  json = {}

  json[:description] = options[:description] if options[:description]
  json[:public] = !!options[:public]
  json[:files] = {}

  files.each_pair do |(name, content)|
    if content.to_s.strip == ""
      raise "Cannot gist empty files" unless options[:skip_empty]
    else
      name = name == "-" ? default_filename : File.basename(name)
      json[:files][name] = {:content => content}
    end
  end

  return if json[:files].empty? && options[:skip_empty]

  existing_gist = options[:update].to_s.split("/").last

  url = "#{base_path}/gists"
  url << "/" << CGI.escape(existing_gist) if existing_gist.to_s != ''

  request = Net::HTTP::Post.new(url)
  request['Authorization'] = "token #{access_token}" if access_token.to_s != ''
  request.body = JSON.dump(json)
  request.content_type = 'application/json'

  retried = false

  begin
    response = http(api_url, request)
    if Net::HTTPSuccess === response
      on_success(response.body, options)
    else
      raise "Got #{response.class} from gist: #{response.body}"
    end
  rescue => e
    raise if retried
    retried = true
    retry
  end

rescue => e
  raise e.extend Error
end
on_success(body, options={}) click to toggle source

Called after an HTTP response to gist to perform post-processing.

@param [String] body the text body from the github api @param [Hash] options more detailed options, see

the documentation for {multi_gist}
# File lib/gist.rb, line 494
def on_success(body, options={})
  json = JSON.parse(body)

  output = case options[:output]
           when :javascript
             %Q{<script src="#{json['html_url']}.js"></script>}
           when :html_url
             json['html_url']
           when :raw_url
             rawify(json['html_url'])
           when :short_url
             shorten(json['html_url'])
           when :short_raw_url
             shorten(rawify(json['html_url']))
           else
             json
           end

  Gist.copy(output.to_s) if options[:copy]
  Gist.open(json['html_url']) if options[:open]

  output
end
open(url) click to toggle source

Open a URL in a browser.

@param [String] url @raise [RuntimeError] if no browser integration could be found

This method was heavily inspired by defunkt’s Gist#open, @see github.com/defunkt/gist/blob/bca9b29/lib/gist.rb#L157

# File lib/gist.rb, line 587
def open(url)
  command = if ENV['BROWSER']
              ENV['BROWSER']
            elsif RUBY_PLATFORM =~ /darwin/
              'open'
            elsif RUBY_PLATFORM =~ /linux/
              %w(
                sensible-browser
                xdg-open
                firefox
                firefox-bin
              ).detect do |cmd|
                which cmd
              end
            elsif ENV['OS'] == 'Windows_NT' || RUBY_PLATFORM =~ /djgpp|(cyg|ms|bcc)win|mingw|wince/i
              'start ""'
            else
              raise "Could not work out how to use a browser."
            end

  `#{command} #{url}`
end
paste() click to toggle source

Get a string from the clipboard.

@param [String] content @raise [Gist::Error] if no clipboard integration could be found

# File lib/gist.rb, line 543
def paste
  `#{clipboard_command(:paste)}`
end
pretty_gist(response) click to toggle source

return prettified string result of response body for all gists

@params [Net::HTTPResponse] response @return [String] prettified result of listing all gists

see developer.github.com/v3/gists/#response

# File lib/gist.rb, line 283
def pretty_gist(response)
  body = JSON.parse(response.body)
  if response.code == '200'
    body.each do |gist|
      description = "#{gist['description'] || gist['files'].keys.join(" ")} #{gist['public'] ? '' : '(secret)'}"
      puts "#{gist['html_url']} #{description.tr("\n", " ")}\n"
      $stdout.flush
    end

  else
    raise Error, body['message']
  end
end
rawify(url) click to toggle source

Convert github url into raw file url

Unfortunately the url returns from github’s api is legacy, we have to taking a HTTPRedirection before appending it with ‘/raw’. Let’s looking forward for github’s api fix :)

@param [String] url @return [String] the raw file url

# File lib/gist.rb, line 323
def rawify(url)
  uri = URI(url)
  request = Net::HTTP::Get.new(uri.path)
  response = http(uri, request)
  if Net::HTTPSuccess === response
    url + '/raw'
  elsif Net::HTTPRedirection === response
    rawify(response.header['location'])
  end
end
read_gist(id, file_name=nil) click to toggle source
# File lib/gist.rb, line 215
def read_gist(id, file_name=nil)
  url = "#{base_path}/gists/#{id}"

  access_token = auth_token()

  request = Net::HTTP::Get.new(url)
  request['Authorization'] = "token #{access_token}" if access_token.to_s != ''
  response = http(api_url, request)

  if response.code == '200'
    body = JSON.parse(response.body)
    files = body["files"]

    if file_name
      file = files[file_name]
      raise Error, "Gist with id of #{id} and file #{file_name} does not exist." unless file
    else
      file = files.values.first
    end

    puts file["content"]
  else
    raise Error, "Gist with id of #{id} does not exist."
  end
end
shorten(url) click to toggle source

Convert long github urls into short git.io ones

@param [String] url @return [String] shortened url, or long url if shortening fails

# File lib/gist.rb, line 301
def shorten(url)
  request = Net::HTTP::Post.new("/create")
  request.set_form_data(:url => url)
  response = http(GIT_IO_URL, request)
  case response.code
  when "200"
    URI.join(GIT_IO_URL, response.body).to_s
  when "201"
    response['Location']
  else
    url
  end
end
should_be_public?(options={}) click to toggle source
# File lib/gist.rb, line 633
def should_be_public?(options={})
  if options.key? :private
    !options[:private]
  else
    !Gist.legacy_private_gister?
  end
end
which(cmd, path=ENV['PATH']) click to toggle source

Find command from PATH environment.

@param [String] cmd command name to find @param [String] options PATH environment variable @return [String] the command found

# File lib/gist.rb, line 552
def which(cmd, path=ENV['PATH'])
  if RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|bccwin|cygwin/
    path.split(File::PATH_SEPARATOR).each {|dir|
      f = File.join(dir, cmd+".exe")
      return f if File.executable?(f) && !File.directory?(f)
    }
    nil
  else
    return system("which #{cmd} > /dev/null 2>&1")
  end
end