gistribute 0.2 → 0.3

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
  SHA256:
3
- metadata.gz: 943e1cbafa8fcd6769fdf9e11c4770a7589335247b4232fa4f37c7720cfb3769
4
- data.tar.gz: 1d4fe35b75840e4d8f9a2510616790fdafe9e7b216422514736f9d82943f7dc4
3
+ metadata.gz: 54d0c1a3f95efadf4af148ba00f9c52f66bc80b4f979d357a7430c5c412e96ab
4
+ data.tar.gz: 49fafdc9abd4ba1d994f796cf047c925159ee3d9bf9128386a8e3341fff04194
5
5
  SHA512:
6
- metadata.gz: 84563d81f9d47098a8a6a48b64c20a606f0831a34cf2d9fc3b150c0dd6e25cc9728103e0e3e7648a5e154f81c1ddc7ac2a7670d8d753689693f9d6f4b966dc52
7
- data.tar.gz: ad01c45e1e7adcd4650ac23fcf0ed6c3f72112837639874350a3a22e1bc655e1369e26341d11b5d9a388d89e2e5f525697474235a7f503e16d08f63879be70ca
6
+ metadata.gz: 4d01816a4228bf6041d1b6753b77655ce613166e4bdc8772a9d2e8482d1031fde7b3607c9664ccbd855b85d4e84ed82805dec1401f9d7a9c99af75fdd7991e4a
7
+ data.tar.gz: 83041e56c9d92034509149eb5a1968cdfea92dba1b15d3d66751b3597d2c602940e078a9eed2af3fd92f191cc9a05d9ad41687be5538180c9180c2bf53bef08e
@@ -0,0 +1,14 @@
1
+ name: Blocking Issues
2
+
3
+ on:
4
+ issues:
5
+ types: [opened, edited, deleted, transferred, closed, reopened]
6
+ pull_request_target:
7
+ types: [opened, edited, closed, reopened]
8
+
9
+ jobs:
10
+ blocking_issues:
11
+ runs-on: ubuntu-latest
12
+ name: Check for blocking issues
13
+ steps:
14
+ - uses: Levi-Lesches/blocking-issues@v2
@@ -0,0 +1,17 @@
1
+ on: [push, pull_request]
2
+
3
+ name: Lint
4
+
5
+ jobs:
6
+ lint:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - name: Checkout repository
10
+ uses: actions/checkout@v3
11
+ - name: Set up Ruby
12
+ uses: ruby/setup-ruby@v1
13
+ with:
14
+ ruby-version: 3.2
15
+ bundler-cache: true
16
+ - name: Run RuboCop
17
+ run: bundle exec rubocop
@@ -0,0 +1,21 @@
1
+ on: [push, pull_request]
2
+
3
+ name: Test
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ubuntu-latest
8
+ env:
9
+ GISTRIBUTE_TEST_OAUTH_TOKEN: ${{ secrets.GISTRIBUTE_TEST_OAUTH_TOKEN }}
10
+ steps:
11
+ - name: Checkout repository
12
+ uses: actions/checkout@v3
13
+ - name: Set up Ruby
14
+ uses: ruby/setup-ruby@v1
15
+ with:
16
+ ruby-version: 3.2
17
+ bundler-cache: true
18
+ - name: Check for focused tests
19
+ run: bundle exec rubocop --only RSpec/Focus
20
+ - name: Run tests
21
+ run: bundle exec rspec --format doc
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
19
+
20
+ # Don't push Gemfile.lock on a RubyGem
21
+ Gemfile.lock
data/.rubocop.yml ADDED
@@ -0,0 +1,94 @@
1
+ require:
2
+ - rubocop-rspec
3
+
4
+ AllCops:
5
+ NewCops: enable
6
+ TargetRubyVersion: 3.2
7
+
8
+ Gemspec/DevelopmentDependencies:
9
+ EnforcedStyle: gemspec
10
+
11
+ Gemspec/RequiredRubyVersion:
12
+ Enabled: false
13
+
14
+ Layout/ArgumentAlignment:
15
+ Enabled: false
16
+
17
+ Layout/EndAlignment:
18
+ EnforcedStyleAlignWith: variable
19
+
20
+ Layout/ExtraSpacing:
21
+ Enabled: false
22
+
23
+ Layout/FirstHashElementIndentation:
24
+ EnforcedStyle: consistent
25
+
26
+ Layout/MultilineMethodCallIndentation:
27
+ EnforcedStyle: indented
28
+
29
+ Layout/SpaceAroundEqualsInParameterDefault:
30
+ Enabled: false
31
+
32
+ Layout/TrailingWhitespace:
33
+ AllowInHeredoc: false
34
+
35
+ Lint/NonDeterministicRequireOrder:
36
+ Enabled: false
37
+
38
+ Metrics:
39
+ Enabled: false
40
+
41
+ Naming/HeredocDelimiterNaming:
42
+ Enabled: false
43
+
44
+ Naming/MethodParameterName:
45
+ Enabled: false
46
+
47
+ Naming/VariableNumber:
48
+ Enabled: false
49
+
50
+ Security/Eval:
51
+ Exclude:
52
+ - lib/cli.rb
53
+
54
+ Style/ClassAndModuleChildren:
55
+ Enabled: false
56
+
57
+ Style/Documentation:
58
+ Enabled: false
59
+
60
+ Style/FormatString:
61
+ Enabled: false
62
+
63
+ Style/GuardClause:
64
+ Enabled: false
65
+
66
+ Style/IfUnlessModifier:
67
+ Enabled: false
68
+
69
+ Style/MultilineTernaryOperator:
70
+ Enabled: false
71
+
72
+ Style/NestedParenthesizedCalls:
73
+ Enabled: false
74
+
75
+ Style/NestedTernaryOperator:
76
+ Enabled: false
77
+
78
+ Style/Next:
79
+ Enabled: false
80
+
81
+ Style/NumericPredicate:
82
+ Enabled: false
83
+
84
+ Style/ParallelAssignment:
85
+ Enabled: false
86
+
87
+ Style/RequireOrder:
88
+ Enabled: true
89
+
90
+ Style/StderrPuts:
91
+ Enabled: false
92
+
93
+ Style/StringLiterals:
94
+ EnforcedStyle: double_quotes
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+ gemspec
data/README.md CHANGED
@@ -1,13 +1,14 @@
1
- # Gistribute
1
+ # gistribute
2
2
 
3
- Gistribute is a simple file distribution system based on GitHub's Gist service.
3
+ gistribute is a simple file distribution system based on [GitHub
4
+ Gists](https://gist.github.com/).
4
5
 
5
- I decided to make Gistribute when I found myself sharing configuration files
6
+ I decided to make gistribute when I found myself sharing configuration files
6
7
  and such with others by placing all of them into a Gist along with a little
7
- Bash script that curled all of the files, via their raw URLs, to the right
8
- location on the other's computer. This program removed the need to make that
9
- script, and the need to update the raw URLs in that script whenever you make
10
- a tweak to one of the files that you're sharing.
8
+ Bash script that `curl`ed all of the files, via their raw URLs, to the right
9
+ location on the other's computer. This program removes the need to make that
10
+ script, and the need to update the raw URLs in that script whenever you edit
11
+ a file in the Gist.
11
12
 
12
13
  ## Installation
13
14
 
@@ -17,30 +18,38 @@ gem install gistribute
17
18
 
18
19
  ## How It Works
19
20
 
20
- Gistribute looks at the filename that you enter on Gist to find all of the
21
+ gistribute looks at the filename that you enter on Gist to find all of the
21
22
  data that it needs to download the file to the other user's computer. It
22
- allows you to choose what Gistribute will call the file when it is installing
23
+ allows you to choose what gistribute will call the file when it is installing
23
24
  it, and where it will install the file.
24
25
 
25
- ## Usage
26
-
27
- If, for example, you were sharing a .vimrc file, and you wanted Gistribute to
26
+ If, for example, you were sharing a `~/.vimrc` file, and you wanted gistribute to
28
27
  call it "Vim configuration" when it was installing it, you would name the file
29
- `Vim configuration||~|.vimrc`. If the filename contains the sequence `||`,
28
+ `Vim configuration || ~|.vimrc`. If the filename contains the sequence `||`,
30
29
  anything before that will be considered the description of that file, and
31
- anything after it the installation path. If there is no `||`, Gistribute will
30
+ anything after it the installation path. If there is no `||`, gistribute will
32
31
  use the name of the file (without the path to it) as the default description.
33
32
 
34
- The file path separator is the pipe character because GitHub Gist doesn't
35
- allow slashes in filenames- this quirk is the result of Gist actually saving
36
- those files to a Git repository on their servers, and handling slashes in
37
- file names can't possibly be very nice.
33
+ ## Usage
34
+
35
+ ### Uploads
36
+
37
+ To upload a gistribution, use the `upload` subcommand and pass in as many files
38
+ or directories as you would like. Directories will be recursively processed and
39
+ uploaded. The install paths will be the same ones that were used on your
40
+ system. The home directory will be handled automatically.
41
+
42
+ ```sh
43
+ gistribute upload ~/.bashrc ~/.bash_profile
44
+ ```
45
+
46
+ ### Downloads
38
47
 
39
- If the resulting Gist link was, for example, https://gist.github.com/123456,
40
- the user would be able to run either of these commands to download the file
41
- to `~/.vimrc`:
48
+ If the resulting Gist link was, for example, `https://gist.github.com/user/123456`,
49
+ the receiver would be able to run either of these commands to download the files
50
+ to `~/.bashrc` and `~/.bash_profile`:
42
51
 
43
52
  ```sh
44
- gistribute 123456
45
- gistribute https://gist.github.com/123456
53
+ gistribute install 123456
54
+ gistribute install https://gist.github.com/user/123456
46
55
  ```
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler"
2
4
  Bundler::GemHelper.install_tasks
3
5
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2
1
+ 0.3
data/bin/gistribute CHANGED
@@ -1,86 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "colorize"
4
- require "fileutils"
5
- require "json"
6
- require "open-uri"
4
+ require "gistribute"
7
5
 
8
- # Print to STDOUT to clear line
9
- CLEAR = "\r\e[K"
10
-
11
- if %w[-v --version].include? ARGV.first
12
- puts "Gistribute #{File.read(File.expand_path("../../VERSION", __FILE__)).strip}"
13
- exit 0
14
- end
15
-
16
- print "Downloading data..."
17
-
18
- # The user can pass in either just the ID or the entire URL to the Gist.
19
- id = ARGV.first[/(^|\/)([[:xdigit:]]+)/, 2]
20
-
21
- begin
22
- gist = JSON.parse(URI.open("https://api.github.com/gists/#{id}").read)
23
- rescue OpenURI::HTTPError => msg
24
- print <<~EOS.red
25
- #{CLEAR}There was an error downloading the requested Gist.
26
- The error is as follows:
27
- EOS
28
- puts msg
29
-
30
- puts "The ID that was queried is:".red
31
- puts id
32
-
33
- exit -1
34
- end
35
-
36
- # The JSON data contains a lot of information. The information that is
37
- # relevant to this program is as follows:
38
- #
39
- # {
40
- # "html_url" => "Link to the Gist",
41
- # "description" => "The description for the Gist",
42
- #
43
- # "owner" => nil, # IF ANONYMOUS
44
- # "owner" => { # IF TIED TO A USER
45
- # "login" => "username"
46
- # },
47
- #
48
- # "files" => {
49
- # "filename of first file" => {
50
- # "content" => "entire contents of the file"
51
- # }
52
- # # Repeat the above for every file in the Gist.
53
- # }
54
- # }
55
-
56
- # Regular expression word wrap to keep lines less than 80 characters. Then
57
- # check to see if it's empty- if not, put newlines on each side so that it
58
- # will be padded when displayed in the output.
59
- desc = gist["description"].gsub(/(.{1,79})(\s+|\Z)/, "\\1\n").strip
60
- desc = "\n#{desc}\n" unless desc.empty?
61
-
62
- puts <<~EOS
63
- #{CLEAR}Finished downloading Gist from: #{gist["html_url"]}
64
- Gist uploaded by #{
65
- gist["owner"] ? "user #{gist["owner"]["login"]}" : "an anonymous user"
66
- }.
67
- #{desc}
68
- Beginning install...
69
- EOS
70
-
71
- gist["files"].each do |filename, data|
72
- metadata = filename.split("||").map &:strip
73
-
74
- # | as path separator in the Gist's file name, as Gist doesn't allow the
75
- # usage of /.
76
- path = metadata.last.gsub(/[~|]/, "|" => "/", "~" => Dir.home)
77
- # Default description is the name of the file.
78
- description = metadata.size == 1 ? File.basename(path) : metadata.first
79
-
80
- puts " #{"*".green} Installing #{description}..."
81
-
82
- # Handle directories that don't exist.
83
- FileUtils.mkdir_p File.dirname(path)
84
-
85
- File.write(path, data["content"])
86
- end
6
+ Gistribute::CLI.new.run
data/gistribute.gemspec CHANGED
@@ -1,10 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Gem::Specification.new do |gem|
2
4
  gem.name = "gistribute"
3
- gem.version = File.read(File.expand_path("../VERSION", __FILE__)).strip
5
+ gem.version = File.read(File.expand_path("VERSION", __dir__)).strip
4
6
 
5
7
  gem.author = "Vinny Diehl"
6
8
  gem.email = "vinny.diehl@gmail.com"
7
- gem.homepage = "https://github.com/gbchaosmaster/gistribute"
9
+ gem.homepage = "https://github.com/vinnydiehl/gistribute"
10
+ gem.metadata["rubygems_mfa_required"] = "true"
8
11
 
9
12
  gem.license = "MIT"
10
13
 
@@ -13,13 +16,17 @@ Gem::Specification.new do |gem|
13
16
 
14
17
  gem.bindir = "bin"
15
18
  gem.executables = %w[gistribute]
16
-
17
- gem.test_files = Dir["spec/**/*"]
18
- gem.files = Dir["bin/**/*"] + gem.test_files + %w[
19
- LICENSE Rakefile README.md VERSION gistribute.gemspec
20
- ]
19
+ gem.files = `git ls-files -z`.split "\x0"
21
20
 
22
21
  gem.add_dependency "colorize", "~> 1.0"
22
+ gem.add_dependency "faraday-retry", "~> 2.2"
23
+ gem.add_dependency "launchy", "~> 2.5"
24
+ gem.add_dependency "octokit", "~> 6.1"
25
+ gem.add_dependency "optimist_xl", "~> 3.3"
26
+
23
27
  gem.add_development_dependency "fuubar", "~> 2.0"
24
28
  gem.add_development_dependency "rspec", "~> 3.12"
29
+ gem.add_development_dependency "rubocop", "~> 1.54"
30
+ gem.add_development_dependency "rubocop-rspec", "~> 2.22"
31
+ gem.add_development_dependency "yard", "~> 0.9"
25
32
  end
data/lib/cli/auth.rb ADDED
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gistribute
4
+ class CLI
5
+ def authenticate
6
+ access_token = if File.exist?(CONFIG_FILE)
7
+ File.read(CONFIG_FILE).strip
8
+ else
9
+ device_res = URI.decode_www_form(
10
+ Net::HTTP.post_form(
11
+ URI("https://github.com/login/device/code"), "client_id" => CLIENT_ID, "scope" => "gist"
12
+ ).body
13
+ ).to_h
14
+
15
+ retry_interval = device_res["interval"].to_i
16
+
17
+ Launchy.open(device_res["verification_uri"])
18
+ puts <<~EOS
19
+ Opening GitHub, please enter the authentication code: #{device_res['user_code']}
20
+ If your browser did not open, visit #{device_res['verification_uri']}
21
+ EOS
22
+
23
+ uri = URI("https://github.com/login/oauth/access_token")
24
+
25
+ # Keep trying until the user enters the code or the device code expires
26
+ token = nil
27
+ loop do
28
+ sleep(retry_interval)
29
+
30
+ response = URI.decode_www_form(
31
+ Net::HTTP.post_form(
32
+ uri, "client_id" => CLIENT_ID, "device_code" => device_res["device_code"],
33
+ "grant_type" => "urn:ietf:params:oauth:grant-type:device_code"
34
+ ).body
35
+ ).to_h
36
+
37
+ if (token = response["access_token"])
38
+ File.write(CONFIG_FILE, token)
39
+ break
40
+ elsif response["error"] == "authorization_pending"
41
+ # The user has not yet entered the code; keep waiting silently
42
+ next
43
+ elsif response["error"] == "expired_token"
44
+ panic! "Token expired! Please try again."
45
+ else
46
+ panic! response["error_description"]
47
+ end
48
+ end
49
+
50
+ token
51
+ end
52
+
53
+ @client = Octokit::Client.new(access_token:)
54
+
55
+ success_message = "Logged in as #{@client.user.login}."
56
+ if @subcommand == "login"
57
+ puts success_message.green
58
+ else
59
+ puts success_message
60
+ puts
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gistribute
4
+ class CLI
5
+ def install
6
+ print "Downloading data..."
7
+
8
+ id = Gistribute.parse_id(ARGV.first)
9
+
10
+ begin
11
+ gist = @client.gist(id)
12
+ rescue Octokit::Error => e
13
+ $stderr.print <<~EOS.chop.red
14
+ \rThere was an error downloading the requested Gist.
15
+ The error is as follows:
16
+ EOS
17
+ $stderr.puts " #{e.response_status} #{JSON.parse(e.response_body)['message']}"
18
+
19
+ $stderr.print "The ID that was queried is: ".red
20
+ $stderr.puts id
21
+
22
+ exit 1
23
+ end
24
+
25
+ # Regular expression word wrap to keep lines less than 80 characters. Then
26
+ # check to see if it's empty- if not, put newlines on each side so that it
27
+ # will be padded when displayed in the output.
28
+ gist_description = gist.description.gsub(/(.{1,79})(\s+|\Z)/, "\\1\n").strip
29
+ gist_description = "\n#{gist_description}\n" unless gist_description.empty?
30
+
31
+ # Process files
32
+ files = gist.files.map do |filename, data|
33
+ metadata = filename.to_s.split("||").map(&:strip)
34
+
35
+ # | as path separator in the Gist's file name, as Gist doesn't allow the
36
+ # usage of /.
37
+ path = Gistribute.decode(metadata.last)
38
+ # Default description is the name of the file.
39
+ description = metadata.size == 1 ? File.basename(path) : metadata.first
40
+
41
+ { description:, path:, content: data[:content] }
42
+ end
43
+
44
+ puts <<~EOS
45
+ \rFinished downloading Gist from: #{gist.html_url}
46
+ Gist uploaded by #{
47
+ gist.owner ? "user #{gist.owner[:login]}" : 'an anonymous user'
48
+ }.
49
+ #{gist_description}
50
+ EOS
51
+
52
+ unless @subcommand_options.yes
53
+ puts "Files:"
54
+
55
+ files.each do |f|
56
+ print "#{f[:description]}: " unless f[:description].empty?
57
+ puts f[:path]
58
+ end
59
+ end
60
+
61
+ if @subcommand_options.yes || confirm?("\nWould you like to install these files? [Yn] ")
62
+ puts unless @subcommand_options.yes
63
+
64
+ files.each do |f|
65
+ if File.exist?(f[:path]) && !@subcommand_options.force &&
66
+ !confirm?("File already exists: #{f[:path]}\nWould you like to overwrite it? [Yn] ")
67
+ puts " #{'*'.red} #{f[:description]} skipped."
68
+ next
69
+ end
70
+
71
+ # Handle directories that don't exist.
72
+ FileUtils.mkdir_p File.dirname(f[:path])
73
+ File.write(f[:path], f[:content])
74
+
75
+ # If using `--yes`, we print the path in the this string rather than
76
+ # above with the prompt
77
+ puts " #{'*'.green} #{f[:description]} installed#{
78
+ @subcommand_options.yes ? " to: #{f[:path]}" : '.'
79
+ }"
80
+ end
81
+ else
82
+ puts "Aborting.".red
83
+ end
84
+ end
85
+ end
86
+ end
data/lib/cli/upload.rb ADDED
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gistribute
4
+ class CLI
5
+ def upload
6
+ files_json = process_files(@files)
7
+
8
+ if files_json.empty?
9
+ panic! "No files found, aborting."
10
+ end
11
+
12
+ if @subcommand_options.yes || confirm?("\nUpload these files? [Yn] ")
13
+ gist = @client.create_gist({
14
+ description: "[gistribution] #{@subcommand_options.description}".strip,
15
+ public: !@subcommand_options.private,
16
+ files: files_json
17
+ })
18
+
19
+ puts if @subcommand_options.yes
20
+ print "Gistribution uploaded to: ".green
21
+ puts gist.html_url
22
+ else
23
+ puts "Aborted.".red
24
+ end
25
+ end
26
+
27
+ def process_files(files)
28
+ files.each_with_object({}) do |file, hash|
29
+ hash.merge!(process_file file)
30
+ end
31
+ end
32
+
33
+ def process_file(file)
34
+ file = File.expand_path(file)
35
+
36
+ if File.directory?(file)
37
+ # Recursively process every file in the directory
38
+ process_files(
39
+ Dir.glob("#{file}/**/*", File::FNM_DOTMATCH).reject { |f| File.directory? f }
40
+ )
41
+ else
42
+ unless (content = File.read(file))
43
+ panic! "Files cannot be empty."
44
+ end
45
+
46
+ puts "File: #{file}"
47
+ desc = get_input("Enter pretty file name (leave blank to use raw file name): ")
48
+
49
+ # Return a hash directly for single files
50
+ { "#{"#{desc} || " unless desc.empty?}#{Gistribute.encode file}" => { content: } }
51
+ end
52
+ end
53
+ end
54
+ end