aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Étienne Deparis 2019-10-31 19:36:37 +0100
committerGravatar Étienne Deparis 2019-10-31 19:36:37 +0100
commit8b4926a15aba21a3b0db3ef84553e59cb9b83419 (patch)
treee4821c0b441ca584129e4b9338c5fb4609753264
downloadgitsearch-8b4926a15aba21a3b0db3ef84553e59cb9b83419.tar.gz
gitsearch-8b4926a15aba21a3b0db3ef84553e59cb9b83419.tar.xz
gitsearch-8b4926a15aba21a3b0db3ef84553e59cb9b83419.zip
Initial import
-rw-r--r--.editorconfig23
-rw-r--r--.gitignore3
-rw-r--r--.rubocop.yml31
-rw-r--r--.ruby-version1
-rw-r--r--COPYING14
-rw-r--r--Gemfile15
-rw-r--r--Gemfile.lock71
-rw-r--r--README.org39
-rw-r--r--Rakefile3
-rw-r--r--config.ru4
-rw-r--r--config/sources.yml8
-rw-r--r--docs/gitsearch.service.example21
-rw-r--r--gitsearch.rb35
-rw-r--r--lib/git_search.rb115
-rw-r--r--lib/tasks/search.rake34
-rw-r--r--lib/tasks/sinatra.rake36
-rw-r--r--public/style.css142
-rw-r--r--tmp/pids/.keep0
-rw-r--r--views/index.slim24
-rw-r--r--views/layout.slim27
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
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..5a8e332
--- /dev/null
+++ b/COPYING
@@ -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.
+
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..f8c9413
--- /dev/null
+++ b/Gemfile
@@ -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