fronde 0.3.4 → 0.5.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/bin/fronde +15 -30
  3. data/lib/ext/nil_time.rb +25 -0
  4. data/lib/ext/r18n.rb +37 -0
  5. data/lib/ext/time.rb +39 -0
  6. data/lib/ext/time_no_time.rb +23 -0
  7. data/lib/fronde/cli/commands.rb +97 -104
  8. data/lib/fronde/cli/data/Rakefile +8 -0
  9. data/lib/fronde/cli/data/config.yml +13 -0
  10. data/lib/fronde/cli/data/gitignore +6 -0
  11. data/lib/fronde/cli/data/zsh_completion +37 -0
  12. data/lib/fronde/cli/helpers.rb +55 -0
  13. data/lib/fronde/cli/opt_parse.rb +140 -0
  14. data/lib/fronde/cli/throbber.rb +110 -0
  15. data/lib/fronde/cli.rb +42 -42
  16. data/lib/fronde/config/data/org-config.el +25 -0
  17. data/lib/fronde/config/data/ox-fronde.el +158 -0
  18. data/lib/fronde/config/data/themes/umaneti/css/htmlize.css +364 -0
  19. data/lib/fronde/config/data/themes/umaneti/css/style.css +250 -0
  20. data/lib/fronde/config/data/themes/umaneti/img/bottom.png +0 -0
  21. data/lib/fronde/config/data/themes/umaneti/img/content.png +0 -0
  22. data/lib/fronde/config/data/themes/umaneti/img/tic.png +0 -0
  23. data/lib/fronde/config/data/themes/umaneti/img/top.png +0 -0
  24. data/lib/fronde/config/helpers.rb +62 -0
  25. data/lib/fronde/config/lisp.rb +80 -0
  26. data/lib/fronde/config.rb +148 -98
  27. data/lib/fronde/emacs.rb +23 -20
  28. data/lib/fronde/index/atom_generator.rb +55 -66
  29. data/lib/fronde/index/data/all_tags.org +19 -0
  30. data/lib/fronde/index/data/template.org +26 -0
  31. data/lib/fronde/index/data/template.xml +37 -0
  32. data/lib/fronde/index/org_generator.rb +72 -88
  33. data/lib/fronde/index.rb +57 -86
  34. data/lib/fronde/org/file.rb +299 -0
  35. data/lib/fronde/org/file_extracter.rb +101 -0
  36. data/lib/fronde/org.rb +105 -0
  37. data/lib/fronde/preview.rb +43 -39
  38. data/lib/fronde/slug.rb +54 -0
  39. data/lib/fronde/source/gemini.rb +34 -0
  40. data/lib/fronde/source/html.rb +67 -0
  41. data/lib/fronde/source.rb +209 -0
  42. data/lib/fronde/sync/neocities.rb +220 -0
  43. data/lib/fronde/sync/rsync.rb +46 -0
  44. data/lib/fronde/sync.rb +32 -0
  45. data/lib/fronde/templater.rb +101 -71
  46. data/lib/fronde/version.rb +1 -1
  47. data/lib/tasks/cli.rake +33 -0
  48. data/lib/tasks/org.rake +58 -43
  49. data/lib/tasks/site.rake +66 -31
  50. data/lib/tasks/sync.rake +37 -40
  51. data/lib/tasks/tags.rake +11 -7
  52. data/locales/en.yml +61 -14
  53. data/locales/fr.yml +69 -14
  54. metadata +77 -95
  55. data/lib/fronde/config/lisp_config.rb +0 -340
  56. data/lib/fronde/config/org-config.el +0 -19
  57. data/lib/fronde/config/ox-fronde.el +0 -121
  58. data/lib/fronde/org_file/class_methods.rb +0 -72
  59. data/lib/fronde/org_file/extracter.rb +0 -72
  60. data/lib/fronde/org_file/htmlizer.rb +0 -43
  61. data/lib/fronde/org_file.rb +0 -298
  62. data/lib/fronde/utils.rb +0 -229
@@ -0,0 +1,37 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <feed xmlns="http://www.w3.org/2005/Atom"
3
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
4
+ xml:lang="{{ lang }}">
5
+
6
+ <title>{{ title | escape }}</title>
7
+ <link href="{{ domain }}/feeds/{{ slug }}.xml" rel="self" type="application/atom+xml"/>
8
+ <link href="{{ tagurl }}" rel="alternate" type="text/html" title="{{ title }}"/>
9
+ <updated>{{ upddate }}</updated>
10
+ <author><name>{{ author }}</name></author>
11
+ <id>urn:md5:{{ domain | md5 }}</id>
12
+ <generator uri="https://git.umaneti.net/fronde/about/">Fronde</generator>
13
+
14
+ {%- for article in entries %}
15
+
16
+ <entry>
17
+ <title>{{ article.title | escape }}</title>
18
+ <link href="{{ article.url }}" rel="alternate"
19
+ type="{{ publication_format }}"
20
+ title="{{ article.title }}"/>
21
+ <id>urn:md5:{{ article.timekey | md5 }}</id>
22
+ <published>{{ article.published_xml }}</published>
23
+ <updated>{{ article.updated_xml }}</updated>
24
+ <author><name>{{ article.author | escape }}</name></author>
25
+ {%- for keyword in article.keywords %}
26
+ <dc:subject>{{ keyword | escape }}</dc:subject>
27
+ {%- endfor %}
28
+ {%- if publication_format == 'text/html' %}
29
+ <content type="html">
30
+ {%- else %}
31
+ <content>
32
+ {%- endif %}
33
+ {{- article.published_body | escape -}}
34
+ </content>
35
+ </entry>
36
+ {%- endfor %}
37
+ </feed>
@@ -1,114 +1,98 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ using TimePatch
4
+
3
5
  module Fronde
4
- # Embeds methods responsible for generating an org file for a given
5
- # index.
6
- module IndexOrgGenerator
7
- def to_org(index_name = 'index', is_project: false)
8
- return project_home_page(index_name) if is_project
6
+ # Reopen Index class to embed methods responsible for generating an
7
+ # org file for a given index.
8
+ class Index
9
+ def blog_home_page
10
+ org_index @project['title'], '__HOME_PAGE__', @entries.map(&:to_h)
11
+ end
12
+
13
+ def to_org(index_name = 'index')
9
14
  return all_tags_index if index_name == 'index'
10
- [org_header(index_name),
11
- org_articles(@index[index_name])].join("\n")
15
+
16
+ org_index(
17
+ @tags_names[index_name], index_name,
18
+ (@index[index_name] || []).map(&:to_h)
19
+ )
12
20
  end
13
21
  alias_method :to_s, :to_org
14
22
 
15
23
  def write_org(index_name)
16
- return unless save?
17
- slug = Fronde::OrgFile.slug index_name
18
- FileUtils.mkdir 'tags' unless Dir.exist? 'tags'
19
- content = to_org index_name
20
- orgdest = "tags/#{slug}.org"
21
- File.write(orgdest, content)
24
+ slug = Slug.slug index_name
25
+ orgdest = "#{@project['path']}/tags/#{slug}.org"
26
+ File.write orgdest, to_org(index_name)
22
27
  end
23
28
 
24
- private
25
-
26
- def project_home_page(project_name)
27
- content = [org_header(project_name, is_tag: false)]
28
- if @projects[project_name]&.any?
29
- content += org_articles(@projects[project_name])
29
+ def write_all_org(verbose: true)
30
+ FileUtils.mkdir_p "#{@project['path']}/tags"
31
+ @index.each_key do |tag|
32
+ write_org(tag)
33
+ warn R18n.t.fronde.index.index_generated(tag: tag) if verbose
30
34
  end
31
- content.join("\n")
35
+ write_blog_home_page(verbose)
32
36
  end
33
37
 
34
- def write_all_blog_home(verbose)
35
- Fronde::Config.sources.each do |project|
36
- next unless project['is_blog']
37
- next unless Dir.exist?(project['path'])
38
- warn "Generated blog home for #{project['name']}" if verbose
39
- orgdest = format('%<root>s/index.org', root: project['path'])
40
- File.write(orgdest, to_org(project['name'], is_project: true))
41
- end
42
- end
38
+ private
43
39
 
44
- def all_tags_index
45
- content = [
46
- org_header(R18n.t.fronde.index.all_tags, is_tag: false)
47
- ]
48
- sort_tags_by_name_and_weight.each do |t, tags|
49
- content << ''
50
- content << org_title(R18n.t.fronde.index.send(t), 'index-tags')
51
- next if tags.empty?
52
- tags.each do |k|
53
- content << "- #{tag_published_url(k)} (#{@index[k].length})"
40
+ # Render an Org index file.
41
+ #
42
+ # @param title [String] the title of the current org index
43
+ # @param slug [String] the slug of the current org index
44
+ # @param entries [Array] the article to list in this file
45
+ # @return [String] the org file content as a String
46
+ def org_index(title, slug, entries)
47
+ entries.map! do |article|
48
+ published = article['published']
49
+ unless published == ''
50
+ article['published'] = R18n.t.fronde.index.published_on published
54
51
  end
52
+ article
55
53
  end
56
- content.join("\n")
57
- end
58
-
59
- def tag_published_url(tag_name)
60
- domain = Fronde::Config.get('domain')
61
- title = @tags_names[tag_name]
62
- tag_link = "#{domain}/tags/#{tag_name}.html"
63
- "[[#{tag_link}][#{title}]]"
54
+ Config::Helpers.render_liquid_template(
55
+ File.read(File.expand_path('./data/template.org', __dir__)),
56
+ 'title' => title,
57
+ 'slug' => slug,
58
+ 'project_path' => @project.public_absolute_path,
59
+ 'project_type' => @project.type,
60
+ 'domain' => Fronde::CONFIG.get('domain'),
61
+ 'lang' => Fronde::CONFIG.get('lang'),
62
+ 'author' => Fronde::CONFIG.get('author'),
63
+ 'unsorted' => R18n.t.fronde.index.unsorted,
64
+ 'entries' => entries
65
+ )
64
66
  end
65
67
 
66
- def org_header(title = nil, is_tag: true)
67
- if is_tag
68
- title = @tags_names[title]
69
- elsif title.nil? || title == 'index'
70
- title = Fronde::Config.get('title')
71
- end
72
- <<~HEADER.strip
73
- #+title: #{title}
74
- #+author: #{Fronde::Config.get('author')}
75
- #+language: #{Fronde::Config.get('lang')}
76
- HEADER
77
- end
78
-
79
- def org_articles(articles_list)
80
- last_year = nil
81
- articles_list.map do |article|
82
- year_title = ''
83
- year = article.timekey.slice(0, 4)
84
- if year != last_year
85
- year_title = format("\n%<title>s\n", title: org_title(year))
86
- last_year = year
68
+ def all_tags_index
69
+ indexes = sort_tags_by_name_and_weight.map do |title, tags|
70
+ all_tags = tags.map do |tag|
71
+ {
72
+ 'slug' => tag, 'title' => @tags_names[tag],
73
+ 'weight' => @index[tag].length
74
+ }
87
75
  end
88
- year_title + org_entry(article)
76
+ { 'title' => R18n.t.fronde.index.send(title), 'tags' => all_tags }
89
77
  end
78
+ Config::Helpers.render_liquid_template(
79
+ File.read(File.expand_path('./data/all_tags.org', __dir__)),
80
+ 'title' => R18n.t.fronde.index.all_tags,
81
+ 'lang' => Fronde::CONFIG.get('lang'),
82
+ 'author' => Fronde::CONFIG.get('author'),
83
+ 'domain' => Fronde::CONFIG.get('domain'),
84
+ 'project_path' => @project.public_absolute_path,
85
+ 'project_type' => @project.type,
86
+ 'indexes' => indexes
87
+ )
90
88
  end
91
89
 
92
- def org_entry(article)
93
- line = "- *[[#{article.url}][#{article.title}]]*"
94
- if article.date
95
- art_date = article.datestring(:full, year: false)
96
- published = R18n.t.fronde.index.published_on art_date
97
- line += " / #{published}"
90
+ def write_blog_home_page(verbose)
91
+ orgdest = format('%<root>s/index.org', root: @project['path'])
92
+ if verbose
93
+ warn R18n.t.fronde.org.generate_blog_index(name: @project['name'])
98
94
  end
99
- line += " \\\\\n #{article.excerpt}" if article.excerpt != ''
100
- line
101
- end
102
-
103
- def org_title(year, html_class = 'index-year')
104
- year = R18n.t.fronde.index.unsorted if year == '0000'
105
- <<~ENDPROP
106
- * #{year}
107
- :PROPERTIES:
108
- :HTML_CONTAINER_CLASS: #{html_class}
109
- :UNNUMBERED: notoc
110
- :END:
111
- ENDPROP
95
+ File.write(orgdest, blog_home_page)
112
96
  end
113
97
  end
114
98
  end
data/lib/fronde/index.rb CHANGED
@@ -1,11 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'fileutils'
4
- require 'digest/md5'
5
- require 'fronde/config'
6
- require 'fronde/org_file'
7
- require 'fronde/index/atom_generator'
8
- require 'fronde/index/org_generator'
3
+ require_relative 'slug'
4
+ require_relative 'config'
5
+ require_relative 'org/file'
9
6
 
10
7
  module Fronde
11
8
  # Generates website indexes and atom feeds for all the org documents
@@ -13,118 +10,92 @@ module Fronde
13
10
  class Index
14
11
  attr_reader :date
15
12
 
16
- include Fronde::IndexAtomGenerator
17
- include Fronde::IndexOrgGenerator
18
-
19
- def initialize
20
- @pubdir = Fronde::Config.get('public_folder')
13
+ def initialize(project)
14
+ @project = project
21
15
  @index = { 'index' => [] }
22
- @projects = {}
16
+ @entries = []
23
17
  @tags_names = {}
24
- @date = DateTime.now
25
- feed
26
- sort!
18
+ @date = Time.now
19
+ generate_feeds if @project.blog?
20
+ sort_feeds!
27
21
  end
28
22
 
29
- def entries
30
- @index.keys.reject { |k| k == 'index' }
23
+ def all_tags
24
+ @index.keys.reject { |tag| tag == 'index' }
31
25
  end
32
26
 
33
27
  def empty?
34
28
  @index['index'].empty?
35
29
  end
36
30
 
37
- def write_all(verbose: true)
38
- @index.each_key do |k|
39
- write_org(k)
40
- warn "Generated index file for #{k}" if verbose
41
- write_atom(k)
42
- warn "Generated atom feed for #{k}" if verbose
31
+ def sort_by(kind)
32
+ accepted_values = %i[name weight]
33
+ unless accepted_values.include?(kind)
34
+ error_msg = R18n.t.fronde.error.index.wrong_sort_kind(
35
+ kind: kind, accepted_values: accepted_values.inspect
36
+ )
37
+ raise ArgumentError, error_msg
43
38
  end
44
- write_all_blog_home(verbose)
39
+ sort_tags_by_name_and_weight[:"by_#{kind}"].map do |tag|
40
+ @tags_names[tag] + " (#{@index[tag].length})"
41
+ end.reverse
42
+ # Reverse in order to have most important or A near next prompt
43
+ # and avoid to scroll to find the beginning of the list.
45
44
  end
46
45
 
47
- def sort_by(kind)
48
- if [:name, :weight].include?(kind)
49
- tags_sorted = sort_tags_by_name_and_weight["by_#{kind}".to_sym]
50
- # Reverse in order to have most important or A near next prompt
51
- # and avoid to scroll to find the beginning of the list.
52
- return tags_sorted.map do |k|
53
- @tags_names[k] + " (#{@index[k].length})"
54
- end.reverse
46
+ class << self
47
+ def all_blog_index(&block)
48
+ all_blogs = CONFIG.sources.filter_map do |project|
49
+ Index.new(project) if project.blog?
50
+ end
51
+ return all_blogs unless block
52
+
53
+ all_blogs.each(&block)
55
54
  end
56
- raise ArgumentError, "#{kind} not in [:name, :weight]"
57
55
  end
58
56
 
59
57
  private
60
58
 
61
- def feed
62
- Fronde::Config.sources.each do |project|
63
- next unless project['is_blog']
64
- if project['recursive']
65
- file_pattern = '**/*.org'
66
- else
67
- file_pattern = '*.org'
68
- end
69
- Dir.glob(file_pattern, base: project['path']).map do |s|
70
- org_file = File.join(project['path'], s)
71
- next if exclude_file?(org_file, project)
72
- add_to_indexes(
73
- Fronde::OrgFile.new(org_file, project: project)
74
- )
75
- end
76
- end
77
- end
59
+ def generate_feeds
60
+ file_pattern = '**/*.org'
61
+ Dir.glob(file_pattern, base: @project['path']).map do |index_file|
62
+ # Obviously don't parse tags
63
+ next if index_file.start_with?('tags/')
64
+
65
+ org_file = File.join(@project['path'], index_file)
66
+ next if @project.exclude_file? org_file
78
67
 
79
- def add_to_project_index(article)
80
- project = article.project
81
- @projects[project['name']] ||= []
82
- @projects[project['name']] << article
68
+ add_to_indexes(Org::File.new(org_file))
69
+ end
83
70
  end
84
71
 
85
72
  def add_to_indexes(article)
86
73
  @index['index'] << article
87
- add_to_project_index article
88
- article.keywords.each do |k|
89
- slug = Fronde::OrgFile.slug k
90
- @tags_names[slug] = k # Overwrite is permitted
74
+ @entries << article
75
+ article.keywords.each do |tag|
76
+ slug = Slug.slug tag
77
+ @tags_names[slug] = tag # Overwrite is permitted
91
78
  @index[slug] ||= []
92
79
  @index[slug] << article
93
80
  end
94
81
  end
95
82
 
96
- def sort!
97
- @index.each do |k, i|
98
- @index[k] = i.sort { |a, b| b.timekey <=> a.timekey }
99
- end
100
- @projects.each do |k, i|
101
- @projects[k] = i.sort { |a, b| b.timekey <=> a.timekey }
83
+ def sort_feeds!
84
+ @index.transform_values! do |articles|
85
+ articles.sort_by(&:timekey).reverse
102
86
  end
87
+ @entries = @entries.sort_by(&:timekey).reverse
103
88
  end
104
89
 
105
90
  def sort_tags_by_name_and_weight
106
- tags_sorted = {}
107
- all_keys = entries
108
- tags_sorted[:by_name] = all_keys.sort
109
- tags_sorted[:by_weight] = all_keys.sort do |a, b|
110
- @index[b].length <=> @index[a].length
111
- end
112
- tags_sorted
113
- end
114
-
115
- def exclude_file?(file_path, project)
116
- # Obviously excluding index itself for blogs
117
- return true if file_path == File.join(project['path'], 'index.org')
118
- return false unless project['exclude']
119
- file_path.match? project['exclude']
120
- end
121
-
122
- def save?
123
- return true unless empty?
124
- Fronde::Config.sources.each do |project|
125
- return true if project['is_blog'] && Dir.exist?(project['path'])
126
- end
127
- false
91
+ all_keys = all_tags
92
+ {
93
+ by_name: all_keys.sort,
94
+ by_weight: all_keys.sort_by { @index[_1].length }.reverse
95
+ }
128
96
  end
129
97
  end
130
98
  end
99
+
100
+ require_relative 'index/org_generator'
101
+ require_relative 'index/atom_generator'