neruda 0.0.10 → 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.
Files changed (41) hide show
  1. checksums.yaml +5 -5
  2. data/bin/pablo +105 -246
  3. data/lib/neruda/config.rb +94 -0
  4. data/lib/neruda/config/lisp_config.rb +201 -0
  5. data/lib/neruda/config/org-config.el +17 -0
  6. data/lib/neruda/config/ox-neruda.el +88 -0
  7. data/lib/neruda/index.rb +108 -0
  8. data/lib/neruda/index/atom_generator.rb +86 -0
  9. data/lib/neruda/index/org_generator.rb +92 -0
  10. data/lib/neruda/org_file.rb +266 -0
  11. data/lib/neruda/org_file/class_methods.rb +55 -0
  12. data/lib/neruda/org_file/extracter.rb +61 -0
  13. data/lib/neruda/org_file/htmlizer.rb +78 -0
  14. data/lib/neruda/preview.rb +53 -0
  15. data/lib/neruda/templater.rb +111 -0
  16. data/lib/neruda/utils.rb +130 -0
  17. data/lib/neruda/version.rb +5 -0
  18. data/lib/tasks/org.rake +69 -0
  19. data/lib/tasks/site.rake +84 -0
  20. data/lib/tasks/sync.rake +30 -0
  21. data/locales/en.yml +18 -0
  22. data/locales/fr.yml +18 -0
  23. data/themes/default/css/style.css +216 -0
  24. data/themes/default/fonts/Yanone_Kaffeesatz_400.woff +0 -0
  25. data/themes/default/fonts/Yanone_Kaffeesatz_400.woff2 +0 -0
  26. metadata +145 -39
  27. data/README.md +0 -98
  28. data/docs/Rakefile.example +0 -4
  29. data/docs/config.yml.example +0 -17
  30. data/lib/assets/chapter.slim +0 -14
  31. data/lib/assets/index.slim +0 -13
  32. data/lib/assets/layout.slim +0 -17
  33. data/lib/assets/style.css +0 -199
  34. data/lib/neruda.rb +0 -112
  35. data/lib/neruda/chapter.rb +0 -26
  36. data/lib/neruda/url.rb +0 -14
  37. data/lib/tasks/book.rake +0 -60
  38. data/lib/tasks/capistrano/chapters.rake +0 -60
  39. data/lib/tasks/capistrano/sinatra.rake +0 -18
  40. data/lib/tasks/chapters.rake +0 -132
  41. data/lib/tasks/sinatra.rake +0 -36
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neruda
4
+ # Embeds methods responsible for generating an org file for a given
5
+ # index.
6
+ module IndexOrgGenerator
7
+ def to_org(index_name = 'index')
8
+ content = [org_header(index_name).strip]
9
+ last_year = nil
10
+ @index[index_name].each do |article|
11
+ year = article.timekey.slice(0, 4)
12
+ if year != last_year
13
+ content << ''
14
+ content << org_title(year)
15
+ last_year = year
16
+ end
17
+ content << org_entry(article)
18
+ end
19
+ content.join("\n")
20
+ end
21
+ alias_method :to_s, :to_org
22
+
23
+ def write_org(index_name)
24
+ return 0 if @blog_path.nil?
25
+ FileUtils.mkdir_p 'src/tags' unless index_name == 'index'
26
+ src = index_source_path(index_name)
27
+ IO.write(src, to_org(index_name))
28
+ end
29
+
30
+ def write_org_lists
31
+ return 0 if @blog_path.nil?
32
+ domain = Neruda::Config.settings['domain']
33
+ content = [org_header(R18n.t.neruda.index.all_tags)]
34
+ sort_tags_by_name_and_weight.each do |t, tags|
35
+ content << ''
36
+ content << org_title(R18n.t.neruda.index.send(t), 'index-tags')
37
+ tags.each do |k|
38
+ title = @tags_names[k] || k
39
+ link = "[[#{domain}/tags/#{k}.html][#{title}]]"
40
+ content << "- #{link} (#{@index[k].length})"
41
+ end
42
+ end
43
+ FileUtils.mkdir_p 'src/tags'
44
+ src = 'src/tags/index.org'
45
+ IO.write(src, content.join("\n"))
46
+ end
47
+
48
+ private
49
+
50
+ def index_source_path(index_name)
51
+ slug = Neruda::OrgFile.slug index_name
52
+ src = ['src', 'tags', "#{slug}.org"]
53
+ src[1] = @blog_path if slug == 'index'
54
+ src.join('/')
55
+ end
56
+
57
+ def org_header(title = nil)
58
+ if title.nil? || title == 'index'
59
+ title = Neruda::Config.settings['title']
60
+ elsif @tags_names.has_key?(title)
61
+ title = @tags_names[title]
62
+ end
63
+ <<~HEADER
64
+ #+title: #{title}
65
+ #+author: #{Neruda::Config.settings['author']}
66
+ #+language: #{Neruda::Config.settings['lang']}
67
+ HEADER
68
+ end
69
+
70
+ def org_entry(article)
71
+ line = "- *[[..#{article.html_file}][#{article.title}]]*"
72
+ if article.date
73
+ art_date = article.datestring(:full, false)
74
+ published = R18n.t.neruda.index.published_on art_date
75
+ line += " / #{published}"
76
+ end
77
+ line += " \\\\\n #{article.excerpt}" if article.excerpt != ''
78
+ line
79
+ end
80
+
81
+ def org_title(year, html_class = 'index-year')
82
+ year = R18n.t.neruda.index.unsorted if year == '0000'
83
+ <<~ENDPROP
84
+ * #{year}
85
+ :PROPERTIES:
86
+ :HTML_CONTAINER_CLASS: #{html_class}
87
+ :UNNUMBERED: notoc
88
+ :END:
89
+ ENDPROP
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,266 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'fileutils'
5
+ # neruda/config is required by htmlizer
6
+ require 'neruda/org_file/htmlizer'
7
+ require 'neruda/org_file/extracter'
8
+ require 'neruda/org_file/class_methods'
9
+ require 'neruda/index'
10
+
11
+ module Neruda
12
+ # Handles org files.
13
+ #
14
+ # This class is responsible for reading or writing existing or new org
15
+ # files, and formating their content to be used on the generated
16
+ # website.
17
+ class OrgFile
18
+ # @return [String] the title of the current org document, taken from
19
+ # the ~#+title:~ header.
20
+ attr_reader :title
21
+
22
+ # @return [DateTime] the date and time of the current org document,
23
+ # taken from the ~#+date:~ header.
24
+ attr_reader :date
25
+
26
+ # @return [Boolean] wether a time has been extracted from the
27
+ # current org document ~#+date:~ header.
28
+ attr_reader :notime
29
+
30
+ # The author of the current org document, taken from the ~#+author:~
31
+ # header.
32
+ #
33
+ # If the current document doesn't have any authorship information,
34
+ # the one from the ~config.yml~ file will be used instead
35
+ #
36
+ # @return [String] the author name
37
+ attr_reader :author
38
+
39
+ # @return [Array] the keywords list of the current org document,
40
+ # taken from the ~#+keywords:~ header.
41
+ attr_reader :keywords
42
+
43
+ # The locale of the current org document, taken from the
44
+ # ~#+language:~ header.
45
+ #
46
+ # If the current document doesn't have any language information, the
47
+ # one from the ~config.yml~ file will be used instead, or "en" by
48
+ # default.
49
+ #
50
+ # @return [String] the document lang
51
+ attr_reader :lang
52
+
53
+ # @return [String] the relative path to the source of this document.
54
+ attr_reader :file
55
+
56
+ # @return [String] the relative path to the generated html file of
57
+ # this document.
58
+ attr_reader :html_file
59
+
60
+ # @return [String] the url of this document, build from the ~domain~
61
+ # settings and the above {#html_file @html_file} attribute.
62
+ attr_reader :url
63
+
64
+ # @return [String] the description of this org document, taken from
65
+ # the ~#+description:~ header.
66
+ attr_reader :excerpt
67
+
68
+ extend Neruda::OrgFileClassMethods
69
+
70
+ include Neruda::OrgFileExtracter
71
+ include Neruda::OrgFileHtmlizer
72
+
73
+ # Prepares the file named by ~file_name~ for read and write
74
+ # operations.
75
+ #
76
+ # If the file ~file_name~ does not exist, the new instance may be
77
+ # populated by data given in the ~opts~ parameter.
78
+ #
79
+ # @example
80
+ # File.exist? './test.org'
81
+ # => true
82
+ # o = Neruda::OrgFile.new('./test.org')
83
+ # => #<Neruda::OrgFile @file='./test.org'...>
84
+ # o.title
85
+ # => "This is an existing test file"
86
+ # File.exist? '/tmp/does_not_exist.org'
87
+ # => false
88
+ # o = Neruda::OrgFile.new('/tmp/does_not_exist.org')
89
+ # => #<Neruda::OrgFile @file='/tmp/does_not_exist.org'...>
90
+ # o.title
91
+ # => ""
92
+ # File.exist? '/tmp/other.org'
93
+ # => false
94
+ # o = Neruda::OrgFile.new('/tmp/other.org', title: 'New file')
95
+ # => #<Neruda::OrgFile @file='/tmp/other.org'...>
96
+ # o.title
97
+ # => "New file"
98
+ #
99
+ # @param file_name [String] path to the corresponding org mode file
100
+ # @param opts [Hash] optional data to initialize new org file
101
+ # @option opts [String] title ('') the title of the new org file
102
+ # @option opts [String] author (system user or '') the author of the
103
+ # document
104
+ # @option opts [Boolean] verbose (false) if the
105
+ # {Neruda::OrgFileHtmlizer#publish publish} method should output
106
+ # emacs process messages
107
+ # @return [Neruda::OrgFile] the new instance of Neruda::OrgFile
108
+ def initialize(file_name, opts = {})
109
+ file_name = nil if file_name == ''
110
+ @file = file_name
111
+ @html_file = Neruda::OrgFile.html_file @file
112
+ @url = Neruda::OrgFile.html_file_with_domain @file
113
+ @options = opts
114
+ if @file && File.exist?(@file)
115
+ extract_data
116
+ else
117
+ init_empty_file
118
+ end
119
+ end
120
+
121
+ # Returns a String representation of the document date, which aims
122
+ # to be used to sort several OrgFiles.
123
+ #
124
+ # The format used for the key is ~%Y%m%d%H%M%S~. If the current
125
+ # OrgFile instance does not have a date, this mehod return
126
+ # ~00000000000000~. If the current OrgFile instance does not have
127
+ # time information, the date is padded with zeros.
128
+ #
129
+ # @example with the org header ~#+date: <2019-07-03 Wed 20:52:49>~
130
+ # org_file.date
131
+ # => #<DateTime: 2019-07-03T20:52:49+02:00...>
132
+ # org_file.timekey
133
+ # => "20190703205349"
134
+ #
135
+ # @example with the org header ~#+date: <2019-07-03 Wed>~
136
+ # org_file.date
137
+ # => #<DateTime: 2019-07-03T00:00:00+02:00...>
138
+ # org_file.timekey
139
+ # => "20190703000000"
140
+ #
141
+ # @example with no date header in the org file
142
+ # org_file.date
143
+ # => nil
144
+ # org_file.timekey
145
+ # => "00000000000000"
146
+ #
147
+ # @return [String] the document key
148
+ def timekey
149
+ return '00000000000000' if @date.nil?
150
+ @date.strftime('%Y%m%d%H%M%S')
151
+ end
152
+
153
+ # Returns the current OrgFile instance DateTime as a String.
154
+ #
155
+ # This method accepts three values for the ~dateformat~ parameter:
156
+ #
157
+ # - ~:full~ (or ~:long~) :: outputs a complete date and time
158
+ # representation, localized through R18n;
159
+ # - ~:short~ :: outputs a short date representation (without time),
160
+ # localized with R18n;
161
+ # - ~:rfc3339~ :: outputs the RFC 3339 date and time representation,
162
+ # used in atom feed.
163
+ #
164
+ # @param dateformat [Symbol] the format to use to convert DateTime
165
+ # into String
166
+ # @param year [Boolean] wether or not the ~:full~ format must
167
+ # contain the year
168
+ # @return [String] the document DateTime string representation
169
+ def datestring(dateformat = :full, year = true)
170
+ return '' if @date.nil?
171
+ return R18n.l @date.to_date if dateformat == :short
172
+ return @date.rfc3339 if dateformat == :rfc3339
173
+ locale = R18n.get.locale
174
+ long_fmt = R18n.t.neruda.index.full_date_format(
175
+ date: locale.format_date_full(@date, year)
176
+ )
177
+ unless @notime
178
+ long_fmt = R18n.t.neruda.index.full_date_with_time_format(
179
+ date: long_fmt, time: locale.time_format.delete('_').strip
180
+ )
181
+ end
182
+ locale.strftime(@date, long_fmt)
183
+ end
184
+
185
+ # Formats given ~string~ with values of the current OrgFile.
186
+ #
187
+ # This method expects to find percent-tags in the given ~string~ and
188
+ # replace them by their corresponding value.
189
+ #
190
+ # *** Format:
191
+ #
192
+ # - %a :: the raw author name
193
+ # - %A :: the HTML rendering of the author name, equivalent to
194
+ # ~<span class="author">%a</span>~
195
+ # - %d :: the ~:short~ date HTML representation, equivalent
196
+ # to ~<time datetime="%I">%i</time>~
197
+ # - %D :: the ~:full~ date and time HTML representation
198
+ # - %i :: the raw ~:short~ date and time
199
+ # - %I :: the raw ~:rfc3339~ date and time
200
+ # - %k :: the keywords separated by a comma
201
+ # - %K :: the HTML list rendering of the keywords
202
+ # - %l :: the lang of the document
203
+ # - %L :: the license information, taken from the
204
+ # {Neruda::Config#settings}
205
+ # - %t :: the title of the document
206
+ # - %u :: the web path to the related published HTML document
207
+ # - %x :: the raw description (eXcerpt)
208
+ # - %X :: the description, enclosed in an HTML ~p~ tag, equivalent
209
+ # to ~<p>%x</p>~
210
+ #
211
+ # @example
212
+ # org_file.format("Article written by %a the %d")
213
+ # => "Article written by Alice Smith the Wednesday 3rd July"
214
+ #
215
+ # @return [String] the given ~string~ after replacement occurs
216
+ def format(string)
217
+ license = Neruda::Config.settings['license'] || ''
218
+ string.gsub('%a', @author)
219
+ .gsub('%A', author_to_html)
220
+ .gsub('%d', date_to_html(:short))
221
+ .gsub('%D', date_to_html)
222
+ .gsub('%i', datestring(:short))
223
+ .gsub('%I', datestring(:rfc3339))
224
+ .gsub('%k', @keywords.join(', '))
225
+ .gsub('%K', keywords_to_html)
226
+ .gsub('%l', @lang)
227
+ .gsub('%L', license.gsub(/\s+/, ' ').strip)
228
+ .gsub('%t', @title)
229
+ .gsub('%u', @html_file)
230
+ .gsub('%x', @excerpt)
231
+ .gsub('%X', "<p>#{@excerpt}</p>")
232
+ end
233
+
234
+ # Writes the current OrgFile content to the underlying file.
235
+ #
236
+ # The intermediate parent folders are created if necessary.
237
+ #
238
+ # @return [Integer] the length written (as returned by the
239
+ # underlying ~IO.write~ method call)
240
+ def write
241
+ raise TypeError, 'no conversion from nil file name to path.' if @file.nil?
242
+ file_dir = File.dirname @file
243
+ FileUtils.mkdir_p file_dir unless Dir.exist? file_dir
244
+ IO.write @file, @content
245
+ end
246
+
247
+ private
248
+
249
+ def init_empty_file
250
+ @title = @options[:title] || ''
251
+ @date = DateTime.now
252
+ @notime = false
253
+ @author = @options[:author] || Neruda::Config.settings['author']
254
+ @keywords = []
255
+ @lang = Neruda::Config.settings['lang']
256
+ @excerpt = ''
257
+ @content = @options[:content] || <<~ORG
258
+ #+title: #{@title}
259
+ #+date: <#{@date.strftime('%Y-%m-%d %a. %H:%M:%S')}>
260
+ #+author: #{@author}
261
+ #+language: #{@lang}
262
+
263
+ ORG
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neruda
4
+ # This module holds class methods for the {Neruda::OrgFile} class.
5
+ module OrgFileClassMethods
6
+ def html_file(file_name)
7
+ return nil if file_name.nil?
8
+ path = Neruda::OrgFile.target_for_source(file_name)
9
+ pubfolder = Neruda::Config.settings['public_folder']
10
+ path.sub(/^#{pubfolder}\//, '/')
11
+ end
12
+
13
+ def html_file_with_domain(file_name)
14
+ return nil if file_name.nil?
15
+ Neruda::Config.settings['domain'] + html_file(file_name)
16
+ end
17
+
18
+ def source_for_target(file_name)
19
+ # file_name may be frozen...
20
+ src = file_name.sub(/\.html$/, '.org')
21
+ pubfolder = Neruda::Config.settings['public_folder']
22
+ src.sub(/^#{pubfolder}\//, 'src/')
23
+ end
24
+
25
+ def target_for_source(file_name)
26
+ # file_name may be frozen...
27
+ target = file_name.sub(/\.org$/, '.html')
28
+ pubfolder = Neruda::Config.settings['public_folder']
29
+ return target.sub(/^src\//, "#{pubfolder}/") if /^src\//.match?(target)
30
+ subfolder = File.basename(File.dirname(target))
31
+ leaf = File.basename(target)
32
+ "#{pubfolder}/#{subfolder}/#{leaf}"
33
+ end
34
+
35
+ def slug(title)
36
+ title.downcase.gsub(' ', '-')
37
+ .encode('ascii', fallback: ->(k) { translit(k) })
38
+ .gsub(/[^\w-]/, '').gsub(/-$/, '')
39
+ end
40
+
41
+ private
42
+
43
+ def translit(char)
44
+ return 'a' if ['á', 'à', 'â', 'ä', 'ǎ', 'ã', 'å'].include?(char)
45
+ return 'e' if ['é', 'è', 'ê', 'ë', 'ě', 'ẽ'].include?(char)
46
+ return 'i' if ['í', 'ì', 'î', 'ï', 'ǐ', 'ĩ'].include?(char)
47
+ return 'o' if ['ó', 'ò', 'ô', 'ö', 'ǒ', 'õ'].include?(char)
48
+ return 'u' if ['ú', 'ù', 'û', 'ü', 'ǔ', 'ũ'].include?(char)
49
+ return 'y' if ['ý', 'ỳ', 'ŷ', 'ÿ', 'ỹ'].include?(char)
50
+ return 'c' if char == 'ç'
51
+ return 'n' if char == 'ñ'
52
+ '-'
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neruda
4
+ # This module holds extracter methods for the {Neruda::OrgFile} class.
5
+ module OrgFileExtracter
6
+ private
7
+
8
+ # Main method, which will call the other to initialize an
9
+ # {Neruda::OrgFile} instance.
10
+ def extract_data
11
+ @content = IO.read @file
12
+ @title = extract_title
13
+ @date = extract_date
14
+ @author = extract_author
15
+ @keywords = extract_keywords
16
+ @lang = extract_lang
17
+ @excerpt = extract_excerpt
18
+ end
19
+
20
+ def extract_date
21
+ timerx = '([0-9:]{5})(?::([0-9]{2}))?'
22
+ m = /^#\+date: *<([0-9-]{10}) [\w.]+(?: #{timerx})?> *$/i.match(@content)
23
+ return nil if m.nil?
24
+ @notime = m[2].nil?
25
+ if @notime
26
+ time = '00:00:00'
27
+ else
28
+ time = "#{m[2]}:#{m[3] || '00'}"
29
+ end
30
+ DateTime.strptime("#{m[1]} #{time}", '%Y-%m-%d %H:%M:%S')
31
+ end
32
+
33
+ def extract_title
34
+ m = /^#\+title:(.+)$/i.match(@content)
35
+ return @file if m.nil?
36
+ m[1].strip
37
+ end
38
+
39
+ def extract_author
40
+ m = /^#\+author:(.+)$/i.match(@content)
41
+ return Neruda::Config.settings['author'] if m.nil?
42
+ m[1].strip
43
+ end
44
+
45
+ def extract_keywords
46
+ m = /^#\+keywords:(.+)$/i.match(@content)
47
+ return [] if m.nil?
48
+ m[1].split(',').map(&:strip)
49
+ end
50
+
51
+ def extract_lang
52
+ m = /^#\+language:(.+)$/i.match(@content)
53
+ return Neruda::Config.settings['lang'] if m.nil?
54
+ m[1].strip
55
+ end
56
+
57
+ def extract_excerpt
58
+ @content.scan(/^#\+description:(.+)$/i).map { |l| l[0].strip }.join(' ')
59
+ end
60
+ end
61
+ end