gitnoted 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 514bf87ae44f1a65208940e4a8db87e5a049b27d
4
+ data.tar.gz: 944db11d0806f73287350138421c35d8b956b38f
5
+ SHA512:
6
+ metadata.gz: 0ae0007cff3bb75b96d16831d7e779ab2798a80db0dfade08fe3dcb88c9b7b6f470dcb39cb41832c9dc207f14d5a458a6e4a3e98123552ac0b9ce63ee3f2b320
7
+ data.tar.gz: 32581b4a57bcf3970965cd85f448bbd2955d3cf2b530332b2be49b7edb89a9bbff6234efc5f18abad3385ae152fbe3d5fa8c4647d1d164354ee49b4f764a64ca
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -0,0 +1,40 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ gitnoted (0.1.0)
5
+ concurrent-ruby (>= 1.0.5)
6
+ puma (~> 3.7)
7
+ rack-cors (~> 0.4)
8
+ redcarpet (~> 3.4)
9
+ rugged (~> 0.25)
10
+ sigdump (>= 0.2.4)
11
+ sinatra (~> 1.4)
12
+
13
+ GEM
14
+ remote: https://rubygems.org/
15
+ specs:
16
+ concurrent-ruby (1.0.5)
17
+ puma (3.8.1)
18
+ rack (1.6.5)
19
+ rack-cors (0.4.1)
20
+ rack-protection (1.5.3)
21
+ rack
22
+ rake (12.0.0)
23
+ redcarpet (3.4.0)
24
+ rugged (0.25.1.1)
25
+ sigdump (0.2.4)
26
+ sinatra (1.4.8)
27
+ rack (~> 1.5)
28
+ rack-protection (~> 1.4)
29
+ tilt (>= 1.3, < 3)
30
+ tilt (2.0.6)
31
+
32
+ PLATFORMS
33
+ ruby
34
+
35
+ DEPENDENCIES
36
+ gitnoted!
37
+ rake (>= 0.9.2)
38
+
39
+ BUNDLED WITH
40
+ 1.14.6
@@ -0,0 +1,162 @@
1
+ # GitNoted
2
+
3
+ GitNoted is a simple document server that works with [Github Wiki](https://help.github.com/articles/about-github-wikis/) or [Gollum](https://github.com/gollum/gollum) to serve foot notes for external web sites just by adding a small script tag.
4
+
5
+ * [JavaScript tag](#javascript-tag)
6
+ * [Writing notes](#writing-notes)
7
+ * [Running server with Github Wiki](#running-server-with-github-wiki)
8
+ * [Running server with other git repositories](#running-server-with-other-git-repositories)
9
+ * [Deploying to Heroku](#deploying-to-heroku)
10
+ * [Server usage](#server-usage)
11
+
12
+ ## JavaScript tag
13
+
14
+ You can find the JavaScript code on [lib/git_noted/public/js/gitnoted.js](lib/git_noted/public/js/gitnoted.js). This file will be served as `/js/gitnoted.js` from the GitNoted server. You can import it using `<script>` tag as following.
15
+
16
+ ```
17
+ <!-- GitNoted depends on jQuery -->
18
+ <script src="https://code.jquery.com/jquery-3.1.1.min.js"
19
+ integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
20
+ crossorigin="anonymous"></script>
21
+
22
+ <!-- GitNoted -->
23
+ <link rel="stylesheet" type="text/css" href="http://localhost:4567/css/gitnoted.css" />
24
+ <script type="text/javascript" src="http://localhost:4567/js/gitnoted.js"></script>
25
+ ```
26
+
27
+ Please replace `http://localhost:4567` to point your GitNoted server (see bellow).
28
+
29
+ Once the JavaScript tag is set up, you can add following `<div class="gitnoted" data-labels="label,label,...">` tags your HTML to embed notes. Here is an example:
30
+
31
+ ```
32
+ <div class="gitnoted" data-labels="purpose:test,message:hello"></div>
33
+ ```
34
+
35
+ `data-labels` is used to search notes by labels. Notes that include all of them match.
36
+
37
+
38
+ ## Writing notes
39
+
40
+ Edit mode must be Markdown. Other formats are not supported.
41
+
42
+ In the body of a page, you need to include `:label: label,label,label,...` line at the beginning or at the end. Example:
43
+
44
+ ```
45
+ # My first wiki page
46
+
47
+ Hello!
48
+
49
+ ---
50
+
51
+ :label: purpose:test,message:hello
52
+ ```
53
+
54
+ ## Running server with Github Wiki
55
+
56
+ Prepare following information first:
57
+
58
+ * Domain names (and ports) of your website that embeds notes. You can have multiple web sites. Here uses `example.com` and `localhost:8080` as an example.
59
+ * Repository URL of a Github Wiki. Format of the URL is `https://github.com/<user>/<repo>.wiki.git` where "&lt;user&gt; and &lt;repo&gt; are your repository's username and repository name.
60
+
61
+ This command starts a server on http://localhost:4567:
62
+
63
+ ```
64
+ $ gem install gitnoted
65
+ $ gitnoted "https://github.com/frsyuki/gitnoted.wiki.git" \
66
+ ./repo \
67
+ -a example.com -a localhost:8080 \
68
+ -h localhost -p 4567
69
+ ```
70
+
71
+ If your repository is private, you also need to set `GITHUB_ACCESS_TOKEN` environment variable. You can create a token on [your account configuration page](https://github.com/settings/tokens). Example:
72
+
73
+ ```
74
+ $ gem install gitnoted
75
+ $ export GITHUB_ACCESS_TOKEN=abcdef0123456789abcdef0123456789abcdef01
76
+ $ gitnoted \
77
+ "https://github.com/frsyuki/my_secret_repository.wiki.git" \
78
+ ./repo \
79
+ -a example.com -a localhost:8080 \
80
+ -h localhost -p 4567
81
+ ```
82
+
83
+ ## Running server with other git repositories
84
+
85
+ Instead of using Github Wiki, you can use any git repositories that contain files named `<name>.md`. You can use your text editor or tools such as [Gollum](https://github.com/gollum/gollum) to push `.md` files.
86
+
87
+ To start GitNoted for those git repositories, prepare following information:
88
+
89
+ * Domain names (and ports) of your website that embeds notes. You can have multiple web sites. Here uses `example.com` and `localhost:8080` as an example.
90
+ * Repository URL of the git repository. It should provide http access (ssh is not supported at this moment).
91
+ * Username and password of the git repository. You need to set them to GIT_USERNAME and GIT_PASSWORD environment variables.
92
+
93
+ This command starts a server on http://localhost:4567:
94
+
95
+ ```
96
+ $ gem install gitnoted
97
+ $ export GIT_USERNAME=myname
98
+ $ export GIT_PASSWORD=topsecret
99
+ $ gitnoted \
100
+ "https://github.com/frsyuki/my_secret_repository.wiki.git" \
101
+ ./repo \
102
+ -a example.com -a localhost:8080 \
103
+ -h localhost -p 4567
104
+ ```
105
+
106
+ ## Deploying to Heroku
107
+
108
+ You need to create 4 files in a new git repository.
109
+
110
+ ### Procfile
111
+
112
+ Put a gitnoted command in your `Procfile` with `$PORT` variable as the port number. Here is an example:
113
+
114
+ ```
115
+ web: bundle exec gitnoted "https://github.com/frsyuki/gitnoted.wiki.git ./repo -a example.com -h 0.0.0.0 -p $PORT
116
+ ```
117
+
118
+ ### Gemfile
119
+
120
+ ```
121
+ source "https://rubygems.org"
122
+ ruby "2.4.0"
123
+ gem "gitnoted"
124
+ ```
125
+
126
+ ### .buildpacks
127
+
128
+ ```
129
+ https://github.com/ddollar/heroku-buildpack-apts
130
+ https://github.com/heroku/heroku-buildpack-ruby
131
+ ```
132
+
133
+ ### Aptfile
134
+
135
+ ```
136
+ cmake
137
+ pkg-config
138
+ ```
139
+
140
+ ## Server usage
141
+
142
+ ```
143
+ $ gitnoted [options] <git url> <local path to store>
144
+ options:
145
+ -a, --allow-origin DOMAIN[:PORT] Allow cross-origin resource sharing (CORS) from this domain (can be set multiple times)
146
+ -h, --host ADDRESS Bind address (default: 'localhost')
147
+ -p, --port PORT Port (default: 4567)
148
+ -e, --extra-app PATH.rb Add an extra Sinatra application
149
+ -i, --interval SECONDS Interval to update the git repository
150
+ --threads MIN:MAX Number of HTTP worker threads
151
+ environment variables:
152
+ GIT_USERNAME Git username
153
+ GIT_PASSWORD Git password
154
+ GITHUB_ACCESS_TOKEN Github personal API token
155
+ ```
156
+
157
+ ----
158
+
159
+ GitNoted
160
+ Author: Sadayuki Furuhashi
161
+ License: MIT
162
+
@@ -0,0 +1,5 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/testtask'
5
+ task :default => [:build]
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'logger'
5
+
6
+ extra_app_paths = []
7
+
8
+ scheduler_options = {
9
+ interval: 60
10
+ }
11
+
12
+ app_options = {
13
+ allow_origins: [],
14
+ }
15
+
16
+ repo_options = {
17
+ logger: Logger.new(STDOUT),
18
+ }
19
+
20
+ server_options = {
21
+ server: 'puma',
22
+ Host: 'localhost',
23
+ Port: 4567,
24
+ Threads: nil,
25
+ }
26
+
27
+ op = OptionParser.new
28
+ op.banner = "#{$0} [options] <git url> <local path to store>"
29
+
30
+ op.separator " options:"
31
+
32
+ op.on('-a', '--allow-origin DOMAIN[:PORT]', "Allow cross-origin resource sharing (CORS) from this domain (can be set multiple times)") do |v|
33
+ app_options[:allow_origins] << v
34
+ end
35
+
36
+ op.on('-h', '--host ADDRESS', "Bind address (default: 'localhost')") do |v|
37
+ server_options[:Host] = v
38
+ end
39
+
40
+ op.on('-p', '--port PORT', Integer, "Port (default: 4567)") do |v|
41
+ server_options[:Port] = v
42
+ end
43
+
44
+ op.on('-e', '--extra-app PATH.rb', "Add an extra Sinatra application") do |v|
45
+ extra_app_paths << v
46
+ end
47
+
48
+ op.on('-i', '--interval SECONDS', Integer, "Interval to update the git repository") do |v|
49
+ scheduler_options[:interval] = v
50
+ end
51
+
52
+ op.on('--threads MIN:MAX', "Number of HTTP worker threads") do |v|
53
+ server_options[:Threads] = v
54
+ end
55
+
56
+ op.separator <<EOF
57
+ environment variables:
58
+ GIT_USERNAME Git username
59
+ GIT_PASSWORD Git password
60
+ GITHUB_ACCESS_TOKEN Github personal API token
61
+ EOF
62
+
63
+
64
+ op.parse!(ARGV)
65
+
66
+ if ARGV.length != 2
67
+ puts op.to_s
68
+ exit 1
69
+ end
70
+
71
+ remote_url, local_path = *ARGV
72
+
73
+ require 'git_noted'
74
+ require 'sigdump/setup'
75
+
76
+ ENV['RACK_ENV'] ||= 'production'
77
+
78
+ if ENV['GIT_USERNAME']
79
+ repo_options[:username] = ENV['GIT_USERNAME']
80
+ repo_options[:password] = ENV['GIT_PASSWORD']
81
+ elsif ENV['GITHUB_ACCESS_TOKEN']
82
+ repo_options[:username] = ENV['GITHUB_ACCESS_TOKEN']
83
+ repo_options[:password] = 'x-oauth-basic'
84
+ end
85
+
86
+ repository = GitNoted::Repository.new(remote_url, local_path, **repo_options)
87
+ app = GitNoted::Application.with(repository: repository, **app_options)
88
+ repository.schedule_update! scheduler_options[:interval]
89
+
90
+ extra_app_paths.each do |path|
91
+ app.instance_eval(File.read(path), path)
92
+ end
93
+
94
+ app.run!(server_options)
95
+
@@ -0,0 +1,25 @@
1
+
2
+ Gem::Specification.new do |gem|
3
+ gem.name = "gitnoted"
4
+ gem.description = "Simple document server that works with Github Wiki or Gollum to serve foot notes for external websites just by adding a small script tag"
5
+ gem.homepage = "https://github.com/frsyuki/gitnoted"
6
+ gem.summary = gem.description
7
+ gem.version = "0.1.0"
8
+ gem.authors = ["Sadayuki Furuhashi"]
9
+ gem.email = ["frsyuki@gmail.com"]
10
+ gem.license = "MIT"
11
+ gem.has_rdoc = false
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
15
+ gem.require_paths = ['lib']
16
+
17
+ gem.add_dependency 'sinatra', '~> 1.4'
18
+ gem.add_dependency 'puma', '~> 3.7'
19
+ gem.add_dependency 'rack-cors', '~> 0.4'
20
+ gem.add_dependency 'redcarpet', '~> 3.4'
21
+ gem.add_dependency 'rugged', '~> 0.25'
22
+ gem.add_dependency 'concurrent-ruby', '>= 1.0.5'
23
+ gem.add_dependency 'sigdump', '>= 0.2.4'
24
+ gem.add_development_dependency "rake", ">= 0.9.2"
25
+ end
@@ -0,0 +1,2 @@
1
+ require 'git_noted/application'
2
+ require 'git_noted/repository'
@@ -0,0 +1,163 @@
1
+ require 'sinatra/base'
2
+ require 'rack/cors'
3
+ require 'rack/static'
4
+ require 'redcarpet'
5
+ require 'erb'
6
+ require 'json'
7
+ require 'git_noted/repository'
8
+
9
+ module GitNoted
10
+ class Application < Sinatra::Base
11
+ def self.default_renderer
12
+ renderer = Redcarpet::Render::HTML.new({
13
+ escape_html: true,
14
+ safe_links_only: true,
15
+ })
16
+ redcarpet = Redcarpet::Markdown.new(renderer, {
17
+ tables: true,
18
+ no_intra_emphasis: true
19
+ })
20
+ redcarpet.method(:render)
21
+ end
22
+
23
+ def self.with(allow_origins: [], **options)
24
+ Class.new(self) do
25
+ alias_method :initialize_saved, :initialize
26
+ define_method(:initialize) do
27
+ initialize_saved(**options)
28
+ end
29
+
30
+ use Rack::Cors do
31
+ allow do
32
+ origins *allow_origins unless allow_origins.empty?
33
+
34
+ resource '/api/*', {
35
+ methods: [:get, :options, :head],
36
+ headers: :any,
37
+ expose: [],
38
+ credentials: true,
39
+ max_age: 600,
40
+ }
41
+ end
42
+
43
+ allow do
44
+ origins '*'
45
+ resource '/public/*', :headers => :any, :methods => :get
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ def initialize(repository:, renderer: Application.default_renderer)
52
+ super()
53
+ @repository = repository
54
+ @renderer = renderer
55
+ end
56
+
57
+ attr_accessor :repository
58
+ attr_accessor :renderer
59
+
60
+ use Rack::Static, urls: ["/js", "/css"], root: File.expand_path("../public", __FILE__)
61
+
62
+ include ERB::Util
63
+
64
+ TRANSPARENT_1PX_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=".unpack('m').first
65
+
66
+ get '/' do
67
+ redirect "/index.html"
68
+ end
69
+
70
+ # GET /api/notes
71
+ # ?labels=k1:v1,k2:v2,...
72
+ # ?exclude_labels=k1:v1,k2:v2,...
73
+ get '/api/notes' do
74
+ notes = load_notes(params).map do |note|
75
+ {
76
+ labels: note.labels,
77
+ body: read_note(note)
78
+ }
79
+ end
80
+
81
+ response = {
82
+ notes: notes
83
+ }
84
+
85
+ content_type "application/json"
86
+ return response.to_json
87
+ end
88
+
89
+ # GET /api/notes.html
90
+ # ?labels=k1:v1,k2:v2,...
91
+ # ?exclude_labels=k1:v1,k2:v2,...
92
+ get '/api/notes.html' do
93
+ notes = load_notes(params)
94
+
95
+ html = %[<ul class="gitnoted">]
96
+ notes.each do |note|
97
+ body = render_note(note)
98
+ html << %[<li class="gitnoted-note">]
99
+ html << %[<div class="gitnoted-body">#{body}</div>]
100
+ html << %[<ul class="gitnoted-labels">]
101
+ note.labels.each do |label|
102
+ html << %[<li class="gitnoted-label">#{html_escape(label)}</li>]
103
+ end
104
+ html << %[</ul>]
105
+ html << %[</li>]
106
+ end
107
+ html << %[</ul>]
108
+
109
+ content_type "text/html"
110
+ return html
111
+ end
112
+
113
+ # GET /api/labels
114
+ # ?prefix=k1:
115
+ # ?used_with=k1:v1
116
+ get '/api/labels' do
117
+ labels = load_labels(params)
118
+
119
+ response = {
120
+ labels: labels
121
+ }
122
+
123
+ content_type "application/json"
124
+ return response.to_json
125
+ end
126
+
127
+ # force update
128
+ get '/api/github_hook' do
129
+ @repository.update!
130
+
131
+ response = { }
132
+
133
+ content_type "application/json"
134
+ return response.to_json
135
+ end
136
+
137
+ get '/favicon.ico' do
138
+ content_type "image/png"
139
+ return TRANSPARENT_1PX_PNG
140
+ end
141
+
142
+ def read_note(note)
143
+ @repository.read(note)
144
+ end
145
+
146
+ def render_note(note)
147
+ @renderer.call(read_note(note))
148
+ end
149
+
150
+ def load_notes(params)
151
+ label_names = (params[:labels] || '').split(",")
152
+ exclude_label_names = (params[:exclude_labels] || '').split(",")
153
+ @repository.search_notes(labels: label_names, exclude_labels: exclude_label_names)
154
+ end
155
+
156
+ def load_labels(params)
157
+ used_with_label_names = (params[:used_with] || '').split(',')
158
+ prefix = params[:prefix]
159
+ prefix = nil if prefix == ''
160
+ @repository.search_labels(prefix: prefix, used_with: used_with_label_names)
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,60 @@
1
+ ul.gitnoted {
2
+ list-style-type: none;
3
+ list-style-position: outside;
4
+ margin: 0;
5
+ padding: 0;
6
+ }
7
+
8
+ ul.gitnoted > li.gitnoted-note {
9
+ display: block;
10
+ margin: 2em 0;
11
+ padding: 0.8em 1em 0.5em 1em;
12
+ border: 1px solid #ddd;
13
+ background-color: #fff;
14
+ }
15
+
16
+ ul.gitnoted > li.gitnoted-note > div.gitnoted-body
17
+ h1 {
18
+ font-size: 120%;
19
+ }
20
+
21
+ ul.gitnoted > li.gitnoted-note > div.gitnoted-body
22
+ h2 {
23
+ font-size: 120%;
24
+ }
25
+
26
+ ul.gitnoted > li.gitnoted-note > div.gitnoted-body
27
+ h3 {
28
+ font-size: 100%;
29
+ }
30
+
31
+ ul.gitnoted > li.gitnoted-note > div.gitnoted-body
32
+ h4 {
33
+ font-size: 100%;
34
+ }
35
+
36
+ ul.gitnoted > li.gitnoted-note > ul.gitnoted-labels {
37
+ list-style-type: none;
38
+ list-style-position: outside;
39
+ text-align: right;
40
+ font-size: 70%;
41
+ }
42
+
43
+ ul.gitnoted > li.gitnoted-note > ul.gitnoted-labels > li.gitnoted-label {
44
+ display: inline-block;
45
+ margin-left: 1.3em;
46
+ margin-right: -1em;
47
+ padding: 0 0.5em;
48
+ color: #fff;
49
+ background-color: #bbb;
50
+ border-radius: 0.8em;
51
+ }
52
+
53
+ ul.gitnoted > li.gitnoted-note > ul.gitnoted-labels .gitnoted-label-button:hover {
54
+ background-color: #888;
55
+ }
56
+
57
+ ul.gitnoted > li.gitnoted-note code {
58
+ white-space: pre;
59
+ }
60
+
@@ -0,0 +1,54 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title>GitNotes setup completed</title>
5
+ </head>
6
+ <body>
7
+ <h1>GitNotes setup completed</h1>
8
+ <section id="api_notes_html">
9
+ <form action="/api/notes.html":>
10
+ <h2>GET /api/notes.html</h2>
11
+ <p>Search and render notes.</p>
12
+ <div>
13
+ <label for="labels">Labels (separated by comma ","):</label><br />
14
+ <input type="text" name="labels" id="labels" />
15
+ </div>
16
+ <div>
17
+ <label for="exclude_labels">Exclude labels (separated by comma ","):</label><br />
18
+ <input type="text" name="exclude_labels" id="exclude_labels" />
19
+ </div>
20
+ <input type="submit" value="Go">
21
+ </form>
22
+ </section>
23
+ <section id="api_notes">
24
+ <form action="/api/notes":>
25
+ <h2>GET /api/notes</h2>
26
+ <p>Search notes.</p>
27
+ <div>
28
+ <label for="labels">Labels (separated by comma ","):</label><br />
29
+ <input type="text" name="labels" id="labels" />
30
+ </div>
31
+ <div>
32
+ <label for="exclude_labels">Exclude labels (separated by comma ","):</label><br />
33
+ <input type="text" name="exclude_labels" id="exclude_labels" />
34
+ </div>
35
+ <input type="submit" value="Go">
36
+ </form>
37
+ </section>
38
+ <section id="api_labels">
39
+ <form action="/api/labels":>
40
+ <h2>GET /api/labels</h2>
41
+ <p>Search labels.</p>
42
+ <div>
43
+ <label for="prefix">Label name prefix:</label><br />
44
+ <input type="text" name="prefix" id="prefix" />
45
+ </div>
46
+ <div>
47
+ <label for="used_with">Used with labels (separated by comma ","):</label><br />
48
+ <input type="text" name="used_with" id="used_with" />
49
+ </div>
50
+ <input type="submit" value="Go">
51
+ </form>
52
+ </section>
53
+ </body>
54
+ </html>
@@ -0,0 +1,46 @@
1
+ var gitNotesUrl = new URL(document.currentScript.src)
2
+ gitNotesUrl.pathname = "/api/notes.html"
3
+
4
+ $(document).ready(function() {
5
+ function injectExpandScript(e, parentLabels) {
6
+ e.querySelectorAll('.gitnoted-label').forEach(function (e) {
7
+ var url = new URL(gitNotesUrl)
8
+ url.searchParams.set('labels', e.innerText)
9
+ url.searchParams.set('exclude_labels', parentLabels)
10
+ var nextParentLabels = `${parentLabels},${e.innerText}`
11
+ e.classList.add('gitnoted-label-button')
12
+ e.onclick = function() {
13
+ console.log(`exclude_labels: ${nextParentLabels}`)
14
+ fetch(url, {mode: 'cors'}).then(function (r) {
15
+ return r.text()
16
+ }).then(function (t) {
17
+ var template = document.createElement('template')
18
+ template.innerHTML = t
19
+ var note = template.content.firstChild
20
+ injectExpandScript(note, nextParentLabels)
21
+ e.parentNode.parentNode.appendChild(note, e)
22
+ e.classList.remove('gitnoted-label-button')
23
+ e.onclick = null
24
+ })
25
+ }
26
+ })
27
+ }
28
+
29
+ document.querySelectorAll('div.gitnoted').forEach(function (e) {
30
+ var url = new URL(gitNotesUrl)
31
+ if (e.dataset.labels) {
32
+ url.searchParams.set('labels', e.dataset.labels)
33
+ }
34
+ fetch(url, {mode: 'cors'}).then(function (r) {
35
+ return r.text()
36
+ }).then(function (t) {
37
+ var template = document.createElement('template')
38
+ template.innerHTML = t
39
+ var note = template.content.firstChild
40
+ if (e.dataset.labels) {
41
+ injectExpandScript(note, e.dataset.labels)
42
+ }
43
+ e.parentNode.replaceChild(note, e)
44
+ })
45
+ })
46
+ })
@@ -0,0 +1,148 @@
1
+ require 'rugged'
2
+ require 'concurrent'
3
+ require 'pathname'
4
+
5
+ module GitNoted
6
+ class Repository
7
+ class Note
8
+ def initialize(name, path, labels)
9
+ @name = name
10
+ @path = path
11
+ @labels = labels
12
+ end
13
+
14
+ attr_reader :name, :path, :labels
15
+ end
16
+
17
+ def initialize(remote_url, local_path, username: nil, password: nil, logger: Logger.new(STDERR))
18
+ @local_path = Pathname.new(local_path).cleanpath
19
+ @remote_url = remote_url
20
+ if username
21
+ @credentials = Rugged::Credentials::UserPassword.new(username: username, password: password)
22
+ else
23
+ @credentials = nil
24
+ end
25
+ @logger = logger
26
+
27
+ # initial update
28
+ update! || index!
29
+ end
30
+
31
+ def update!
32
+ begin
33
+ @repo ||= Rugged::Repository.init_at(@local_path.to_s)
34
+ updated = fetch(@repo)
35
+ return false unless updated
36
+ rescue => e
37
+ # fall back to clone.
38
+ @logger.warn "Failed to update repository incrementally. Falling back to clone: #{e}"
39
+ end
40
+ @repo = clone
41
+ index!
42
+ return true
43
+ end
44
+
45
+ def read(note)
46
+ File.read(note.path).sub(/^:label:(.*)$\n/, '')
47
+ end
48
+
49
+ def schedule_update!(interval)
50
+ Concurrent::ScheduledTask.execute(interval) do
51
+ begin
52
+ update!
53
+ rescue => e
54
+ @logger.error "Failed to update repository: #{e}"
55
+ e.backtrace.each do |bt|
56
+ @logger.error " #{bt}"
57
+ end
58
+ ensure
59
+ schedule_update!(interval)
60
+ end
61
+ end
62
+ end
63
+
64
+ def search_notes(labels: nil, exclude_labels: nil)
65
+ labels ||= []
66
+ exclude_labels ||= []
67
+
68
+ @notes.select do |note|
69
+ labels.all? {|t| note.labels.include?(t) } &&
70
+ !exclude_labels.any? {|t| note.labels.include?(t) }
71
+ end
72
+ end
73
+
74
+ def search_labels(prefix: nil, used_with: nil)
75
+ match = {}
76
+ @notes.each do |note|
77
+ if used_with.nil? || used_with.all? {|t| note.labels.include?(t) }
78
+ if prefix.nil?
79
+ matching = note.labels
80
+ else
81
+ matching = note.labels.select {|label| label.start_with?(prefix) }
82
+ end
83
+ matching.each {|label| match[label] = true }
84
+ end
85
+ end
86
+ match.keys.sort
87
+ end
88
+
89
+ private
90
+
91
+ def clone
92
+ logger_timer "Cloning remote repository." do
93
+ tmp_path = "#{@local_path}.tmp"
94
+ FileUtils.rm_rf tmp_path
95
+ Rugged::Repository.clone_at(@remote_url, tmp_path, credentials: @credentials)
96
+ FileUtils.rm_rf @local_path
97
+ FileUtils.mv tmp_path, @local_path
98
+ Rugged::Repository.init_at(@local_path.to_s)
99
+ end
100
+ end
101
+
102
+ def fetch(repo)
103
+ logger_timer "Fetching remote repository." do
104
+ remote = repo.remotes["origin"]
105
+ unless remote
106
+ raise "Remote repository is not fetched yet."
107
+ end
108
+ fetched = remote.fetch
109
+ return fetched[:total_objects] > 0
110
+ end
111
+ end
112
+
113
+ def index!
114
+ logger_timer "Updating label index." do
115
+ files = Dir["#{@local_path}/**/*.md"]
116
+ @notes = files.map do |path|
117
+ name = Pathname.new(path).relative_path_from(Pathname.new(@local_path)).sub(/\.md$/, '')
118
+ parse_note(name, path, File.read(path))
119
+ end
120
+ end
121
+ end
122
+
123
+ LABEL_KEY_CHARSET = /[a-zA-Z0-9_\-\.\/]/
124
+ LABEL_VALUE_CHARSET = /[a-zA-Z0-9_\-\.\/]/
125
+
126
+ def parse_note(name, path, text)
127
+ m = /^:label:(.*)$/.match(text)
128
+ if m
129
+ labels = m[1].scan(/[a-zA-Z0-9_\-\.\/]+:[a-zA-Z0-9_\-\.\/]+/)
130
+ else
131
+ labels = []
132
+ end
133
+
134
+ Note.new(name, path, labels)
135
+ end
136
+
137
+ def logger_timer(start_message, end_message = "%.2f seconds.")
138
+ @logger.info start_message
139
+ start_time = Time.now
140
+ begin
141
+ return yield
142
+ ensure
143
+ finish_time = Time.now
144
+ @logger.info end_message % (finish_time - start_time)
145
+ end
146
+ end
147
+ end
148
+ end
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gitnoted
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sadayuki Furuhashi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-03-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sinatra
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: puma
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.7'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rack-cors
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.4'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: redcarpet
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.4'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rugged
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.25'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.25'
83
+ - !ruby/object:Gem::Dependency
84
+ name: concurrent-ruby
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 1.0.5
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 1.0.5
97
+ - !ruby/object:Gem::Dependency
98
+ name: sigdump
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 0.2.4
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 0.2.4
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: 0.9.2
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: 0.9.2
125
+ description: Simple document server that works with Github Wiki or Gollum to serve
126
+ foot notes for external websites just by adding a small script tag
127
+ email:
128
+ - frsyuki@gmail.com
129
+ executables:
130
+ - gitnoted
131
+ extensions: []
132
+ extra_rdoc_files: []
133
+ files:
134
+ - Gemfile
135
+ - Gemfile.lock
136
+ - README.md
137
+ - Rakefile
138
+ - bin/gitnoted
139
+ - gitnoted.gemspec
140
+ - lib/git_noted.rb
141
+ - lib/git_noted/application.rb
142
+ - lib/git_noted/public/css/gitnoted.css
143
+ - lib/git_noted/public/index.html
144
+ - lib/git_noted/public/js/gitnoted.js
145
+ - lib/git_noted/repository.rb
146
+ homepage: https://github.com/frsyuki/gitnoted
147
+ licenses:
148
+ - MIT
149
+ metadata: {}
150
+ post_install_message:
151
+ rdoc_options: []
152
+ require_paths:
153
+ - lib
154
+ required_ruby_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ required_rubygems_version: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: '0'
164
+ requirements: []
165
+ rubyforge_project:
166
+ rubygems_version: 2.6.8
167
+ signing_key:
168
+ specification_version: 4
169
+ summary: Simple document server that works with Github Wiki or Gollum to serve foot
170
+ notes for external websites just by adding a small script tag
171
+ test_files: []