neruda 0.0.1
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 +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: []
|