├── Gemfile ├── test ├── test_helper.rb └── authorize_test.rb ├── lib ├── github_downloads.rb └── github_downloads │ ├── version.rb │ └── uploader.rb ├── .gitignore ├── Rakefile ├── github_downloads.gemspec ├── Gemfile.lock ├── LICENSE └── README.md /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | 3 | -------------------------------------------------------------------------------- /lib/github_downloads.rb: -------------------------------------------------------------------------------- 1 | require 'github_downloads/uploader' -------------------------------------------------------------------------------- /lib/github_downloads/version.rb: -------------------------------------------------------------------------------- 1 | module GithubDownloads 2 | VERSION = "0.1.3" 3 | end 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # .github-upload-token stores OAuth token, used by github_downloads gem 3 | .github-upload-token 4 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rake/testtask' 3 | 4 | Rake::TestTask.new do |t| 5 | t.libs << 'lib' 6 | t.pattern = 'test/**/*_test.rb' 7 | t.verbose = false 8 | end 9 | 10 | task :default => :test 11 | -------------------------------------------------------------------------------- /test/authorize_test.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'github_downloads' 3 | 4 | class GithubDownloadsTest < Test::Unit::TestCase 5 | def teardown 6 | ENV.clear 7 | end 8 | 9 | def test_lookup_env 10 | ENV['GH_LOGIN'] = "my-login" 11 | ENV['GH_USERNAME'] = "my-username" 12 | ENV['GH_REPOSITORY'] = "my-repo" 13 | ENV['GH_OAUTH_TOKEN'] = "12345" 14 | uploader = GithubDownloads::Uploader.new 15 | 16 | assert_equal "my-login", uploader.login 17 | assert_equal "my-username", uploader.username 18 | assert_equal "my-repo", uploader.repo 19 | assert_equal "12345", uploader.token 20 | end 21 | 22 | def test_authorize 23 | uploader = GithubDownloads::Uploader.new 24 | uploader.authorize 25 | end 26 | 27 | def test_upload 28 | uploader = GithubDownloads::Uploader.new 29 | uploader.authorize 30 | uploader.upload_file("LICENSE", "license of this repo", "LICENSE") 31 | end 32 | end -------------------------------------------------------------------------------- /github_downloads.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | $:.unshift File.expand_path('../lib', __FILE__) 4 | require 'github_downloads/version' 5 | 6 | Gem::Specification.new do |s| 7 | s.name = "github_downloads" 8 | s.version = GithubDownloads::VERSION 9 | s.authors = ["Clemens Mueller", "Peter Wagenet"] 10 | s.email = ["cmueller.418@gmail.com", "peter.wagenet@gmail.com"] 11 | s.homepage = "https://github.com/pangratz/github_downloads" 12 | s.summary = "Upload files to GitHub Downloads" 13 | s.description = "Library to upload files to GitHub Downloads section of a specific repository" 14 | 15 | s.files = `git ls-files app lib`.split("\n") 16 | s.platform = Gem::Platform::RUBY 17 | s.require_paths = ['lib'] 18 | 19 | s.add_runtime_dependency "rest-client", "~> 1.6" 20 | s.add_runtime_dependency "github_api", "~> 0.6" 21 | 22 | s.add_development_dependency "rake" 23 | end 24 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | github_downloads (0.1.0) 5 | github_api (~> 0.6) 6 | rest-client (~> 1.6) 7 | 8 | GEM 9 | remote: http://rubygems.org/ 10 | specs: 11 | faraday (0.8.1) 12 | multipart-post (~> 1.1) 13 | github_api (0.6.2) 14 | faraday (~> 0.8.1) 15 | hashie (~> 1.2.0) 16 | multi_json (~> 1.3) 17 | nokogiri (~> 1.5.2) 18 | oauth2 19 | hashie (1.2.0) 20 | httpauth (0.1) 21 | json (1.7.3) 22 | jwt (0.1.4) 23 | json (>= 1.2.4) 24 | mime-types (1.19) 25 | multi_json (1.3.6) 26 | multipart-post (1.1.5) 27 | nokogiri (1.5.5) 28 | oauth2 (0.8.0) 29 | faraday (~> 0.8) 30 | httpauth (~> 0.1) 31 | jwt (~> 0.1.4) 32 | multi_json (~> 1.0) 33 | rack (~> 1.2) 34 | rack (1.4.1) 35 | rake (0.9.2.2) 36 | rest-client (1.6.7) 37 | mime-types (>= 1.16) 38 | 39 | PLATFORMS 40 | ruby 41 | 42 | DEPENDENCIES 43 | github_downloads! 44 | rake 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | 3 | Copyright (c) Clemens Müller (@pangratz) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # github_downloads 2 | 3 | Simple library to upload files to the Downloads section of a GitHub repository. It authenticates via OAuth token to GitHub. 4 | 5 | ## Installation 6 | 7 | Install the gem by 8 | 9 | gem install github_downloads 10 | 11 | or put it in your Gemfile and run `bundle install` 12 | 13 | gem "github_downloads" 14 | 15 | ## Configuration 16 | 17 | The library needs the following settings so it can upload files to a specific GitHub repository: 18 | 19 | - **login** : your GitHub username 20 | - **username** : the username of the GitHub repository, to which the file shall be uploaded. May be the same as login but if you upload a file to a repository of an organization, it's the organizations' name 21 | - **repo** : the name of the repository 22 | - **token** : the OAuth token needed to authenticate to GitHub 23 | 24 | There are several ways to provide this information to the library. 25 | 26 | You can set the login, username, repo and oauth token when you instantiate the library: 27 | 28 | ```ruby 29 | uploader = GithubDownloads::Uploader.new(login, username, repo, token) 30 | ``` 31 | 32 | You can also let the library use the login, username and repository of the .git repository of the project in which you are using this library. This works basically by getting the values from git config. So the `login` is retrieved via `git config github.user` and the `username` and `repo` are extracted from `git config remote.origin.url`: 33 | 34 | ```ruby 35 | uploader = GithubDownloads::Uploader.new 36 | ``` 37 | 38 | You can also define the login, username, repository and token via `ENV`: 39 | 40 | ```ruby 41 | ENV['GH_LOGIN'] = "pangratz" 42 | ENV['GH_USERNAME'] = "pangratz" 43 | ENV['GH_REPOSITORY'] = "github_downloads" 44 | ENV['GH_OAUTH_TOKEN'] = "12345" 45 | 46 | uploader = GithubDownloads::Uploader.new 47 | ``` 48 | 49 | If you don't have an OAuth token yet, you can use the `authorize` method of the `Uploader`: 50 | 51 | ```ruby 52 | uploader = GithubDownloads::Uploader.new 53 | uploader.authorize 54 | ``` 55 | 56 | The `authorize` checks if there is a file named `.github-upload-token`. If so, it uses the OAuth token which is stored in this file. If there is no such file, the library will ask you to enter the GitHub password. Then GitHub is contacted via its API and an OAuth token is retrieved which is then stored in the `.github-upload-token` for further usage. 57 | 58 | If you want to revoke the access of this library, simply revoke the access to the created application with the name `GitHub Downloads Gem (API)` on https://github.com/settings/applications 59 | 60 | If you don't trust this authorize method, you can create one yourself as described in http://developer.github.com/v3/oauth/#create-a-new-authorization 61 | 62 | ## Uploading a file 63 | 64 | After initialization, you can finally upload a file to the specified repository. This is achieved by invoking the `upload_file` method. This method takes 3 arguments: 65 | 66 | - **filename** : name of the file in the Downloads section of GitHub repository 67 | - **description** : description of the file 68 | - **file** : path to the file, which shall be uploaded 69 | 70 | If there is already a file with specified `filename`, it is replaced. 71 | 72 | ```ruby 73 | uploader = GithubDownloads::Uploader.new 74 | uploader.authorize 75 | 76 | uploader.upload_file("my-library.latest.js", "Latest build", "build/latest.js") 77 | ``` 78 | 79 | ## Sample usage inside Rakefile 80 | 81 | One use case for this library is to provide a task in your `Rakefile` which uploads the latest build of your library to the GitHub repository. 82 | 83 | ```ruby 84 | require 'github_downloads' 85 | 86 | desc "builds project and stores file in target/build.js" 87 | task :dist do 88 | ... 89 | end 90 | 91 | desc "upload latest build to GitHub repository" 92 | task :upload_latest => :dist do 93 | uploader = GithubDownloads::Uploader.new 94 | uploader.authorize 95 | uploader.upload_file("project-latest.js", "Latest build of project", "target/build.js") 96 | end 97 | ``` 98 | 99 | # Origin 100 | 101 | This library is extracted from [https://github.com/emberjs/ember.js/lib/github_uploader.rb](https://github.com/emberjs/ember.js/blob/7598f93ac9d75368d1bb838af511b81a86aa7f61/lib/github_uploader.rb) 102 | 103 | # Release 104 | 105 | To release a new version do 106 | 107 | gem install gem-release 108 | gem bump 109 | gem release --tag -------------------------------------------------------------------------------- /lib/github_downloads/uploader.rb: -------------------------------------------------------------------------------- 1 | require "rest-client" 2 | require "github_api" 3 | require "json" 4 | 5 | module GithubDownloads 6 | class Uploader 7 | 8 | attr_reader :login, :username, :repo, :token 9 | 10 | def initialize(login=nil, username=nil, repo=nil, token=nil, root=Dir.pwd) 11 | @login = init_login(login) 12 | @username = init_username(username) 13 | @repo = init_repo(repo) 14 | @root = root 15 | @token = token || ENV['GH_OAUTH_TOKEN'] || check_token 16 | end 17 | 18 | def init_login(login=nil) 19 | login || ENV['GH_LOGIN'] || `git config github.user`.chomp 20 | end 21 | 22 | def init_username(username=nil) 23 | username || ENV['GH_USERNAME'] || repo_url[2] 24 | end 25 | 26 | def init_repo(repo=nil) 27 | repo || ENV['GH_REPOSITORY'] || repo_url[3] 28 | end 29 | 30 | def repo_url 31 | origin = `git config remote.origin.url`.chomp 32 | origin.match(/github\.com[\/:]((.+?)\/(.+?))(\.git)?$/) || [] 33 | end 34 | 35 | def authorized? 36 | !!@token 37 | end 38 | 39 | def token_path 40 | File.expand_path(".github-upload-token", @root) 41 | end 42 | 43 | def check_token 44 | File.exist?(token_path) ? File.open(token_path, "rb").read : nil 45 | end 46 | 47 | def authorize 48 | return if authorized? 49 | 50 | require 'cgi' 51 | 52 | puts "There is no file named .github-upload-token in this folder. This file holds the OAuth token needed to communicate with GitHub." 53 | puts "You will be asked to enter your GitHub password so a new OAuth token will be created." 54 | print "GitHub Password for #{@login}: " 55 | system "stty -echo" # disable echoing of entered chars so password is not shown on console 56 | pw = STDIN.gets.chomp 57 | system "stty echo" # enable echoing of entered chars 58 | puts "" 59 | 60 | # check if the user already granted access for Ember.js Uploader by checking the available authorizations 61 | response = RestClient.get "https://#{CGI.escape(@login)}:#{CGI.escape(pw)}@api.github.com/authorizations" 62 | JSON.parse(response.to_str).each do |auth| 63 | if auth["note"] == "GitHub Downloads Gem" 64 | # user already granted access, so we reuse the existing token 65 | @token = auth["token"] 66 | end 67 | end 68 | 69 | ## we need to create a new token 70 | unless @token 71 | payload = { 72 | :scopes => ["public_repo"], 73 | :note => "GitHub Downloads Gem", 74 | :note_url => "https://github.com/pangratz/github_downloads" 75 | } 76 | response = RestClient.post "https://#{CGI.escape(@login)}:#{CGI.escape(pw)}@api.github.com/authorizations", payload.to_json, :content_type => :json 77 | @token = JSON.parse(response.to_str)["token"] 78 | end 79 | 80 | # finally save the token into .github-upload-token 81 | File.open(token_path, 'w') {|f| f.write(@token)} 82 | 83 | # add entry to .gitignore if not already exists 84 | gitignore = File.expand_path(".gitignore", @root) 85 | system "touch #{gitignore}" 86 | includes = File.open(gitignore).lines.any? { |line| line.chomp == '.github-upload-token' } 87 | if !includes 88 | File.open(gitignore, "a") do |f| 89 | f.puts("\n# .github-upload-token stores OAuth token, used by github_downloads gem") 90 | f.puts(".github-upload-token") 91 | end 92 | end 93 | end 94 | 95 | def remove_file(filename) 96 | return false unless authorized? 97 | 98 | gh = Github.new :user => @username, :repo => @repo, :oauth_token => @token 99 | 100 | # remvove previous download with the same name 101 | gh.repos.downloads.list @username, @repo do |download| 102 | if filename == download.name 103 | gh.repos.downloads.delete @username, @repo, download.id 104 | break 105 | end 106 | end 107 | end 108 | 109 | def upload_file(filename, description, file) 110 | return false unless authorized? 111 | 112 | remove_file(filename) 113 | 114 | gh = Github.new :user => @username, :repo => @repo, :oauth_token => @token 115 | 116 | # step 1 117 | hash = gh.repos.downloads.create @username, @repo, 118 | "name" => filename, 119 | "size" => File.size(file), 120 | "description" => description 121 | 122 | # step 2 123 | gh.repos.downloads.upload hash, file 124 | 125 | return true 126 | end 127 | 128 | end 129 | end --------------------------------------------------------------------------------