neruda 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +14 -0
- data/README.org +56 -0
- data/TODO.org +12 -0
- data/bin/pablo +151 -0
- data/docs/Rakefile.example +4 -0
- data/docs/config.yml.example +17 -0
- data/lib/assets/chapter.slim +14 -0
- data/lib/assets/index.slim +13 -0
- data/lib/assets/layout.slim +17 -0
- data/lib/assets/style.css +199 -0
- data/lib/neruda/chapter.rb +26 -0
- data/lib/neruda/url.rb +14 -0
- data/lib/neruda.rb +96 -0
- data/lib/tasks/capistrano/chapters.rake +49 -0
- data/lib/tasks/capistrano/sinatra.rake +16 -0
- data/lib/tasks/chapters.rake +135 -0
- data/lib/tasks/sinatra.rake +23 -0
- metadata +117 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0f5fb5997c152378f937f4b226182001790d4c35
|
4
|
+
data.tar.gz: 5fd12f03ac8acdab7a6f26e03226a0dba2a39ee6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dc0c38d5bfce0fd5c82e39fe0aedc5596a15e4e38a0d2b0fc400959708fc23b22f4feb5fa96691a0059e034791529f4ff927e21dde2559e1a82ca65daf2b2fd5
|
7
|
+
data.tar.gz: 81112c53025ca25ebcd6a64927455556948c486d0a05d624d1d8c502a54174dc7f5d70d8e0badf7fa3ffaf64eab67c3f385de3cd85d858fe1b8b46fc9dec467e
|
data/LICENSE
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
2
|
+
Version 2, December 2004
|
3
|
+
|
4
|
+
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
5
|
+
|
6
|
+
Everyone is permitted to copy and distribute verbatim or modified
|
7
|
+
copies of this license document, and changing it is allowed as long
|
8
|
+
as the name is changed.
|
9
|
+
|
10
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
11
|
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
12
|
+
|
13
|
+
0. You just DO WHAT THE FUCK YOU WANT TO.
|
14
|
+
|
data/README.org
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
#+title: Neruda
|
2
|
+
|
3
|
+
*Neruda* is a little [[http://sinatrarb.com][Sinatra]] app, which aims to offer a quick access for
|
4
|
+
writers to web publication. It is named in memory of Pablo Neruda.
|
5
|
+
|
6
|
+
* Install
|
7
|
+
|
8
|
+
These installation instructions use [[https://rvm.io][rvm]] as ruby provider. If you want to
|
9
|
+
use rbenv or your system ruby, please refer to their specific
|
10
|
+
documentation to know how to install ruby 2.4 and [[https://bundler.io/][bundler]].
|
11
|
+
|
12
|
+
First we need to upgrade rvm (always do that from time to time):
|
13
|
+
|
14
|
+
#+begin_src shell
|
15
|
+
$ rvm get latest
|
16
|
+
$ rvm install ruby-2.4.1
|
17
|
+
#+end_src
|
18
|
+
|
19
|
+
Then the installation itself:
|
20
|
+
|
21
|
+
#+begin_src shell
|
22
|
+
$ mkdir mysite
|
23
|
+
$ rvm use ruby-2.4.1
|
24
|
+
$ rvm gemset create neruda
|
25
|
+
$ rvm gemset use neruda
|
26
|
+
$ gem install neruda
|
27
|
+
#+end_src
|
28
|
+
|
29
|
+
Finally, you need to configure it to your needs.
|
30
|
+
|
31
|
+
#+begin_src shell
|
32
|
+
$ pablo init
|
33
|
+
#+end_src
|
34
|
+
|
35
|
+
* Running it
|
36
|
+
|
37
|
+
For testing it, you should just run:
|
38
|
+
|
39
|
+
#+begin_src shell
|
40
|
+
$ pablo start
|
41
|
+
#+end_src
|
42
|
+
|
43
|
+
For production use, just pass the right environnement variable:
|
44
|
+
|
45
|
+
#+begin_src shell
|
46
|
+
$ APP_ENV=production pablo start
|
47
|
+
#+end_src
|
48
|
+
|
49
|
+
To stop it, just enter =ctrl+C= if you are in development mode or enter
|
50
|
+
=pablo stop= in production mode.
|
51
|
+
|
52
|
+
You can now start your first chapter: =pablo new=
|
53
|
+
|
54
|
+
* Known issues
|
55
|
+
|
56
|
+
See [[TODO.org][TODO.org]]
|
data/TODO.org
ADDED
data/bin/pablo
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
def help
|
7
|
+
unless File.exist? 'config/config.yml'
|
8
|
+
STDERR.puts 'Please init your website: pablo init'
|
9
|
+
exit 1
|
10
|
+
end
|
11
|
+
|
12
|
+
options = ['start', 'stop', 'new']
|
13
|
+
options << 'capify' if File.exist? 'Capfile'
|
14
|
+
|
15
|
+
STDERR.puts "pablo [ #{options.join(' | ')} ]"
|
16
|
+
exit 1
|
17
|
+
end
|
18
|
+
|
19
|
+
def capify
|
20
|
+
unless File.exist? 'Capfile'
|
21
|
+
STDERR.puts 'Capfile does not exist. Aborting'
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
|
25
|
+
capfile = File.new('Capfile', 'a')
|
26
|
+
capfile.write <<EOF
|
27
|
+
neruda_spec = Gem::Specification.find_by_name 'neruda'
|
28
|
+
Dir.glob("\#{neruda_spec.gem_dir}/lib/tasks/*.rake").each { |r| import r }
|
29
|
+
Dir.glob("\#{neruda_spec.gem_dir}/lib/tasks/capistrano/*.rake").each { |r| import r }
|
30
|
+
EOF
|
31
|
+
end
|
32
|
+
|
33
|
+
def _init_path
|
34
|
+
FileUtils.mkdir_p 'config'
|
35
|
+
FileUtils.mkdir_p 'public'
|
36
|
+
FileUtils.mkdir_p 'tmp/pids'
|
37
|
+
FileUtils.mkdir_p 'private/orgs'
|
38
|
+
FileUtils.mkdir_p 'private/epubs'
|
39
|
+
end
|
40
|
+
|
41
|
+
def _init_config
|
42
|
+
config = {}
|
43
|
+
print 'Name of your website: '
|
44
|
+
config['title'] = STDIN.gets.strip
|
45
|
+
print 'Your author name: '
|
46
|
+
config['author'] = STDIN.gets.strip
|
47
|
+
print 'Main language of your website [en_US]: '
|
48
|
+
lang = STDIN.gets.strip
|
49
|
+
lang = 'en_US' if lang == ''
|
50
|
+
config['lang'] = lang
|
51
|
+
|
52
|
+
main_title = config['title'].tr(' ', '_').downcase.gsub(/[^a-z0-9_]/, '')
|
53
|
+
print "Filename of the main generated epub [#{main_title}]: "
|
54
|
+
epub = STDIN.gets.strip
|
55
|
+
if epub == ''
|
56
|
+
epub = main_title
|
57
|
+
else
|
58
|
+
epub = File.basename(epub, '.epub')
|
59
|
+
end
|
60
|
+
config['book_filename'] = epub
|
61
|
+
config['chapters'] = []
|
62
|
+
|
63
|
+
IO.write 'config/config.yml', config.to_yaml
|
64
|
+
end
|
65
|
+
|
66
|
+
def _init_assets
|
67
|
+
unless Dir.exist? 'views'
|
68
|
+
FileUtils.cp_r File.join(__dir__, '../lib/assets'), 'views'
|
69
|
+
end
|
70
|
+
|
71
|
+
return if File.exist? 'public/style.css'
|
72
|
+
FileUtils.mv 'views/style.css', 'public/style.css'
|
73
|
+
end
|
74
|
+
|
75
|
+
def _init_rackup
|
76
|
+
unless File.exist? 'Rakefile'
|
77
|
+
FileUtils.copy File.join(__dir__, '../docs/Rakefile.example'), 'Rakefile'
|
78
|
+
end
|
79
|
+
|
80
|
+
return if File.exist? 'config.ru'
|
81
|
+
rackup_conf = <<EOF
|
82
|
+
# frozen_string_literal: true
|
83
|
+
|
84
|
+
require 'neruda'
|
85
|
+
run Neruda::App
|
86
|
+
EOF
|
87
|
+
IO.write('config.ru', rackup_conf)
|
88
|
+
end
|
89
|
+
|
90
|
+
def init
|
91
|
+
_init_path
|
92
|
+
_init_config
|
93
|
+
_init_assets
|
94
|
+
_init_rackup
|
95
|
+
end
|
96
|
+
|
97
|
+
def create_new(title)
|
98
|
+
if title == ''
|
99
|
+
filename = 'new'
|
100
|
+
else
|
101
|
+
filename = title.tr(' ', '_').downcase.gsub(/[^a-z0-9_]/, '')
|
102
|
+
end
|
103
|
+
|
104
|
+
filename = "private/orgs/#{filename}.org"
|
105
|
+
|
106
|
+
if Dir.exist? 'private/orgs'
|
107
|
+
config = YAML.load_file('config/config.yml')
|
108
|
+
IO.write filename, <<EOF
|
109
|
+
#+title: #{title}
|
110
|
+
#+date: <#{Date.today.strftime('%Y-%m-%d %a.')}>
|
111
|
+
#+author: #{config['author']}
|
112
|
+
|
113
|
+
|
114
|
+
EOF
|
115
|
+
end
|
116
|
+
|
117
|
+
editor = ENV['EDITOR'] || 'emacs'
|
118
|
+
exec editor, filename
|
119
|
+
end
|
120
|
+
|
121
|
+
if ARGV[0] == 'init'
|
122
|
+
init
|
123
|
+
|
124
|
+
elsif ARGV[0] == 'capify'
|
125
|
+
capify
|
126
|
+
|
127
|
+
elsif ['start', 'run'].include?(ARGV[0])
|
128
|
+
puts 'You can also start neruda with the following command:'
|
129
|
+
puts 'rake sinatra:start'
|
130
|
+
|
131
|
+
exec 'rake', 'sinatra:start'
|
132
|
+
|
133
|
+
elsif ARGV[0] == 'stop'
|
134
|
+
puts 'You can also stop neruda with the following command:'
|
135
|
+
puts 'rake sinatra:stop'
|
136
|
+
|
137
|
+
exec 'rake', 'sinatra:stop'
|
138
|
+
|
139
|
+
elsif ARGV[0] == 'new'
|
140
|
+
if ARGV[1].nil?
|
141
|
+
print 'Title: '
|
142
|
+
title = STDIN.gets.strip
|
143
|
+
else
|
144
|
+
title = ARGV[1]
|
145
|
+
end
|
146
|
+
|
147
|
+
create_new title
|
148
|
+
|
149
|
+
else
|
150
|
+
help
|
151
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
---
|
2
|
+
|
3
|
+
title: My Awesome Title
|
4
|
+
author: Me Myself
|
5
|
+
# license: © Me
|
6
|
+
# lang: en_US
|
7
|
+
|
8
|
+
book_filename: my_awesome_title
|
9
|
+
|
10
|
+
## If we are behind a subfolder reverse proxy
|
11
|
+
# base_path: /subfolder
|
12
|
+
|
13
|
+
chapters:
|
14
|
+
- in_the
|
15
|
+
- order
|
16
|
+
- we_wish
|
17
|
+
- they_appear
|
@@ -0,0 +1,14 @@
|
|
1
|
+
.chapter
|
2
|
+
h1.chapter-title = title
|
3
|
+
|
4
|
+
.chapter-meta
|
5
|
+
p.chapter-info
|
6
|
+
span.chapter-date = meta_data 'date'
|
7
|
+
br
|
8
|
+
span.chapter-epub
|
9
|
+
a href=proxy_url("/epub/#{@slug}") epub version
|
10
|
+
' -
|
11
|
+
span.chapter-permalink
|
12
|
+
a href=proxy_url("/chapter/#{@slug}") permalink
|
13
|
+
|
14
|
+
== @content.to_html
|
@@ -0,0 +1,17 @@
|
|
1
|
+
doctype html
|
2
|
+
html
|
3
|
+
head
|
4
|
+
title #{title}
|
5
|
+
meta name='author' content=author
|
6
|
+
link rel='stylesheet' href=proxy_url('/style.css') type='text/css'
|
7
|
+
|
8
|
+
body
|
9
|
+
#content
|
10
|
+
== yield
|
11
|
+
|
12
|
+
footer#footer.status
|
13
|
+
p © #{author}
|
14
|
+
p#gotop
|
15
|
+
a href=proxy_url('/') Accueil
|
16
|
+
' ·
|
17
|
+
a href='#top' Aller en haut
|
@@ -0,0 +1,199 @@
|
|
1
|
+
body {
|
2
|
+
font-family: serif;
|
3
|
+
font-size: 20pt;
|
4
|
+
}
|
5
|
+
|
6
|
+
a:hover {
|
7
|
+
text-decoration: none;
|
8
|
+
}
|
9
|
+
|
10
|
+
dt {
|
11
|
+
font-weight: bold;
|
12
|
+
}
|
13
|
+
dt:after {
|
14
|
+
font-weight: bold;
|
15
|
+
content: ":";
|
16
|
+
}
|
17
|
+
|
18
|
+
figure {
|
19
|
+
text-align: center;
|
20
|
+
}
|
21
|
+
|
22
|
+
kbd {
|
23
|
+
border-width: .3em;
|
24
|
+
border-style: solid;
|
25
|
+
}
|
26
|
+
|
27
|
+
#content,
|
28
|
+
#footer,
|
29
|
+
.status {
|
30
|
+
width: 968px;
|
31
|
+
margin: 1em auto;
|
32
|
+
}
|
33
|
+
|
34
|
+
#content img {
|
35
|
+
max-width: 100%;
|
36
|
+
}
|
37
|
+
|
38
|
+
|
39
|
+
/**
|
40
|
+
* Content
|
41
|
+
*/
|
42
|
+
|
43
|
+
#content {
|
44
|
+
padding: 1em;
|
45
|
+
line-height: 1.5em;
|
46
|
+
}
|
47
|
+
|
48
|
+
#content h1.chapter-title {
|
49
|
+
margin: 0;
|
50
|
+
line-height: 1.5em;
|
51
|
+
}
|
52
|
+
|
53
|
+
.chapter-meta {
|
54
|
+
font-size: smaller;
|
55
|
+
padding: 0 .4em;
|
56
|
+
border-left-style: solid;
|
57
|
+
border-left-width: .4em;
|
58
|
+
border-radius: .4em;
|
59
|
+
}
|
60
|
+
.chapter-meta p {
|
61
|
+
margin: .3em 0;
|
62
|
+
}
|
63
|
+
|
64
|
+
.src {
|
65
|
+
font-family: Inconsolata, monospace;
|
66
|
+
font-size: 1em;
|
67
|
+
display:block;
|
68
|
+
padding-left:.4em;
|
69
|
+
margin:1em 1.5em;
|
70
|
+
line-height:1.5em;
|
71
|
+
border-left-width: .4em;
|
72
|
+
border-left-style: solid;
|
73
|
+
border-radius: .4em;
|
74
|
+
overflow-x: auto;
|
75
|
+
}
|
76
|
+
|
77
|
+
blockquote {
|
78
|
+
display: block;
|
79
|
+
padding: 0 1.5em;
|
80
|
+
font-style: italic;
|
81
|
+
margin: 1em 0;
|
82
|
+
}
|
83
|
+
blockquote::after {
|
84
|
+
display: table;
|
85
|
+
content: "";
|
86
|
+
clear: both;
|
87
|
+
}
|
88
|
+
blockquote>*:first-child::before {
|
89
|
+
content: "« ";
|
90
|
+
font-size: 3em;
|
91
|
+
}
|
92
|
+
blockquote>*:last-child::after {
|
93
|
+
content: " »";
|
94
|
+
font-size: 2em;
|
95
|
+
}
|
96
|
+
|
97
|
+
div.footnotes,
|
98
|
+
.footdef {
|
99
|
+
font-size: smaller;
|
100
|
+
}
|
101
|
+
div.footnotes::before,
|
102
|
+
.footdef::before {
|
103
|
+
content: "---";
|
104
|
+
display: block;
|
105
|
+
}
|
106
|
+
div.footnotes,
|
107
|
+
.footdef sup {
|
108
|
+
font-size: smaller;
|
109
|
+
}
|
110
|
+
|
111
|
+
#content .chapter::after {
|
112
|
+
content: "❦";
|
113
|
+
font-size: xx-large;
|
114
|
+
display: block;
|
115
|
+
text-align: center;
|
116
|
+
}
|
117
|
+
|
118
|
+
/**
|
119
|
+
* Footer
|
120
|
+
*/
|
121
|
+
#gotop {
|
122
|
+
position: fixed;
|
123
|
+
bottom: 0em;
|
124
|
+
right: 1em;
|
125
|
+
font-size: smaller;
|
126
|
+
}
|
127
|
+
|
128
|
+
#footer {
|
129
|
+
clear: both;
|
130
|
+
padding-top: 0;
|
131
|
+
font-size: smaller;
|
132
|
+
margin-bottom: .5em;
|
133
|
+
}
|
134
|
+
|
135
|
+
/**
|
136
|
+
* Responsive...
|
137
|
+
*/
|
138
|
+
@media screen and (max-width: 968px) {
|
139
|
+
#content, .status {
|
140
|
+
width: 90%;
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
|
145
|
+
/**
|
146
|
+
* Colors
|
147
|
+
* We used the Dracula color theme
|
148
|
+
* https://draculatheme.com/
|
149
|
+
*/
|
150
|
+
|
151
|
+
body {
|
152
|
+
background: #282a36;
|
153
|
+
color: #f8f8f2;
|
154
|
+
}
|
155
|
+
|
156
|
+
h1 {
|
157
|
+
color: #ffb86c;
|
158
|
+
}
|
159
|
+
|
160
|
+
h2 {
|
161
|
+
color: #bd93f9;
|
162
|
+
}
|
163
|
+
|
164
|
+
a:link, a:visited {
|
165
|
+
color: #ff79c6;
|
166
|
+
}
|
167
|
+
|
168
|
+
kbd {
|
169
|
+
border-color: #f8f8f2;
|
170
|
+
}
|
171
|
+
|
172
|
+
code {
|
173
|
+
background: #373844;
|
174
|
+
color: #e2e2dc;
|
175
|
+
}
|
176
|
+
|
177
|
+
mark {
|
178
|
+
background: #f1fa8c;
|
179
|
+
}
|
180
|
+
|
181
|
+
.chapter-meta {
|
182
|
+
border-left-color: #373844;
|
183
|
+
background-color: #464752;
|
184
|
+
}
|
185
|
+
|
186
|
+
.src {
|
187
|
+
border-left-color: #bd93f9;
|
188
|
+
}
|
189
|
+
|
190
|
+
blockquote>*:first-child::before {
|
191
|
+
color: #50fa7b;
|
192
|
+
}
|
193
|
+
blockquote>*:last-child::after {
|
194
|
+
color: #50fa7b;
|
195
|
+
}
|
196
|
+
|
197
|
+
.comment-content {
|
198
|
+
border-left-color: #bd93f9;
|
199
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Various method to handle org conversion and metadata access
|
4
|
+
module Neruda::Chapter
|
5
|
+
def meta_data(key)
|
6
|
+
return nil if @content.nil?
|
7
|
+
value = @content.in_buffer_settings[key.upcase]
|
8
|
+
return value if value.nil? || !key.casecmp('date').zero?
|
9
|
+
time_for(value).strftime('%A %d %B %Y')
|
10
|
+
end
|
11
|
+
|
12
|
+
def title
|
13
|
+
return Neruda::CONFIG['title'] if @content.nil?
|
14
|
+
return @title unless @title.nil?
|
15
|
+
# We use an instance variable to avoid Orgmode::Parser to render
|
16
|
+
# title with the rest of the file
|
17
|
+
# Thus we are removing title from the buffer_settings
|
18
|
+
@title = @content.in_buffer_settings.delete('TITLE')
|
19
|
+
return @title unless @title.nil?
|
20
|
+
@title = Neruda::CONFIG['title']
|
21
|
+
end
|
22
|
+
|
23
|
+
def author
|
24
|
+
meta_data('author') || Neruda::CONFIG['author']
|
25
|
+
end
|
26
|
+
end
|
data/lib/neruda/url.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
# Various method to ease url handling
|
6
|
+
module Neruda::Url
|
7
|
+
def proxy_url(url)
|
8
|
+
wanted_uri = url(url)
|
9
|
+
return wanted_uri if Neruda::CONFIG['base_path'].nil?
|
10
|
+
uri_data = URI.parse(wanted_uri)
|
11
|
+
uri_data.path = Neruda::CONFIG['base_path'] + uri_data.path
|
12
|
+
uri_data.to_s
|
13
|
+
end
|
14
|
+
end
|
data/lib/neruda.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'yaml'
|
5
|
+
require 'org-ruby'
|
6
|
+
require 'sinatra/base'
|
7
|
+
|
8
|
+
# Namespacing
|
9
|
+
module Neruda
|
10
|
+
CONFIG = YAML.load_file(File.join('config', 'config.yml')).freeze
|
11
|
+
end
|
12
|
+
|
13
|
+
# The following must be required after to allow compact name style
|
14
|
+
require 'neruda/url'
|
15
|
+
require 'neruda/chapter'
|
16
|
+
|
17
|
+
# Main Sinatra application
|
18
|
+
class Neruda::App < Sinatra::Base
|
19
|
+
configure :production, :development do
|
20
|
+
# When used as a Gem, we lose the correct path.
|
21
|
+
set :root, Dir.pwd
|
22
|
+
enable :logging
|
23
|
+
disable :method_override, :sessions
|
24
|
+
mime_type :epub, 'application/epub+zip'
|
25
|
+
end
|
26
|
+
|
27
|
+
include Neruda::Url
|
28
|
+
include Neruda::Chapter
|
29
|
+
|
30
|
+
def find_slug
|
31
|
+
@slug = params[:chapter]
|
32
|
+
halt 404 if @slug.nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
def find_file(kind = 'org')
|
36
|
+
f = File.join('private', "#{kind}s", "#{@slug}.#{kind}")
|
37
|
+
halt 404 unless File.exist? f
|
38
|
+
f
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_chapter
|
42
|
+
@org_file = find_file
|
43
|
+
end
|
44
|
+
|
45
|
+
get '/epub/:chapter' do
|
46
|
+
find_slug
|
47
|
+
if @slug == 'all' && !Neruda::CONFIG['book_filename'].nil?
|
48
|
+
@slug = Neruda::CONFIG['book_filename']
|
49
|
+
end
|
50
|
+
epub_file = find_file('epub')
|
51
|
+
halt 404 unless File.exist? epub_file
|
52
|
+
content_type :epub
|
53
|
+
send_file epub_file, filename: "#{@slug}.epub"
|
54
|
+
end
|
55
|
+
|
56
|
+
get '/chapter/:chapter' do
|
57
|
+
find_slug
|
58
|
+
find_chapter
|
59
|
+
@content = Orgmode::Parser.load @org_file
|
60
|
+
title # Force the early removal of the title
|
61
|
+
slim :chapter
|
62
|
+
end
|
63
|
+
|
64
|
+
unless Neruda::CONFIG['base_path'].nil?
|
65
|
+
# If we are behind a misconfigured subfolder reverse proxy, this one
|
66
|
+
# could be usefull
|
67
|
+
get Neruda::CONFIG['base_path'] do
|
68
|
+
call env.merge('PATH_INFO' => '/')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
get '/' do
|
73
|
+
@slug = 'index'
|
74
|
+
text_content = ''
|
75
|
+
if File.exist? File.join('private', 'orgs', 'index.org')
|
76
|
+
find_chapter
|
77
|
+
@content = Orgmode::Parser.load @org_file
|
78
|
+
text_content = @content.to_html
|
79
|
+
end
|
80
|
+
|
81
|
+
@chapters = []
|
82
|
+
if File.exist? File.join('config', 'chapters.yml')
|
83
|
+
@chapters = YAML.load_file(File.join('config', 'chapters.yml'))
|
84
|
+
end
|
85
|
+
|
86
|
+
slim :index, locals: { text: text_content }
|
87
|
+
end
|
88
|
+
|
89
|
+
error 403 do
|
90
|
+
slim 'h1 Access forbidden'
|
91
|
+
end
|
92
|
+
|
93
|
+
error 404 do
|
94
|
+
slim 'h1 Not Found'
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
namespace :chapters do
|
4
|
+
|
5
|
+
desc 'Upload epub files listed in tmp/epub_to_upload.yml'
|
6
|
+
task :upload_epubs do
|
7
|
+
next unless File.exist?('tmp/epub_to_upload.yml')
|
8
|
+
epub_to_upload = YAML.load_file('tmp/epub_to_upload.yml')
|
9
|
+
next if epub_to_upload.empty?
|
10
|
+
on roles(:app) do
|
11
|
+
epub_to_upload.each do |f|
|
12
|
+
upload! f, "#{shared_path}/#{f}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'Upload the chapters list index'
|
18
|
+
task upload_index: 'chapters:index' do
|
19
|
+
on roles(:app) do
|
20
|
+
upload! 'config/chapters.yml', "#{release_path}/config/chapters.yml"
|
21
|
+
info 'chatpers.yml has been built and uploaded'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'Upload the complete book'
|
26
|
+
task upload_book: ['chapters:make_book', 'chapters:upload_epubs']
|
27
|
+
|
28
|
+
namespace :purge do
|
29
|
+
desc 'Remove remote orphaned epub files'
|
30
|
+
task :remote do
|
31
|
+
neruda_config = YAML.load_file('config/config.yml')
|
32
|
+
final_org = neruda_config['book_filename'] || 'all'
|
33
|
+
on roles(:app) do
|
34
|
+
within release_path do
|
35
|
+
epub_files = capture :ls, '-1', 'private/epubs/*.epub'
|
36
|
+
epub_files.each_line do |filename|
|
37
|
+
filename.delete!("\n")
|
38
|
+
file_radix = File.basename(filename, '.epub')
|
39
|
+
next if file_radix == final_org
|
40
|
+
org_file = "private/orgs/#{file_radix}.org"
|
41
|
+
unless test("[ -e '#{release_path}/#{org_file}' ]")
|
42
|
+
execute :rm, filename
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
namespace :sinatra do
|
2
|
+
namespace :restart do
|
3
|
+
desc 'Restart the remote neruda application'
|
4
|
+
task :remote do
|
5
|
+
on roles(:app) do
|
6
|
+
within release_path do
|
7
|
+
if test("[ -e '#{release_path}/tmp/pids/sinatra.pid' ]")
|
8
|
+
execute :pkill, '-F', 'tmp/pids/sinatra.pid'
|
9
|
+
end
|
10
|
+
execute :bundle, :exec, :rackup, '-E', fetch(:app_env),
|
11
|
+
'-P', 'tmp/pids/sinatra.pid', '-D'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
namespace :chapters do
|
4
|
+
desc 'Identify all org files'
|
5
|
+
task :list_all_orgs do
|
6
|
+
org_to_convert = []
|
7
|
+
File.unlink 'tmp/org_to_convert.yml' if File.exist? 'tmp/org_to_convert.yml'
|
8
|
+
neruda_config = YAML.load_file('config/config.yml')
|
9
|
+
chapters = neruda_config['chapters']
|
10
|
+
next unless chapters.any?
|
11
|
+
chapters.each do |file_radix|
|
12
|
+
next if file_radix == 'index'
|
13
|
+
filename = "private/orgs/#{file_radix}.org"
|
14
|
+
org_to_convert << filename
|
15
|
+
end
|
16
|
+
next if org_to_convert.empty?
|
17
|
+
Dir.mkdir 'tmp' unless Dir.exist? 'tmp'
|
18
|
+
IO.write('tmp/org_to_convert.yml', org_to_convert.to_yaml)
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Identify orphan (without epubs) org files'
|
22
|
+
task list_orphaned_orgs: 'chapters:list_all_orgs' do
|
23
|
+
next unless File.exist? 'tmp/org_to_convert.yml'
|
24
|
+
org_to_convert = []
|
25
|
+
chapters = YAML.load_file('tmp/org_to_convert.yml')
|
26
|
+
next unless chapters.any?
|
27
|
+
chapters.each do |filename|
|
28
|
+
file_radix = File.basename(filename, '.org')
|
29
|
+
epub_file = "private/epubs/#{file_radix}.epub"
|
30
|
+
org_to_convert << filename unless File.exist? epub_file
|
31
|
+
end
|
32
|
+
next if org_to_convert.empty?
|
33
|
+
IO.write('tmp/org_to_convert.yml', org_to_convert.to_yaml)
|
34
|
+
end
|
35
|
+
|
36
|
+
desc 'Convert org files from tmp/org_to_convert.yml to epubs'
|
37
|
+
task :convert_org do
|
38
|
+
next unless File.exist?('tmp/org_to_convert.yml')
|
39
|
+
File.unlink 'tmp/epub_to_upload.yml' if File.exist? 'tmp/epub_to_upload.yml'
|
40
|
+
org_to_convert = YAML.load_file('tmp/org_to_convert.yml')
|
41
|
+
next if org_to_convert.empty?
|
42
|
+
epub_to_upload = []
|
43
|
+
org_to_convert.each do |filename|
|
44
|
+
file_radix = File.basename(filename, '.org')
|
45
|
+
next if file_radix == 'index'
|
46
|
+
epub_file = "private/epubs/#{file_radix}.epub"
|
47
|
+
epub_to_upload << epub_file
|
48
|
+
execute :pandoc, '-S', "-o #{epub_file}", filename
|
49
|
+
end
|
50
|
+
next if epub_to_upload.empty?
|
51
|
+
IO.write('tmp/epub_to_upload.yml', epub_to_upload.to_yaml)
|
52
|
+
end
|
53
|
+
|
54
|
+
desc 'Build missing epubs'
|
55
|
+
task build_epubs: ['chapters:list_orphaned_orgs', 'chapters:convert_org']
|
56
|
+
|
57
|
+
namespace :build_epubs do
|
58
|
+
desc '(re)Build all epub files for each org files'
|
59
|
+
task force_all: ['chapters:list_all_orgs', 'chapters:convert_org']
|
60
|
+
end
|
61
|
+
|
62
|
+
desc 'Remove orphaned (epub without org files) epub files'
|
63
|
+
task :purge do
|
64
|
+
neruda_config = YAML.load_file('config/config.yml')
|
65
|
+
final_org = neruda_config['book_filename'] || 'all'
|
66
|
+
chapters = neruda_config['chapters']
|
67
|
+
Dir.glob('private/orgs/*.org') do |filename|
|
68
|
+
file_radix = File.basename(filename, '.org')
|
69
|
+
unless chapters.include? file_radix
|
70
|
+
STDERR.puts "WARNING: #{filename} exists but #{file_radix} " \
|
71
|
+
'is no more in the chapters list.'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
Dir.glob('private/epubs/*.epub') do |filename|
|
76
|
+
file_radix = File.basename(filename, '.epub')
|
77
|
+
next if file_radix == final_org
|
78
|
+
org_file = "private/orgs/#{file_radix}.org"
|
79
|
+
File.unlink filename unless File.exist? org_file
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
desc 'Create/Update the chapters list index'
|
84
|
+
task :index do
|
85
|
+
require 'org-ruby'
|
86
|
+
neruda_config = YAML.load_file('config/config.yml')
|
87
|
+
next if neruda_config['chapters'].nil?
|
88
|
+
chapters = []
|
89
|
+
neruda_config['chapters'].each do |file_radix|
|
90
|
+
filename = "private/orgs/#{file_radix}.org"
|
91
|
+
next unless File.exist? filename
|
92
|
+
f = Orgmode::Parser.load filename
|
93
|
+
title = f.in_buffer_settings['TITLE']
|
94
|
+
chapters << { slug: file_radix, title: title } unless title.nil?
|
95
|
+
end
|
96
|
+
IO.write('config/chapters.yml', chapters.to_yaml)
|
97
|
+
end
|
98
|
+
|
99
|
+
desc 'Create the org file of the complete book'
|
100
|
+
task prepare_book: 'chapters:index' do
|
101
|
+
chapters = YAML.load_file('config/chapters.yml')
|
102
|
+
next if chapters.nil?
|
103
|
+
|
104
|
+
neruda_config = YAML.load_file('config/config.yml')
|
105
|
+
final_org = neruda_config['book_filename'] || 'all'
|
106
|
+
final_org = "tmp/#{final_org}.org"
|
107
|
+
|
108
|
+
Dir.mkdir 'tmp' unless Dir.exist? 'tmp'
|
109
|
+
File.unlink final_org if File.exist? final_org
|
110
|
+
|
111
|
+
org_file = File.open(final_org, 'a')
|
112
|
+
org_file.write("#+title: #{neruda_config['title']}\n")
|
113
|
+
org_file.write("#+author: #{neruda_config['author']}\n")
|
114
|
+
org_file.write("#+rights: #{neruda_config['license']}\n")
|
115
|
+
org_file.write("#+language: #{neruda_config['lang']}\n\n")
|
116
|
+
|
117
|
+
chapters.each do |c|
|
118
|
+
file_radix = c[:slug]
|
119
|
+
filename = "private/orgs/#{file_radix}.org"
|
120
|
+
next unless File.exist? filename
|
121
|
+
file_content = IO.read(filename)
|
122
|
+
file_content.gsub!(/^#\+date:.*$/mi, '')
|
123
|
+
file_content.gsub!(/^#\+author:.*$/mi, '')
|
124
|
+
file_content.gsub!(/^(\*+)\s+(.*)$/mi, '*\1 \2')
|
125
|
+
file_content.gsub!(/^#\+title:\s?(.*)$/mi, '* \1')
|
126
|
+
|
127
|
+
org_file.write(file_content + "\n")
|
128
|
+
end
|
129
|
+
org_file.close
|
130
|
+
|
131
|
+
IO.write('tmp/org_to_convert.yml', [final_org].to_yaml)
|
132
|
+
end
|
133
|
+
|
134
|
+
task make_book: ['chapters:prepare_book', 'chapters:convert_org']
|
135
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
namespace :sinatra do
|
2
|
+
desc 'Stop the underlaying sinatra application'
|
3
|
+
task :stop do
|
4
|
+
unless File.exist? 'tmp/pids/neruda.pid'
|
5
|
+
STDERR.puts 'No pid file found'
|
6
|
+
exit 1
|
7
|
+
end
|
8
|
+
pid = IO.read('tmp/pids/neruda.pid').strip.to_i
|
9
|
+
Process.kill('TERM', pid)
|
10
|
+
File.unlink 'tmp/pids/neruda.pid'
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Start the underlaying sinatra application'
|
14
|
+
task :start do
|
15
|
+
loc_env = ENV['APP_ENV'] || 'development'
|
16
|
+
cmd = ['rackup', "-E #{loc_env}", '-P', 'tmp/pids/neruda.pid']
|
17
|
+
cmd << '-D' if loc_env == 'production'
|
18
|
+
exec cmd.join(' ')
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Restart local sinatra server'
|
22
|
+
task restart: ['sinatra:stop', 'sinatra:start']
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: neruda
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Étienne Deparis
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-06-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: org-ruby
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.9'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: slim
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: thin
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.7'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.7'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sinatra
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.0'
|
69
|
+
description: Write your org files, we take care of the rest.
|
70
|
+
email: etienne@depar.is
|
71
|
+
executables:
|
72
|
+
- pablo
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- LICENSE
|
77
|
+
- README.org
|
78
|
+
- TODO.org
|
79
|
+
- bin/pablo
|
80
|
+
- docs/Rakefile.example
|
81
|
+
- docs/config.yml.example
|
82
|
+
- lib/assets/chapter.slim
|
83
|
+
- lib/assets/index.slim
|
84
|
+
- lib/assets/layout.slim
|
85
|
+
- lib/assets/style.css
|
86
|
+
- lib/neruda.rb
|
87
|
+
- lib/neruda/chapter.rb
|
88
|
+
- lib/neruda/url.rb
|
89
|
+
- lib/tasks/capistrano/chapters.rake
|
90
|
+
- lib/tasks/capistrano/sinatra.rake
|
91
|
+
- lib/tasks/chapters.rake
|
92
|
+
- lib/tasks/sinatra.rake
|
93
|
+
homepage: https://git.deparis.io/neruda/
|
94
|
+
licenses:
|
95
|
+
- WTFPL
|
96
|
+
metadata: {}
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
requirements: []
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 2.6.12
|
114
|
+
signing_key:
|
115
|
+
specification_version: 4
|
116
|
+
summary: A simplistic way to publish a book online.
|
117
|
+
test_files: []
|