diff options
author | 2019-10-31 19:36:37 +0100 | |
---|---|---|
committer | 2019-10-31 19:36:37 +0100 | |
commit | 8b4926a15aba21a3b0db3ef84553e59cb9b83419 (patch) | |
tree | e4821c0b441ca584129e4b9338c5fb4609753264 | |
download | gitsearch-8b4926a15aba21a3b0db3ef84553e59cb9b83419.tar.gz gitsearch-8b4926a15aba21a3b0db3ef84553e59cb9b83419.tar.xz gitsearch-8b4926a15aba21a3b0db3ef84553e59cb9b83419.zip |
Initial import
-rw-r--r-- | .editorconfig | 23 | ||||
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | .rubocop.yml | 31 | ||||
-rw-r--r-- | .ruby-version | 1 | ||||
-rw-r--r-- | COPYING | 14 | ||||
-rw-r--r-- | Gemfile | 15 | ||||
-rw-r--r-- | Gemfile.lock | 71 | ||||
-rw-r--r-- | README.org | 39 | ||||
-rw-r--r-- | Rakefile | 3 | ||||
-rw-r--r-- | config.ru | 4 | ||||
-rw-r--r-- | config/sources.yml | 8 | ||||
-rw-r--r-- | docs/gitsearch.service.example | 21 | ||||
-rw-r--r-- | gitsearch.rb | 35 | ||||
-rw-r--r-- | lib/git_search.rb | 115 | ||||
-rw-r--r-- | lib/tasks/search.rake | 34 | ||||
-rw-r--r-- | lib/tasks/sinatra.rake | 36 | ||||
-rw-r--r-- | public/style.css | 142 | ||||
-rw-r--r-- | tmp/pids/.keep | 0 | ||||
-rw-r--r-- | views/index.slim | 24 | ||||
-rw-r--r-- | views/layout.slim | 27 |
20 files changed, 646 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..fd65159 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 2 +max_line_length = 72 + +[*.js] +# Because of long method names in vanilla js +max_line_length = 80 + +[*.css] +# 4 space indentation +indent_style = space +indent_size = 4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..555f0dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.ruby-gemset +.bundle +tmp/pids/sinatra.pid diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..90d3a36 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,31 @@ +--- +AllCops: + TargetRubyVersion: 2.4 + +Metrics/AbcSize: + Enabled: false + +Metrics/BlockLength: + Max: 30 + +Metrics/MethodLength: + Max: 15 + +Style/ConditionalAssignment: + EnforcedStyle: assign_inside_condition + +Style/ClassAndModuleChildren: + EnforcedStyle: compact + +Style/HashSyntax: + EnforcedStyle: no_mixed_keys + +Style/RegexpLiteral: + EnforcedStyle: slashes + AllowInnerSlashes: true + +Style/SymbolArray: + EnforcedStyle: brackets + +Style/WordArray: + EnforcedStyle: brackets diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..d7edb56 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +ruby-2.6.5 @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem 'curb', '~> 0.9' +gem 'rainbow', '~> 2.2' +gem 'rake', '~> 12.0' +gem 'sinatra', '~> 2.0' +gem 'slim', '~> 3.0' +gem 'thin', '~> 1.7' + +group :development do + gem 'rspec' + gem 'rubocop' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..0127382 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,71 @@ +GEM + remote: https://rubygems.org/ + specs: + ast (2.4.0) + curb (0.9.10) + daemons (1.3.1) + diff-lcs (1.3) + eventmachine (1.2.7) + jaro_winkler (1.5.4) + mustermann (1.0.3) + parallel (1.18.0) + parser (2.6.5.0) + ast (~> 2.4.0) + rack (2.0.7) + rack-protection (2.0.7) + rack + rainbow (2.2.2) + rake + rake (12.3.3) + rspec (3.9.0) + rspec-core (~> 3.9.0) + rspec-expectations (~> 3.9.0) + rspec-mocks (~> 3.9.0) + rspec-core (3.9.0) + rspec-support (~> 3.9.0) + rspec-expectations (3.9.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-mocks (3.9.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-support (3.9.0) + rubocop (0.76.0) + jaro_winkler (~> 1.5.1) + parallel (~> 1.10) + parser (>= 2.6) + rainbow (>= 2.2.2, < 4.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 1.7) + ruby-progressbar (1.10.1) + sinatra (2.0.7) + mustermann (~> 1.0) + rack (~> 2.0) + rack-protection (= 2.0.7) + tilt (~> 2.0) + slim (3.0.9) + temple (>= 0.7.6, < 0.9) + tilt (>= 1.3.3, < 2.1) + temple (0.8.2) + thin (1.7.2) + daemons (~> 1.0, >= 1.0.9) + eventmachine (~> 1.0, >= 1.0.4) + rack (>= 1, < 3) + tilt (2.0.10) + unicode-display_width (1.6.0) + +PLATFORMS + ruby + +DEPENDENCIES + curb (~> 0.9) + rainbow (~> 2.2) + rake (~> 12.0) + rspec + rubocop + sinatra (~> 2.0) + slim (~> 3.0) + thin (~> 1.7) + +BUNDLED WITH + 2.0.2 diff --git a/README.org b/README.org new file mode 100644 index 0000000..7c47826 --- /dev/null +++ b/README.org @@ -0,0 +1,39 @@ +#+title: Unshorten + +*Unshorten* is a little [[http://sinatrarb.com][Sinatra]] app, which provides a way to expand +short links. + +* Install + +These installation instructions use [[https://rvm.io][rvm]] as ruby provider. If you want +to use rbenv or your system ruby, please refer to their specific +documentation to know how to install ruby 2.4. + +#+begin_src sh +$ rvm get latest +$ rvm install ruby-2.4.1 +$ rvm use ruby-2.4.1@unshorten --create +$ gem install bundler +$ bundle install +#+end_src + +* Running it + +For testing it, you should just run: + +#+begin_src sh +$ bundle exec rake sinatra:start +#+end_src + +For production use, just pass the right environnement variable: + +#+begin_src sh +$ APP_ENV=production bundle exec rake sinatra:start +#+end_src + +To stop it, just enter =ctrl+C= if you are in development mode or enter +the following command if you are in production mode: + +#+begin_src sh +$ APP_ENV=production bundle exec rake sinatra:stop +#+end_src diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..358da59 --- /dev/null +++ b/Rakefile @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +Dir.glob('lib/tasks/*.rake').each { |r| import r } diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..d99fc4c --- /dev/null +++ b/config.ru @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +require './gitsearch.rb' +run Gitsearch diff --git a/config/sources.yml b/config/sources.yml new file mode 100644 index 0000000..1c4667d --- /dev/null +++ b/config/sources.yml @@ -0,0 +1,8 @@ +--- +gitlab: + - gitlab.com + - framagit.org + - gitlab.gnome.org +gogs: + - code.orgmode.org +github: yes diff --git a/docs/gitsearch.service.example b/docs/gitsearch.service.example new file mode 100644 index 0000000..82831a0 --- /dev/null +++ b/docs/gitsearch.service.example @@ -0,0 +1,21 @@ +[Unit] +Description=Control Sinatra Server for gitsearch +After=network.target +ConditionPathExists=!/home/deployer/gitsearch/tmp/pids/sinatra.pid + +[Service] +Type=simple +User=deployer +Group=http +Environment=APP_ENV=production +Environment="PATH=/home/deployer/.rvm/gems/ruby-2.6.5@gitsearch/bin:/home/deployer/.rvm/gems/ruby-2.6.5@global/bin:/home/deployer/.rvm/rubies/ruby-2.6.5/bin:/home/deployer/.rvm/bin:/usr/local/bin:/usr/bin:/bin" +Environment="GEM_HOME=/home/deployer/.rvm/gems/ruby-2.6.5@gitsearch" +Environment="GEM_PATH=/home/deployer/.rvm/gems/ruby-2.6.5@gitsearch:/home/deployer/.rvm/gems/ruby-2.6.5@global" +WorkingDirectory=/home/deployer/gitsearch +ExecStart=/home/deployer/.rvm/gems/ruby-2.6.5@gitsearch/bin/rackup -E production -P tmp/pids/sinatra.pid -o 127.0.0.1 +KillMode=control-group +ExecStopPost=/usr/bin/rm -f /home/deployer/gitsearch/tmp/pids/sinatra.pid +PIDFile=/home/deployer/gitsearch/tmp/pids/sinatra.pid + +[Install] +WantedBy=multi-user.target diff --git a/gitsearch.rb b/gitsearch.rb new file mode 100644 index 0000000..15f81cc --- /dev/null +++ b/gitsearch.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'sinatra/base' +require 'slim/smart' +require './lib/git_search' + +# Main Sinatra application +class Gitsearch < Sinatra::Base + configure :production, :development do + enable :logging + disable :method_override, :sessions + end + + def chdir + Dir.chdir settings.root + end + + before do + chdir + end + + get '/' do + @results = GitSearch.query(params[:search]) if params[:search] + @title = 'Git Search' + slim :index + end + + error 403 do + slim 'h1 Access forbidden' + end + + error 404 do + slim 'h1 Not Found' + end +end diff --git a/lib/git_search.rb b/lib/git_search.rb new file mode 100644 index 0000000..2633f11 --- /dev/null +++ b/lib/git_search.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +require 'curb' +require 'json' +require 'yaml' +require 'uri' + +# CURL helper +class GitSearch + def initialize(query) + @query = query + yml_path = File.join('config', 'sources.yml') + if File.exist? yml_path + @sources = YAML.load_file(yml_path).freeze + else + @sources = {} + end + end + + def curl_perform(url) + c = Curl::Easy.new url + c.headers['User-Agent'] = 'GitSearch 0.1' + c.follow_location = false + c.set :forbid_reuse, 1 + c.perform + JSON.parse(c.body_str) + end + + def github_like(data) + results = [] + data.each do |item| + next if item['fork'] + results << { + 'name' => item['name'], + 'name_with_namespace' => item['full_name'].split('/').join(' / '), + 'description' => item['description'], + 'web_url' => item['html_url'], + 'last_activity_at' => item['updated_at'], + 'avatar_url' => item['owner']['avatar_url'], + 'source' => URI(item['html_url']).host + } + end + results + end + + def raw_github + r = curl_perform( + "https://api.github.com/search/repositories?q=#{@query}" + ) + r['items'] || [] + end + + def github + github_like(raw_github).each do |g| + g['icon'] = "https://#{g['source']}/favicon.ico" + end + end + + def raw_gogs + return [] unless @sources['gogs']&.any? + results = [] + request = '/api/v1/repos/search?q=' + @sources['gogs'].each do |domain| + r = curl_perform("https://#{domain}#{request}#{@query}") + results += r['data'] || [] + end + results + end + + def gogs + github_like(raw_gogs).each do |g| + g['icon'] = "https://#{g['source']}/img/favicon.png" + end + end + + def raw_gitlab + return [] unless @sources['gitlab']&.any? + results = [] + request = '/api/v4/projects?order_by=last_activity_at&search=' + @sources['gitlab'].each do |domain| + results += curl_perform("https://#{domain}#{request}#{@query}") + end + results + end + + def gitlab + results = [] + raw_gitlab.each do |rg| + rg['source'] = URI(rg['web_url']).host + rg['icon'] = "https://#{rg['source']}/favicon.png" + results << rg + end + results + end + + def sort(list) + list.sort do |a, b| + a['name'].downcase <=> b['name'].downcase + end + end + + def raw + @sources.keys.map { |s| send "raw_#{s}".to_sym }.flatten + end + + def run + sort @sources.keys.map { |s| send s.to_sym }.flatten + end + + class << self + def query(query) + GitSearch.new(query).run + end + end +end diff --git a/lib/tasks/search.rake b/lib/tasks/search.rake new file mode 100644 index 0000000..be9b08c --- /dev/null +++ b/lib/tasks/search.rake @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'rainbow' +require 'uri' +require './lib/git_search' + +namespace :search do + desc 'Pretty search result' + task :pretty, [:query] do |_, args| + loc_query = args[:query] || ENV['query'] || '' + if loc_query == '' + STDERR.puts Rainbow('You must give us at least a word').red + exit 1 + end + GitSearch.query(loc_query).each do |r| + puts Rainbow(r['name']).blue.bright + ' :: ' + r['web_url'] + puts Rainbow(r['last_activity_at']).yellow + if !r['description'].nil? && r['description'] != '' + puts r['description'] + end + puts '' + end + end + + desc 'Raw api results' + task :raw, [:query] do |_, args| + loc_query = args[:query] || ENV['query'] || '' + if loc_query == '' + STDERR.puts Rainbow('You must give us at least a word').red + exit 1 + end + puts GitSearch.new(loc_query).raw + end +end diff --git a/lib/tasks/sinatra.rake b/lib/tasks/sinatra.rake new file mode 100644 index 0000000..ea5e4f1 --- /dev/null +++ b/lib/tasks/sinatra.rake @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'rainbow' + +namespace :sinatra do + desc 'Stop the underlaying sinatra application' + task :stop do + unless File.exist? 'tmp/pids/sinatra.pid' + STDERR.puts Rainbow('No pid file found').red + exit 1 + end + pid = IO.read('tmp/pids/sinatra.pid').strip.to_i + Process.kill('TERM', pid) + File.unlink 'tmp/pids/sinatra.pid' + puts Rainbow('Done').green + end + + desc 'Start the underlaying sinatra application' + task :start do + loc_env = ENV['APP_ENV'] || 'development' + cmd = ['rackup', "-E #{loc_env}", '-P', 'tmp/pids/sinatra.pid'] + cmd << '-D' if loc_env == 'production' + begin + if ENV['APP_ENV'].nil? + system({ 'APP_ENV' => loc_env }, cmd.join(' ')) + else + sh(*cmd) + end + rescue Interrupt + puts Rainbow('Kthxbye').blue + end + end + + desc 'Restart local sinatra server' + task restart: ['sinatra:stop', 'sinatra:start'] +end diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000..b843a61 --- /dev/null +++ b/public/style.css @@ -0,0 +1,142 @@ +body { + font-family: sans-serif; + font-size: 20pt; +} + +fieldset { + border: 0px; +} + +a:hover { + text-decoration: none; +} + +dt { + font-weight: bold; +} + +figure { + text-align: center; +} + +kbd { + border-width: .3em; + border-style: solid; +} + +#content, +#footer { + width: 968px; + margin: 1em auto; +} + +/** + * Content + */ + +#content { + padding: 1em; + line-height: 1.5em; +} + +#content h1 { + margin: 0; + line-height: 1.5em; +} + +.result li { + background: #44475a; + padding: .1em 1em; + border-radius: .5em; + display: block; + margin: 0px 0px 1em; + list-style: none; +} + +.result .avatar { + max-width: 48px; + float: left; + margin-top: 1.3em; + margin-right: 1em; +} + +.result .source { + max-width: 32px; + float: right; + margin-top: 1.3em; +} + +/** + * Footer + */ +#footer { + clear: both; + padding-top: 0; + margin-bottom: .5em; +} +.light { + font-size: .7em; + color: #6272a4; +} + +#footer nav ul { + padding: 0px; +} +#footer nav li { + list-style: none; + display: inline-block; + margin-right: .5em; +} +#footer nav li:after { + margin-left: .5em; + content: '-'; +} +#footer nav ul li:last-child:after { + content: ''; +} + +/** + * Responsive... + */ +@media screen and (max-width: 968px) { + #content, .status { + width: 90%; + } +} + + +/** + * Colors + * We used the Dracula color theme + * https://draculatheme.com/ + */ + +body { + background: #282a36; + color: #f8f8f2; +} + +h1 { + color: #ffb86c; +} + +h2 { + color: #bd93f9; +} + +a:link, a:visited { + color: #ff79c6; +} + +kbd { + border-color: #f8f8f2; +} + +code { + background: #373844; + color: #e2e2dc; +} + +mark { + background: #f1fa8c; +} diff --git a/tmp/pids/.keep b/tmp/pids/.keep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tmp/pids/.keep diff --git a/views/index.slim b/views/index.slim new file mode 100644 index 0000000..5a4f96f --- /dev/null +++ b/views/index.slim @@ -0,0 +1,24 @@ +form action=url('/') method='get' + fieldset + input#tiny_url_field(name='search' type='text' + style='width:300px;' + placeholder='What?' + value=params[:search]) + input type='submit' value='Search' + +- if @results && @results.any? + ul.result + - @results.each do |r| + li + img.source src=r['icon'] alt=r['source'] title=r['source'] + - unless r['avatar_url'].nil? + img.avatar src=r['avatar_url'] alt='' + p + strong + a href=r['web_url'] title=r['web_url'] + = r['name_with_namespace'] + br + | last updated: + time.light< = r['last_activity_at'] + - if !r['description'].nil? && r['description'] != '' + p = r['description'] diff --git a/views/layout.slim b/views/layout.slim new file mode 100644 index 0000000..7f391bb --- /dev/null +++ b/views/layout.slim @@ -0,0 +1,27 @@ +doctype html +html + head + title #{@title} + meta name='author' content='Étienne Deparis' + link rel='stylesheet' href='/style.css' type='text/css' + + body + #content + h1 = @title + + == yield + + footer#footer.light + nav + ul + li: a href=url('/') Home + + nav + ul + li + Author: + a< href='https://etienne.depar.is' Étienne Deparis + li + License: + a< href='http://www.wtfpl.net/txt/copying/' WTFPL + li: a href='https://git.deparis.io/gitsearch/about/' Sources |