neruda 0.0.10 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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