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,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open-uri'
4
+
5
+ module Neruda
6
+ # This module contains utilitary methods to ease ~org-config.el~
7
+ # file generation
8
+ module LispConfig
9
+ # Fetch and return the last published version of org mode.
10
+ #
11
+ # @return [String] the new x.x.x version string of org mode
12
+ def org_last_version
13
+ return @org_version if @org_version
14
+ index = open('https://orgmode.org/index.html', 'r').read
15
+ last_ver = index.match(/https:\/\/orgmode\.org\/org-([0-9.]+)\.tar\.gz/)
16
+ # :nocov:
17
+ if last_ver.nil?
18
+ warn 'Org last version not found'
19
+ return nil
20
+ end
21
+ # :nocov:
22
+ @org_version = last_ver[1]
23
+ end
24
+
25
+ # Generate emacs lisp configuration file for org mode and write it.
26
+ #
27
+ # This method saves the generated configuration in the file
28
+ # ~org-config.el~ at the root of your project, overwriting it if it
29
+ # existed already.
30
+ #
31
+ # @return [Integer] the length written (as returned by the
32
+ # underlying ~IO.write~ method call)
33
+ def write_org_lisp_config
34
+ projects = org_generate_projects
35
+ workdir = Dir.pwd
36
+ content = IO.read(File.expand_path('./org-config.el', __dir__))
37
+ .gsub('__WORK_DIR__', workdir)
38
+ .gsub('__NERUDA_DIR__', __dir__)
39
+ .gsub('__ORG_VER__', org_last_version)
40
+ .gsub('__ALL_PROJECTS__', all_projects(projects).strip)
41
+ .gsub('__THEME_CONFIG__', org_theme_config.strip)
42
+ .gsub('__ALL_PROJECTS_NAMES__', project_names(projects))
43
+ .gsub('__LONG_DATE_FMT__', r18n_full_datetime_format)
44
+ .gsub('__AUTHOR_EMAIL__', settings['author_email'] || '')
45
+ .gsub('__AUTHOR_NAME__', settings['author'])
46
+ IO.write("#{workdir}/org-config.el", content)
47
+ end
48
+
49
+ # Generate emacs directory variables file.
50
+ #
51
+ # This method generate the file ~.dir-locals.el~, which is
52
+ # responsible to load neruda org mode settings when visiting an
53
+ # org file of this neruda instance.
54
+ #
55
+ # @return [Integer] the length written (as returned by the
56
+ # underlying ~IO.write~ method call)
57
+ def write_dir_locals
58
+ workdir = Dir.pwd
59
+ IO.write(
60
+ "#{workdir}/.dir-locals.el",
61
+ "((org-mode . ((eval . (load-file \"#{workdir}/org-config.el\")))))"
62
+ )
63
+ end
64
+
65
+ private
66
+
67
+ def r18n_full_datetime_format
68
+ locale = R18n.get.locale
69
+ date_fmt = R18n.t.neruda.index.full_date_format(
70
+ date: locale.full_format
71
+ )
72
+ date_fmt = locale.year_format.sub('_', date_fmt)
73
+ time_fmt = locale.time_format.delete('_').strip
74
+ R18n.t.neruda.index.full_date_with_time_format(
75
+ date: date_fmt, time: time_fmt
76
+ )
77
+ end
78
+
79
+ def project_names(projects)
80
+ projects.keys.map { |p| ["\"#{p}\"", "\"#{p}-assets\""] }
81
+ .flatten.join(' ')
82
+ end
83
+
84
+ def all_projects(projects)
85
+ projects.values.join("\n").strip
86
+ .gsub(/\n\n/, "\n")
87
+ .gsub(/\n/, "\n ")
88
+ end
89
+
90
+ def org_project(project_name, opts)
91
+ orgtpl = opts['org_headers']
92
+ base_directory = File.expand_path(opts['path'])
93
+ publish_in = [Dir.pwd, settings['public_folder']]
94
+ publish_in << project_name unless project_name == 'org'
95
+ publish_in = publish_in.join('/')
96
+ recline = [opts['recursive'] || 't']
97
+ default_ex_ptrn = settings['exclude_pattern']
98
+ if opts['exclude']
99
+ recline << ":exclude \"#{opts['exclude']}\""
100
+ elsif project_name == 'org' && default_ex_ptrn
101
+ recline << ":exclude \"#{default_ex_ptrn}\""
102
+ end
103
+ <<~ORGPROJECT
104
+ ("#{project_name}"
105
+ :base-directory "#{base_directory}"
106
+ :base-extension "org"
107
+ :recursive #{recline.join("\n ")}
108
+ :publishing-directory "#{publish_in}"
109
+ :publishing-function org-html-publish-to-html
110
+ :section-numbers nil
111
+ :with-toc nil
112
+ #{orgtpl})
113
+ ("#{project_name}-assets"
114
+ :base-directory "#{base_directory}"
115
+ :base-extension "jpg\\\\\\|gif\\\\\\|png\\\\\\|svg\\\\\\|pdf"
116
+ :recursive #{recline[0]}
117
+ :publishing-directory "#{publish_in}"
118
+ :publishing-function org-publish-attachment)
119
+ ORGPROJECT
120
+ end
121
+
122
+ def org_default_theme_options
123
+ postamble = <<~POSTAMBLE
124
+ <p><span class="author">#{R18n.t.neruda.org.postamble.written_by}</span>
125
+ #{R18n.t.neruda.org.postamble.with_emacs}</p>
126
+ <p class="date">#{R18n.t.neruda.org.postamble.last_modification}</p>
127
+ <p class="validation">%v</p>
128
+ POSTAMBLE
129
+ { 'html-head' => build_html_head.strip,
130
+ 'html-postamble' => postamble.strip,
131
+ 'html-head-include-default-style' => 't',
132
+ 'html-head-include-scripts' => 'nil' }
133
+ end
134
+
135
+ def org_templates
136
+ orgtplopts = org_default_theme_options.merge
137
+ orgtplopts.merge!(settings['org-html'] || {})
138
+ orgtpl = []
139
+ orgtplopts.each do |k, v|
140
+ val = v.strip.gsub(/"/, '\"')
141
+ if ['t', 'nil', '1'].include? val
142
+ orgtpl << ":#{k} #{val}"
143
+ else
144
+ orgtpl << ":#{k} \"#{val}\""
145
+ end
146
+ end
147
+ orgtpl.join("\n ")
148
+ end
149
+
150
+ def org_generate_projects
151
+ orgtpl = org_templates
152
+ projects = { 'org' => org_project('org', 'org_headers' => orgtpl,
153
+ 'path' => './src') }
154
+ settings['external_sources']&.each do |s|
155
+ opts = { 'org_headers' => orgtpl }
156
+ if s.is_a? String
157
+ opts['path'] = s
158
+ elsif s.is_a? Hash
159
+ opts.merge! s
160
+ end
161
+ next unless opts.has_key?('path')
162
+ pname = File.basename(opts['path']).sub(/^\./, '')
163
+ projects[pname] = org_project(pname, opts)
164
+ end
165
+ projects
166
+ end
167
+
168
+ def org_theme_config
169
+ curtheme = settings['theme'] || 'default'
170
+ workdir = Dir.pwd
171
+ if curtheme == 'default'
172
+ sourcedir = File.expand_path('../../../', __dir__)
173
+ else
174
+ sourcedir = workdir
175
+ end
176
+ <<~THEMECONFIG
177
+ ("theme"
178
+ :base-directory "#{sourcedir}/themes/#{curtheme}"
179
+ :base-extension "jpg\\\\\\|gif\\\\\\|png\\\\\\|js\\\\\\|css\\\\\\|otf\\\\\\|ttf\\\\\\|woff2?"
180
+ :recursive t
181
+ :publishing-directory "#{workdir}/#{settings['public_folder']}/assets"
182
+ :publishing-function org-publish-attachment)
183
+ THEMECONFIG
184
+ end
185
+
186
+ def build_html_head
187
+ stylesheet = <<~CSS
188
+ <link rel="stylesheet" type="text/css" media="screen"
189
+ href="#{settings['domain']}/assets/css/style.css">
190
+ <link rel="stylesheet" type="text/css" media="screen"
191
+ href="#{settings['domain']}/assets/css/htmlize.css">
192
+ CSS
193
+ return stylesheet if settings['blog_path'].nil?
194
+ <<~ATOM
195
+ #{stylesheet.strip}
196
+ <link rel="alternate" type="application/atom+xml" title="Atom 1.0"
197
+ href="#{settings['domain']}/feeds/index.xml" />
198
+ ATOM
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,17 @@
1
+ ;; Add custom org mode to load path
2
+ (add-to-list 'load-path (expand-file-name "org-__ORG_VER__/lisp" "__WORK_DIR__"))
3
+ ;; Load modern version of htmlize.el
4
+ (load-file (expand-file-name "htmlize.el" "__WORK_DIR__"))
5
+
6
+ ;; Current project options
7
+ (setq neruda/current-work-dir "__WORK_DIR__"
8
+ user-mail-address "__AUTHOR_EMAIL__"
9
+ user-full-name "__AUTHOR_NAME__"
10
+ org-html-metadata-timestamp-format "__LONG_DATE_FMT__"
11
+ org-publish-project-alist
12
+ `(__ALL_PROJECTS__
13
+ __THEME_CONFIG__
14
+ ("website" :components (__ALL_PROJECTS_NAMES__ "theme"))))
15
+
16
+ ;; Load neruda lib
17
+ (load-file (expand-file-name "ox-neruda.el" "__NERUDA_DIR__"))
@@ -0,0 +1,88 @@
1
+ ;;; ox-neruda.el --- Neruda Gem specific helpers for Org Export Engine -*- lexical-binding: t; -*-
2
+
3
+ ;; Copyright (C) 2011-2019 Free Software Foundation, Inc.
4
+
5
+ ;; Author: Étienne Deparis <etienne at depar dot is>
6
+ ;; Keywords: org, export
7
+
8
+ ;; This file is not part of GNU Emacs.
9
+
10
+ ;; GNU Emacs is free software: you can redistribute it and/or modify
11
+ ;; it under the terms of the GNU General Public License as published by
12
+ ;; the Free Software Foundation, either version 3 of the License, or
13
+ ;; (at your option) any later version.
14
+
15
+ ;; GNU Emacs is distributed in the hope that it will be useful,
16
+ ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17
+ ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
+ ;; GNU General Public License for more details.
19
+
20
+ ;; You should have received a copy of the GNU General Public License
21
+ ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
22
+
23
+ ;;; Commentary:
24
+
25
+ ;; This library implements specific helpers function, needed by the Ruby
26
+ ;; Gem Neruda, which offers an easy way to publish a static website
27
+ ;; using Org files as sources.
28
+
29
+ ;;; Code:
30
+
31
+ (require 'org)
32
+ (require 'ox-html)
33
+
34
+ ;;; Function Declarations
35
+
36
+ (defvar neruda/current-work-dir nil
37
+ "Location of the current neruda website base directory.")
38
+
39
+ (defvar neruda/org-temp-dir nil
40
+ "Location of the local org-mode temporary directory (where to place
41
+ org timestamps and id locations).")
42
+
43
+ (defun neruda/org-i18n-export (link description format)
44
+ "Export a i18n link"
45
+ (let* ((splitted-link (split-string link "|"))
46
+ (path (car splitted-link))
47
+ (desc (or description path))
48
+ (lang (cadr splitted-link)))
49
+ (pcase format
50
+ (`html (if lang
51
+ (format "<a href=\"%s\" hreflang=\"%s\">%s</a>"
52
+ path lang desc)
53
+ (format "<a href=\"%s\">%s</a>" path desc)))
54
+ (`latex (format "\\href{%s}{%s}" path desc))
55
+ (`ascii (format "%s (%s)" desc path))
56
+ (_ path))))
57
+
58
+ (defun neruda/org-i18n-follow (link)
59
+ "Visit a i18n link"
60
+ (browse-url (car (split-string link "|"))))
61
+
62
+ (org-link-set-parameters "i18n"
63
+ :export #'neruda/org-i18n-export
64
+ :follow #'neruda/org-i18n-follow)
65
+
66
+
67
+ ;;; Set configuration options
68
+
69
+ (setq neruda/org-temp-dir (expand-file-name "tmp" neruda/current-work-dir)
70
+ org-publish-timestamp-directory (expand-file-name "timestamps/" neruda/org-temp-dir)
71
+ org-id-locations-file (expand-file-name "id-locations.el" neruda/org-temp-dir)
72
+ make-backup-files nil
73
+ enable-local-variables :all
74
+ org-confirm-babel-evaluate nil
75
+ org-html-doctype "html5"
76
+ org-html-html5-fancy t
77
+ org-html-htmlize-output-type 'css
78
+ org-html-text-markup-alist '((bold . "<strong>%s</strong>")
79
+ (code . "<code>%s</code>")
80
+ (italic . "<em>%s</em>")
81
+ (strike-through . "<del>%s</del>")
82
+ (underline . "<span class=\"underline\">%s</span>")
83
+ (verbatim . "<code>%s</code>")))
84
+
85
+
86
+ (provide 'ox-neruda)
87
+
88
+ ;;; ox-neruda.el ends here
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'digest/md5'
5
+ require 'neruda/config'
6
+ require 'neruda/org_file'
7
+ require 'neruda/index/atom_generator'
8
+ require 'neruda/index/org_generator'
9
+
10
+ module Neruda
11
+ # Generates website indexes and atom feeds for all the org documents
12
+ # keywords.
13
+ class Index
14
+ attr_reader :date
15
+
16
+ include Neruda::IndexAtomGenerator
17
+ include Neruda::IndexOrgGenerator
18
+
19
+ def initialize(file_list = nil)
20
+ @blog_path = Neruda::Config.settings['blog_path']
21
+ @pubdir = Neruda::Config.settings['public_folder']
22
+ @index = { 'index' => [] }
23
+ @tags_names = {}
24
+ @date = DateTime.now
25
+ if @blog_path.nil?
26
+ @sources = []
27
+ else
28
+ @sources = sources_list(file_list)
29
+ filter_and_prefix_sources!
30
+ @sources.each { |f| add_to_indexes(Neruda::OrgFile.new(f)) }
31
+ sort!
32
+ end
33
+ end
34
+
35
+ def entries
36
+ @index.keys
37
+ end
38
+
39
+ def write_all(verbose = true)
40
+ @index.keys.each do |k|
41
+ write_org(k)
42
+ warn "Generated index file for #{k}" if verbose
43
+ write_atom(k)
44
+ warn "Generated atom feed for #{k}" if verbose
45
+ end
46
+ write_org_lists
47
+ warn 'Generated all tags index' if verbose
48
+ end
49
+
50
+ def sort_by(kind)
51
+ if [:name, :weight].include?(kind)
52
+ return sort_tags_by_name_and_weight["by_#{kind}".to_sym]
53
+ end
54
+ raise ArgumentError, "#{kind} not in [:name, :weight]"
55
+ end
56
+
57
+ private
58
+
59
+ def sources_list(file_list)
60
+ return file_list unless file_list.nil?
61
+ Dir.glob(Neruda::Config.settings['blog_pattern'],
62
+ base: "src/#{@blog_path}")
63
+ end
64
+
65
+ def filter_and_prefix_sources!
66
+ exclude = Neruda::Config.settings['exclude_pattern']
67
+ sources = []
68
+ @sources.each do |f|
69
+ next if f == 'index.org'
70
+ if File.exist?(f)
71
+ file_path = f
72
+ else
73
+ file_path = "src/#{@blog_path}/#{f}"
74
+ next unless File.exist?(file_path)
75
+ end
76
+ next if exclude && file_path.match(exclude)
77
+ sources << file_path
78
+ end
79
+ @sources = sources
80
+ end
81
+
82
+ def add_to_indexes(article)
83
+ @index['index'] << article
84
+ article.keywords.each do |k|
85
+ slug = Neruda::OrgFile.slug k
86
+ @tags_names[slug] = k # Overwrite is permitted
87
+ @index[slug] = [] unless @index.has_key?(slug)
88
+ @index[slug] << article
89
+ end
90
+ end
91
+
92
+ def sort!
93
+ @index.each do |k, i|
94
+ @index[k] = i.sort { |a, b| b.timekey <=> a.timekey }
95
+ end
96
+ end
97
+
98
+ def sort_tags_by_name_and_weight
99
+ tags_sorted = {}
100
+ all_keys = @index.keys.reject { |k| k == 'index' }
101
+ tags_sorted[:by_name] = all_keys.sort
102
+ tags_sorted[:by_weight] = all_keys.sort do |a, b|
103
+ @index[b].length <=> @index[a].length
104
+ end
105
+ tags_sorted
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi'
4
+ require 'neruda/config'
5
+
6
+ module Neruda
7
+ # Embeds Atom feeds sepecific methods
8
+ module IndexAtomGenerator
9
+ def to_atom(index_name = 'index')
10
+ content = [atom_header(index_name)]
11
+ @index[index_name][0...10].each do |article|
12
+ content << atom_entry(article)
13
+ end
14
+ content.join("\n") + '</feed>'
15
+ end
16
+
17
+ def write_atom(index_name)
18
+ return 0 if @blog_path.nil?
19
+ slug = Neruda::OrgFile.slug index_name
20
+ FileUtils.mkdir_p "#{@pubdir}/feeds"
21
+ atomdest = "#{@pubdir}/feeds/#{slug}.xml"
22
+ IO.write(atomdest, to_atom(index_name))
23
+ end
24
+
25
+ private
26
+
27
+ # Render the Atom feed header.
28
+ #
29
+ # @param title [String] the title of the current atom feed
30
+ # @return [String] the Atom header as a String
31
+ def atom_header(title)
32
+ domain = Neruda::Config.settings['domain']
33
+ upddate = @date.rfc3339
34
+ slug = Neruda::OrgFile.slug(title)
35
+ tagurl = "#{domain}/tags/#{slug}.html"
36
+ if title == 'index'
37
+ if Neruda::Config.settings['title']
38
+ title = Neruda::Config.settings['title']
39
+ end
40
+ tagurl = "#{domain}/#{@blog_path}"
41
+ elsif @tags_names.has_key?(title)
42
+ title = @tags_names[title]
43
+ end
44
+ title_esc = CGI.escapeHTML(title)
45
+ <<~ENDATOM
46
+ <?xml version="1.0" encoding="utf-8"?>
47
+ <feed xmlns="http://www.w3.org/2005/Atom"
48
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
49
+ xmlns:wfw="http://wellformedweb.org/CommentAPI/"
50
+ xml:lang="#{Neruda::Config.settings['lang']}">
51
+
52
+ <title>#{title_esc}</title>
53
+ <link href="#{domain}/feeds/#{slug}.xml" rel="self" type="application/atom+xml"/>
54
+ <link href="#{tagurl}" rel="alternate" type="text/html" title="#{title_esc}"/>
55
+ <updated>#{upddate}</updated>
56
+ <author><name>#{Neruda::Config.settings['author'] || ''}</name></author>
57
+ <id>urn:md5:#{Digest::MD5.hexdigest(domain)}</id>
58
+ <generator uri="https://fossil.deparis.io/neruda">Neruda</generator>
59
+ ENDATOM
60
+ end
61
+
62
+ # Render an Atom feed entry.
63
+ #
64
+ # @param article [Neruda::OrgFile] the related org document for this
65
+ # entry
66
+ # @return [String] the Atom entry as a String
67
+ def atom_entry(article)
68
+ keywords = article.keywords.map do |k|
69
+ "<dc:subject>#{CGI.escapeHTML(k)}</dc:subject>"
70
+ end.join
71
+ keywords += "\n " if keywords != ''
72
+ title_esc = CGI.escapeHTML(article.title)
73
+ <<~ENDENTRY
74
+ <entry>
75
+ <title>#{title_esc}</title>
76
+ <link href="#{article.url}" rel="alternate" type="text/html"
77
+ title="#{title_esc}"/>
78
+ <id>urn:md5:#{Digest::MD5.hexdigest(article.timekey)}</id>
79
+ <published>#{article.datestring(:rfc3339)}</published>
80
+ <author><name>#{CGI.escapeHTML(article.author)}</name></author>
81
+ #{keywords}<content type="html">#{CGI.escapeHTML(article.excerpt)}</content>
82
+ </entry>
83
+ ENDENTRY
84
+ end
85
+ end
86
+ end