fronde 0.3.4 → 0.5.0

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