github-grep 0.0.0 → 0.1.0
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.
- checksums.yaml +4 -4
- data/README.md +6 -8
- data/bin/github-grep +7 -69
- data/lib/github_grep.rb +89 -0
- metadata +4 -17
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: bed5f83e7664a3ce11e21935ebc1be098517cd6ab4aa7614b8c7d42309fb0544
         | 
| 4 | 
            +
              data.tar.gz: 7b3923830d8148962d77adf72138cd2576d153fcb928a7837f40d3024d29366c
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: '091a02da44da6fb6b16f01bf82a57a9e690a091da17c702a4c58e24a33f57428dadf0283fa93e92f370f81e857c85e723dacda1b4890543e5fa84fb34aa27cee'
         | 
| 7 | 
            +
              data.tar.gz: 0a02ebf19cc013c6ff40f06f70b802875287f81dd32b65c33fe36b48228d96a120a250f9eaa16c1db52c08f516f3d60d793c9d85960975505197e793ec3f87c3
         | 
    
        data/README.md
    CHANGED
    
    | @@ -1,20 +1,18 @@ | |
| 1 1 | 
             
            makes github search grep and pipeable
         | 
| 2 2 |  | 
| 3 | 
            -
              | 
| 4 | 
            -
             - clone repo
         | 
| 5 | 
            -
             - follow script instructions
         | 
| 3 | 
            +
            First create a [application token](https://github.com/settings/applications) with read access + enable SSO if available.
         | 
| 6 4 |  | 
| 7 5 | 
             
            ```
         | 
| 8 | 
            -
             | 
| 6 | 
            +
            gem install github-grep
         | 
| 9 7 |  | 
| 10 | 
            -
            export GITHUB_TOKEN | 
| 11 | 
            -
            # or: git config github.token  | 
| 8 | 
            +
            export GITHUB_TOKEN=<your token>
         | 
| 9 | 
            +
            # or: git config github.token <your token>
         | 
| 12 10 |  | 
| 13 11 | 
             
            # search code:
         | 
| 14 | 
            -
             | 
| 12 | 
            +
            github-grep 'user:grosser unicorn' | grep 'dictionary' | grep -v 'higher'
         | 
| 15 13 |  | 
| 16 14 | 
             
            # search issues and PR comments:
         | 
| 17 | 
            -
             | 
| 15 | 
            +
            github-grep 'repo:kubernetes/kubernetes network error' --issues | grep 'narrow-it-down' | grep -v 'something good'
         | 
| 18 16 | 
             
            ```
         | 
| 19 17 |  | 
| 20 18 | 
             
            NOTE: there are random 403 errors on the last page of a search (usually empty anyway), contacted github support about that :/
         | 
    
        data/bin/github-grep
    CHANGED
    
    | @@ -1,11 +1,9 @@ | |
| 1 1 | 
             
            #!/usr/bin/env ruby
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            require  | 
| 4 | 
            -
            require 'shellwords'
         | 
| 5 | 
            -
            require 'open3'
         | 
| 2 | 
            +
            $LOAD_PATH << File.expand_path("../../lib", __FILE__)
         | 
| 3 | 
            +
            require "github_grep"
         | 
| 6 4 |  | 
| 7 5 | 
             
            def usage
         | 
| 8 | 
            -
              puts  | 
| 6 | 
            +
              puts <<~TEXT
         | 
| 9 7 | 
             
                Setup
         | 
| 10 8 | 
             
                -----
         | 
| 11 9 | 
             
                # create a new token at https://github.com/settings/tokens/new with repo access
         | 
| @@ -18,74 +16,14 @@ def usage | |
| 18 16 | 
             
              exit 1
         | 
| 19 17 | 
             
            end
         | 
| 20 18 |  | 
| 21 | 
            -
            def code_items_to_lines(items)
         | 
| 22 | 
            -
              items.flat_map do |item|
         | 
| 23 | 
            -
                file = item.fetch('repository').fetch('name') + ":" + item.fetch('path')
         | 
| 24 | 
            -
                lines(item).map { |l| "#{file}: #{l}" }
         | 
| 25 | 
            -
              end
         | 
| 26 | 
            -
            end
         | 
| 27 | 
            -
             | 
| 28 | 
            -
            def issue_items_to_lines(items)
         | 
| 29 | 
            -
              items.flat_map do |item|
         | 
| 30 | 
            -
                number = item.fetch("number")
         | 
| 31 | 
            -
                lines(item).map { |l| "##{number}: #{l}" }
         | 
| 32 | 
            -
              end
         | 
| 33 | 
            -
            end
         | 
| 34 | 
            -
             | 
| 35 | 
            -
            def lines(item)
         | 
| 36 | 
            -
              item.fetch("text_matches").flat_map { |match| match.fetch('fragment').split("\n") }
         | 
| 37 | 
            -
            end
         | 
| 38 | 
            -
             | 
| 39 | 
            -
            def search(q, type)
         | 
| 40 | 
            -
              per_page = 100
         | 
| 41 | 
            -
              page = 1
         | 
| 42 | 
            -
             | 
| 43 | 
            -
              loop do
         | 
| 44 | 
            -
                response = page(q, type, page, per_page)
         | 
| 45 | 
            -
                if page == 1
         | 
| 46 | 
            -
                  $stderr.puts "Found #{response.fetch("total_count")}"
         | 
| 47 | 
            -
                else
         | 
| 48 | 
            -
                  $stderr.puts "Page #{page}"
         | 
| 49 | 
            -
                end
         | 
| 50 | 
            -
             | 
| 51 | 
            -
                items = response.fetch('items')
         | 
| 52 | 
            -
                yield items
         | 
| 53 19 |  | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
              end
         | 
| 57 | 
            -
            end
         | 
| 58 | 
            -
             | 
| 59 | 
            -
            def page(q, type, page, per_page)
         | 
| 60 | 
            -
              github_token = ENV['GITHUB_TOKEN'] || `git config github.token`.strip # TODO: update docs
         | 
| 61 | 
            -
              usage if github_token.empty?
         | 
| 62 | 
            -
             | 
| 63 | 
            -
              # remove --fail and add -v to see response headers
         | 
| 64 | 
            -
              # NOTE: github returns a 403 with a Retry-After: 60 on page 3+ ... talking with support atm but might have to handle it
         | 
| 65 | 
            -
              url = "https://api.github.com/search/#{type}?per_page=#{per_page}&page=#{page}&q=#{CGI.escape(q)}"
         | 
| 66 | 
            -
              command = ["curl", "-v", "-f", "-H", "Authorization: token #{github_token}", "-H", "Accept: application/vnd.github.v3.text-match+json", url]
         | 
| 67 | 
            -
             | 
| 68 | 
            -
              out, err, status = Open3.capture3(*command)
         | 
| 69 | 
            -
              if retry_after = err[/Retry-After: (\d+)/, 1] # 403 Abuse rate limit
         | 
| 70 | 
            -
                warn "Sleeping #{retry_after} to avoid abuse rate-limit"
         | 
| 71 | 
            -
                sleep Integer(retry_after)
         | 
| 72 | 
            -
                out, err, status = Open3.capture3(*command)
         | 
| 73 | 
            -
              end
         | 
| 74 | 
            -
             | 
| 75 | 
            -
              raise "ERROR Request failed\n#{url}\n#{err}\n#{out}" unless status.success?
         | 
| 76 | 
            -
             | 
| 77 | 
            -
              JSON.load(out)
         | 
| 78 | 
            -
            end
         | 
| 20 | 
            +
            github_token = ENV['GITHUB_TOKEN'] || `git config github.token`.strip # TODO: update docs
         | 
| 21 | 
            +
            usage if github_token.empty?
         | 
| 79 22 |  | 
| 80 23 | 
             
            type = (ARGV.delete('--issues') ? :issues : :code)
         | 
| 81 24 |  | 
| 82 25 | 
             
            q = ARGV.shift
         | 
| 83 26 | 
             
            usage if ARGV.size != 0
         | 
| 84 27 |  | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
                puts issue_items_to_lines(items)
         | 
| 88 | 
            -
              else
         | 
| 89 | 
            -
                puts code_items_to_lines(items)
         | 
| 90 | 
            -
              end
         | 
| 91 | 
            -
            end
         | 
| 28 | 
            +
            grep = GithubGrep.new(github_token)
         | 
| 29 | 
            +
            grep.render_search(q, type) { |slice| puts slice }
         | 
    
        data/lib/github_grep.rb
    ADDED
    
    | @@ -0,0 +1,89 @@ | |
| 1 | 
            +
            require 'cgi'
         | 
| 2 | 
            +
            require 'json'
         | 
| 3 | 
            +
            require 'shellwords'
         | 
| 4 | 
            +
            require 'open3'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            class GithubGrep
         | 
| 7 | 
            +
              VERSION = "0.1.0"
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              def initialize(token)
         | 
| 10 | 
            +
                @token = token
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              def render_search(q, type)
         | 
| 14 | 
            +
                search(q, type) do |items|
         | 
| 15 | 
            +
                  if type == :issues
         | 
| 16 | 
            +
                    yield issue_items_to_lines(items)
         | 
| 17 | 
            +
                  else
         | 
| 18 | 
            +
                    yield code_items_to_lines(items)
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              def search(q, type, &block)
         | 
| 24 | 
            +
                headers = ["-H", "Accept: application/vnd.github.v3.text-match+json"]
         | 
| 25 | 
            +
                url = "https://api.github.com/search/#{type}?q=#{CGI.escape(q)}"
         | 
| 26 | 
            +
                all_pages(url, per_page: 100, argv: headers, &block)
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              private
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              def code_items_to_lines(items)
         | 
| 32 | 
            +
                items.flat_map do |item|
         | 
| 33 | 
            +
                  file = item.fetch('repository').fetch('name') + ":" + item.fetch('path')
         | 
| 34 | 
            +
                  lines(item).map { |l| "#{file}: #{l}" }
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              def issue_items_to_lines(items)
         | 
| 39 | 
            +
                items.flat_map do |item|
         | 
| 40 | 
            +
                  number = item.fetch("number")
         | 
| 41 | 
            +
                  lines(item).map { |l| "##{number}: #{l}" }
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              def lines(item)
         | 
| 46 | 
            +
                item.fetch("text_matches").flat_map { |match| match.fetch('fragment').split("\n") }
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              def all_pages(url, per_page:, **kwargs)
         | 
| 50 | 
            +
                page = 1
         | 
| 51 | 
            +
                connector = (url.include?("?") ? "&" : "?")
         | 
| 52 | 
            +
                loop do
         | 
| 53 | 
            +
                  response = request_json("#{url}#{connector}per_page=#{per_page}&page=#{page}", **kwargs)
         | 
| 54 | 
            +
                  hash = response.is_a?(Hash)
         | 
| 55 | 
            +
                  if page == 1 && hash && total = response["total_count"]
         | 
| 56 | 
            +
                    $stderr.puts "Found #{total}"
         | 
| 57 | 
            +
                  else
         | 
| 58 | 
            +
                    $stderr.puts "Page #{page}"
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  items = (hash ? response.fetch('items') : response)
         | 
| 62 | 
            +
                  yield items
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  break if items.size < per_page
         | 
| 65 | 
            +
                  page += 1
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
              end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
              def request_json(url, argv: [])
         | 
| 70 | 
            +
                # NOTE: github returns a 403 with a Retry-After: 60 on page 3+ ... talking with support atm but might have to handle it
         | 
| 71 | 
            +
                command = ["curl", "-sSfv", "-H", "Authorization: token #{@token}", *argv, url]
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                out, err, status = Open3.capture3(*command)
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                # 403 Abuse rate limit often has no Retry-After
         | 
| 76 | 
            +
                retry_after = err[/Retry-After: (\d+)/, 1]
         | 
| 77 | 
            +
                abuse_limit = err.include?("returned error: 403")
         | 
| 78 | 
            +
                if retry_after || abuse_limit
         | 
| 79 | 
            +
                  retry_after ||= "20"
         | 
| 80 | 
            +
                  warn "Sleeping #{retry_after} to avoid abuse rate-limit"
         | 
| 81 | 
            +
                  sleep Integer(retry_after)
         | 
| 82 | 
            +
                  out, err, status = Open3.capture3(*command)
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                raise "ERROR Request failed\n#{url}\n#{err}\n#{out}" unless status.success?
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                JSON.parse(out)
         | 
| 88 | 
            +
              end
         | 
| 89 | 
            +
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,29 +1,15 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: github-grep
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.1.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Michael Grosser
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2021-10- | 
| 11 | 
            +
            date: 2021-10-18 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 | 
            -
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            -
              name: json
         | 
| 15 | 
            -
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            -
                requirements:
         | 
| 17 | 
            -
                - - ">="
         | 
| 18 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            -
                    version: '0'
         | 
| 20 | 
            -
              type: :runtime
         | 
| 21 | 
            -
              prerelease: false
         | 
| 22 | 
            -
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            -
                requirements:
         | 
| 24 | 
            -
                - - ">="
         | 
| 25 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            -
                    version: '0'
         | 
| 27 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 28 14 | 
             
              name: rake
         | 
| 29 15 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -62,6 +48,7 @@ files: | |
| 62 48 | 
             
            - MIT-LICENSE.txt
         | 
| 63 49 | 
             
            - README.md
         | 
| 64 50 | 
             
            - bin/github-grep
         | 
| 51 | 
            +
            - lib/github_grep.rb
         | 
| 65 52 | 
             
            homepage: https://github.com/grosser/github-grep
         | 
| 66 53 | 
             
            licenses:
         | 
| 67 54 | 
             
            - MIT
         | 
| @@ -74,7 +61,7 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 74 61 | 
             
              requirements:
         | 
| 75 62 | 
             
              - - ">="
         | 
| 76 63 | 
             
                - !ruby/object:Gem::Version
         | 
| 77 | 
            -
                  version:  | 
| 64 | 
            +
                  version: '0'
         | 
| 78 65 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 79 66 | 
             
              requirements:
         | 
| 80 67 | 
             
              - - ">="
         |