fronde 0.3.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c646347a1cdbe6537384936ec392c1848617524fd7557f3c32566adb14e3c357
4
+ data.tar.gz: 41785e0d542baa6ac44c8e2f7b8d12d26db6ea5a5e10349ea4002d71b25f435d
5
+ SHA512:
6
+ metadata.gz: ecac97edd7c48c3cd808c906195b11e941f84db6254f0548af1d951e8bba215a6545616cc8b425977b5453b158f5b3032e08d4cf702b1087f110054770e3c739
7
+ data.tar.gz: adb5628d1999d54284ad38c614ca29bd76b287124fb4ebc434a465225f3c1fb97acf1cce4e844de05aaf66b35c40be8216b9045535dfb05370c77eecb0cb4bde
data/LICENSE ADDED
@@ -0,0 +1,14 @@
1
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2
+ Version 2, December 2004
3
+
4
+ Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
5
+
6
+ Everyone is permitted to copy and distribute verbatim or modified
7
+ copies of this license document, and changing it is allowed as long
8
+ as the name is changed.
9
+
10
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
11
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
12
+
13
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
14
+
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'optparse'
5
+ require 'r18n-core'
6
+ require 'fronde/utils'
7
+ require 'fronde/version'
8
+ require 'fronde/cli'
9
+
10
+ R18n.default_places = File.expand_path('../locales', __dir__)
11
+ R18n.set Fronde::Config.settings['lang']
12
+
13
+ optparser = OptionParser.new
14
+ optparser.version = Fronde::VERSION
15
+
16
+ Fronde::Utils::FRONDE_OPTIONS.each do |k, opt|
17
+ optparser.send(opt[:meth] || :on, *Fronde::Utils.decorate_option(k))
18
+ end
19
+
20
+ params = {}
21
+ optparser.parse!(into: params)
22
+
23
+ if params[:version]
24
+ warn optparser.ver
25
+ exit
26
+ end
27
+
28
+ if ARGV[0] == 'help'
29
+ params[:help] = true
30
+ ARGV.shift
31
+ end
32
+ fronde = Fronde::CLI.new(params)
33
+ command = "fronde_#{ARGV[0]}".to_sym
34
+ cmd_err = !fronde.respond_to?(command)
35
+ if params[:help] || cmd_err
36
+ cmd_err = false if params[:help] && !ARGV[0]
37
+ fronde.fronde_help(ARGV[0], error: cmd_err)
38
+ end
39
+ ARGV.shift
40
+
41
+ init_cmds = [:fronde_init, :fronde_config]
42
+ unless File.exist?('config.yml') || init_cmds.include?(command)
43
+ fronde.fronde_init
44
+ end
45
+ fronde.send command
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rake'
4
+ require 'fronde/cli/commands'
5
+
6
+ module Fronde
7
+ # Fronde CLI app
8
+ class CLI
9
+ def initialize(opts = {})
10
+ @options = { verbose: false }.merge(opts)
11
+ init_required_files
12
+ init_rake
13
+ end
14
+
15
+ include Fronde::CLICommands
16
+
17
+ private
18
+
19
+ def init_required_files
20
+ init_rakefile unless File.exist?('Rakefile')
21
+ init_gitignore unless File.exist?('.gitignore')
22
+ end
23
+
24
+ def init_rake
25
+ @rake = Rake.application
26
+ Rake.verbose(false) unless @options[:verbose]
27
+ @rake.raw_load_rakefile
28
+ end
29
+
30
+ def init_rakefile
31
+ rakefile = <<~RAKE
32
+ # frozen_string_literal: true
33
+
34
+ require 'fronde/config'
35
+ require 'r18n-core'
36
+
37
+ fronde_spec = Gem::Specification.find_by_name 'fronde'
38
+ R18n.default_places = "\#{fronde_spec.gem_dir}/locales"
39
+ R18n.set(Fronde::Config.settings['lang'] || 'en')
40
+ R18n::Filters.on(:named_variables)
41
+
42
+ Dir.glob("\#{fronde_spec.gem_dir}/lib/tasks/*.rake").each { |r| import r }
43
+
44
+ task default: 'site:build'
45
+ RAKE
46
+ IO.write 'Rakefile', rakefile
47
+ end
48
+
49
+ def init_gitignore
50
+ gitignore = <<~GITIGNORE
51
+ .dir-locals.el
52
+ Rakefile
53
+ lib
54
+ public_html
55
+ var
56
+ GITIGNORE
57
+ IO.write '.gitignore', gitignore
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fronde
4
+ # Fronde commands
5
+ module CLICommands
6
+ def fronde_init
7
+ cnf = @options.merge
8
+ cnf.delete(:verbose)
9
+ cnf.transform_keys!(&:to_s)
10
+ Fronde::Config.save(Fronde::Config.settings.merge(cnf))
11
+ @rake.options.build_all = true
12
+ @rake.invoke_task('org:install')
13
+ return if File.exist? 'src/index.org'
14
+ Fronde::OrgFile.new('src/index.org', @options).write
15
+ fronde_open 'src/index.org'
16
+ end
17
+ alias_method :fronde_config, :fronde_init
18
+
19
+ def fronde_build
20
+ @rake.options.build_all = true
21
+ task = 'site:build'
22
+ task = "#{task}[true]" if @options[:force]
23
+ @rake.invoke_task task
24
+ end
25
+
26
+ def fronde_preview
27
+ Thread.new do
28
+ sleep 1
29
+ port = Fronde::Config.settings.dig('preview', 'server_port') || 5000
30
+ uri = "http://127.0.0.1:#{port}/"
31
+ current_os = Fronde::Utils.current_os
32
+ case current_os
33
+ when 'windows'
34
+ system 'start', uri
35
+ when 'apple'
36
+ system 'open', uri
37
+ else
38
+ system 'gio', 'open', uri
39
+ end
40
+ end
41
+ @rake.invoke_task('site:preview')
42
+ end
43
+
44
+ def fronde_open(file_path = ARGV[0])
45
+ editor = ENV['EDITOR'] || ENV['VISUAL'] || 'emacs'
46
+ cmd = [editor]
47
+ if file_path.nil? || !File.file?(file_path)
48
+ # file_path may be updated with title given in options
49
+ file_path = create_new_file(file_path)
50
+ # Only move to the end of file for new file. Let the editor handle
51
+ # the best position for already existing files
52
+ cmd << '+6'
53
+ end
54
+ cmd << file_path
55
+ system(*cmd)
56
+ end
57
+ alias_method :fronde_edit, :fronde_open
58
+
59
+ def fronde_publish
60
+ @rake.invoke_task('sync:push')
61
+ end
62
+
63
+ def fronde_help(command = 'basic', error: false)
64
+ warn R18n.t.fronde.bin.error.no_command if error
65
+ cmd = Fronde::Utils.resolve_possible_alias(command)
66
+ cmd_opt = Fronde::Utils::FRONDE_COMMANDS[cmd]
67
+ label = cmd_opt[:label] || command
68
+ warn format("%<label>s\n\n", label: R18n.t.fronde.bin.usage(label))
69
+ if R18n.t.fronde.bin.commands[cmd].translated?
70
+ warn format("%<label>s\n\n", label: R18n.t.fronde.bin.commands[cmd])
71
+ end
72
+ warn help_command_body(cmd).join("\n")
73
+ exit 1 if error
74
+ exit
75
+ end
76
+
77
+ private
78
+
79
+ def new_file_name(file_path)
80
+ file_path = File.expand_path(file_path || '')
81
+ return file_path if file_path[-4..] == '.org'
82
+ # file_path seems to be a dir path. Thus we have to create the new
83
+ # filename from its title
84
+ title = @options[:title]
85
+ # No title, nor a reliable file_path? Better abort
86
+ return nil if title.nil? || title == ''
87
+ filename = "#{Fronde::OrgFile.slug(title)}.org"
88
+ File.join file_path, filename
89
+ end
90
+
91
+ def create_new_file(file_path)
92
+ filename = new_file_name(file_path)
93
+ if filename.nil?
94
+ warn R18n.t.fronde.bin.error.no_file
95
+ exit 1
96
+ end
97
+ FileUtils.mkdir_p File.dirname(filename)
98
+ Fronde::OrgFile.new(filename, @options).write
99
+ filename
100
+ end
101
+
102
+ def help_command_body(command)
103
+ body = [
104
+ R18n.t.fronde.bin.options.cmd_title,
105
+ Fronde::Utils.summarize_command(command)
106
+ ]
107
+ return body unless command == 'basic'
108
+ body + [
109
+ '',
110
+ R18n.t.fronde.bin.commands.cmd_title,
111
+ Fronde::Utils.list_commands
112
+ ]
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'fronde/config/lisp_config'
5
+
6
+ module Fronde
7
+ # Wrapper for configuration
8
+ #
9
+ # This class is a singleton interface, which share the static website
10
+ # being build settings among different steps or tasks.
11
+ #
12
+ # It expects the website author to holds their custom settings in a
13
+ # YAML file named ~config.yml~ available at the root of their
14
+ # project.
15
+ #
16
+ # For example, with the given config file:
17
+ #
18
+ # #+begin_src
19
+ # ---
20
+ # title: My website
21
+ # author: Alice Doe
22
+ # #+end_src
23
+ #
24
+ # Settings will be available like this:
25
+ #
26
+ # #+begin_src
27
+ # Fronde::Config.settings['author']
28
+ # => "Alice Doe"
29
+ # #+end_src
30
+ class Config
31
+ extend Fronde::LispConfig
32
+
33
+ class << self
34
+ # Access the current website settings
35
+ #
36
+ # If the settings have not been loaded yet, this method is
37
+ # responsible for calling the one, which actually loads them.
38
+ #
39
+ # @return [Hash] the website settings
40
+ def settings
41
+ return load_settings unless @config
42
+ @config
43
+ end
44
+
45
+ # Save the settings given as a parameter to the ~config.yml~ file.
46
+ #
47
+ # Not only this method overwrite the old settings, but it replace
48
+ # the current shared settings with the ones given in
49
+ # parameter. Later call to
50
+ # {file:Fronde/Config.html#settings-class_method settings}
51
+ # will, obviously, use these new settings.
52
+ #
53
+ # @param new_config [Hash] the settings to save
54
+ # @return [Hash] the new settings after save
55
+ def save(new_config)
56
+ # Do not save obvious default config values. We'll always try to
57
+ # save author and lang as they default on system variables,
58
+ # which may be different from a system to another. Thus it may
59
+ # be confusing if one use fronde on two different computer and
60
+ # these params always change.
61
+ new_config.delete_if do |k, v|
62
+ ['domain', 'public_folder', 'templates', 'theme'].include?(k) \
63
+ && v == default_settings[k]
64
+ end
65
+ IO.write 'config.yml', new_config.to_yaml
66
+ load_settings # Reload config, taking default settings into account
67
+ end
68
+
69
+ # Load the given settings as if they comes from the ~config.yml~ file.
70
+ #
71
+ # This method is handy for testing purpose. Later call to
72
+ # {file:Fronde/Config.html#settings-class_method settings} will
73
+ # use these new settings.
74
+ #
75
+ # @param config [Hash] the settings to artificially load
76
+ # @return [Hash] the new settings
77
+ def load_test(config)
78
+ @sources = nil # Reset sources
79
+ @config = default_settings.merge config
80
+ end
81
+
82
+ # Return the qualified projects sources list.
83
+ #
84
+ # @return [Array] the fully qualified projects sources list
85
+ def sources
86
+ return @sources if @sources
87
+ default_sources = [{ 'path' => 'src', 'target' => '.' }]
88
+ @sources = (settings['sources'] || default_sources).map do |s|
89
+ build_source(s)
90
+ end.compact
91
+ end
92
+
93
+ private
94
+
95
+ def load_settings
96
+ @sources = nil
97
+ conf_file = 'config.yml'
98
+ if File.exist? conf_file
99
+ @config = default_settings.merge(YAML.load_file(conf_file)).freeze
100
+ else
101
+ @config = default_settings
102
+ end
103
+ end
104
+
105
+ def extract_lang_from_env(default)
106
+ (ENV['LANG'] || default).split('_', 2).first
107
+ end
108
+
109
+ def default_settings
110
+ return @default_settings if @default_settings
111
+ @default_settings = {
112
+ 'author' => (ENV['USER'] || ''),
113
+ 'domain' => '',
114
+ 'lang' => extract_lang_from_env('en'),
115
+ 'public_folder' => 'public_html',
116
+ 'templates' => [],
117
+ 'theme' => 'default'
118
+ }.freeze
119
+ end
120
+
121
+ def build_source(seed)
122
+ opts = { 'recursive' => true, 'is_blog' => false }
123
+ case seed
124
+ when String
125
+ opts['path'] = seed
126
+ when Hash
127
+ opts.merge! seed
128
+ end
129
+ return nil unless opts.has_key?('path')
130
+ opts['path'] = File.expand_path(opts['path'])
131
+ opts['name'] ||= File.basename(opts['path']).sub(/^\./, '')
132
+ opts['target'] ||= opts['name']
133
+ opts
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,269 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'open-uri'
5
+ require 'fronde/version'
6
+
7
+ module Fronde
8
+ # This module contains utilitary methods to ease ~org-config.el~
9
+ # file generation
10
+ module LispConfig
11
+ # Fetch and return the last published version of Org.
12
+ #
13
+ # @return [String] the new x.x.x version string of Org
14
+ def org_last_version
15
+ return @org_version if @org_version
16
+ if File.exist?('var/tmp/last_org_version')
17
+ @org_version = IO.read('var/tmp/last_org_version')
18
+ return @org_version
19
+ end
20
+ versions = JSON.parse(
21
+ URI('https://updates.orgmode.org/data/releases').open.read
22
+ ).sort { |a, b| b['date'] <=> a['date'] }
23
+ @org_version = versions.first['version']
24
+ FileUtils.mkdir_p 'var/tmp'
25
+ IO.write('var/tmp/last_org_version', @org_version)
26
+ @org_version
27
+ end
28
+
29
+ # Generate emacs lisp configuration file for Org and write it.
30
+ #
31
+ # This method saves the generated configuration in the file
32
+ # ~org-config.el~ at the root of your project, overwriting it if it
33
+ # existed already.
34
+ #
35
+ # @return [Integer] the length written (as returned by the
36
+ # underlying ~IO.write~ method call)
37
+ def write_org_lisp_config(with_tags: false)
38
+ projects = org_generate_projects(with_tags: with_tags)
39
+ workdir = Dir.pwd
40
+ content = IO.read(File.expand_path('./org-config.el', __dir__))
41
+ .gsub('__VERSION__', Fronde::VERSION)
42
+ .gsub('__WORK_DIR__', workdir)
43
+ .gsub('__FRONDE_DIR__', __dir__)
44
+ .gsub('__ORG_VER__', org_last_version)
45
+ .gsub('__ALL_PROJECTS__', all_projects(projects))
46
+ .gsub('__THEME_CONFIG__', org_default_theme_config)
47
+ .gsub('__ALL_PROJECTS_NAMES__', project_names(projects))
48
+ .gsub('__LONG_DATE_FMT__', r18n_full_datetime_format)
49
+ .gsub('__AUTHOR_EMAIL__', settings['author_email'] || '')
50
+ .gsub('__AUTHOR_NAME__', settings['author'])
51
+ FileUtils.mkdir_p "#{workdir}/var/lib"
52
+ IO.write("#{workdir}/var/lib/org-config.el", content)
53
+ end
54
+
55
+ # Generate emacs directory variables file.
56
+ #
57
+ # This method generate the file ~.dir-locals.el~, which is
58
+ # responsible to load fronde Org settings when visiting an Org file
59
+ # of this fronde instance.
60
+ #
61
+ # @return [Integer] the length written (as returned by the
62
+ # underlying ~IO.write~ method call)
63
+ def write_dir_locals
64
+ workdir = Dir.pwd
65
+ # rubocop:disable Layout/LineLength
66
+ IO.write(
67
+ "#{workdir}/.dir-locals.el",
68
+ "((org-mode . ((eval . (load-file \"#{workdir}/var/lib/org-config.el\")))))"
69
+ )
70
+ # rubocop:enable Layout/LineLength
71
+ end
72
+
73
+ private
74
+
75
+ def r18n_full_datetime_format
76
+ locale = R18n.get.locale
77
+ date_fmt = R18n.t.fronde.index.full_date_format(
78
+ date: locale.full_format
79
+ )
80
+ date_fmt = locale.year_format.sub('_', date_fmt)
81
+ time_fmt = locale.time_format.delete('_').strip
82
+ R18n.t.fronde.index.full_date_with_time_format(
83
+ date: date_fmt, time: time_fmt
84
+ )
85
+ end
86
+
87
+ def ruby_to_lisp_boolean(value)
88
+ return 't' if value == true
89
+ 'nil'
90
+ end
91
+
92
+ def project_names(projects)
93
+ names = projects.keys.map do |p|
94
+ ["\"#{p}\"", "\"#{p}-assets\""]
95
+ end.flatten
96
+ unless settings['theme'] == 'default'
97
+ names << "\"theme-#{settings['theme']}\""
98
+ end
99
+ sources.each do |s|
100
+ # Default theme defined in settings is already included
101
+ next unless s['theme'] && s['theme'] != settings['theme']
102
+ # Never include theme named 'default' as it does not rely on any
103
+ # file to export.
104
+ next if s['theme'] == 'default'
105
+ theme = "\"theme-#{s['theme']}\""
106
+ next if names.include? theme
107
+ names << theme
108
+ end
109
+ names.join(' ')
110
+ end
111
+
112
+ def all_projects(projects)
113
+ projects.values.join("\n").strip
114
+ .gsub(/\n\s*\n/, "\n")
115
+ .gsub(/\n/, "\n ")
116
+ end
117
+
118
+ # Return the full path to the publication path of a given project
119
+ # configuration.
120
+ #
121
+ # @param project [Hash] a project configuration (as extracted from
122
+ # the ~sources~ key)
123
+ # @return [String] the full path to the target dir of this project
124
+ def publication_path(project)
125
+ publish_in = [Dir.pwd, settings['public_folder']]
126
+ publish_in << project['target'] unless project['target'] == '.'
127
+ publish_in.join('/')
128
+ end
129
+
130
+ def org_project(project_name, opts)
131
+ publish_in = publication_path(opts)
132
+ other_lines = [
133
+ format(':recursive %<value>s',
134
+ value: ruby_to_lisp_boolean(opts['recursive']))
135
+ ]
136
+ if opts['exclude']
137
+ other_lines << format(':exclude "%<value>s"',
138
+ value: opts['exclude'])
139
+ end
140
+ themeconf = org_theme_config(opts['theme'])
141
+ <<~ORGPROJECT
142
+ ("#{project_name}"
143
+ :base-directory "#{opts['path']}"
144
+ :base-extension "org"
145
+ #{other_lines.join("\n ")}
146
+ :publishing-directory "#{publish_in}"
147
+ :publishing-function org-html-publish-to-html
148
+ #{opts['org_headers']})
149
+ ("#{project_name}-assets"
150
+ :base-directory "#{opts['path']}"
151
+ :base-extension "jpg\\\\\\|gif\\\\\\|png\\\\\\|svg\\\\\\|pdf"
152
+ #{other_lines[0]}
153
+ :publishing-directory "#{publish_in}"
154
+ :publishing-function org-publish-attachment)
155
+ #{themeconf}
156
+ ORGPROJECT
157
+ end
158
+
159
+ def org_default_postamble
160
+ <<~POSTAMBLE
161
+ <p><span class="author">#{R18n.t.fronde.org.postamble.written_by}</span>
162
+ #{R18n.t.fronde.org.postamble.with_emacs}</p>
163
+ <p class="date">#{R18n.t.fronde.org.postamble.last_modification}</p>
164
+ <p class="validation">%v</p>
165
+ POSTAMBLE
166
+ end
167
+
168
+ def org_default_html_head
169
+ <<~HTMLHEAD
170
+ <link rel="stylesheet" type="text/css" media="screen"
171
+ href="__DOMAIN__/assets/__THEME__/css/style.css">
172
+ <link rel="stylesheet" type="text/css" media="screen"
173
+ href="__DOMAIN__/assets/__THEME__/css/htmlize.css">
174
+ __ATOM_FEED__
175
+ HTMLHEAD
176
+ end
177
+
178
+ def org_default_html_options(project)
179
+ defaults = {
180
+ 'section-numbers' => 'nil',
181
+ 'with-toc' => 'nil',
182
+ 'html-postamble' => org_default_postamble,
183
+ 'html-head' => '__ATOM_FEED__',
184
+ 'html-head-include-default-style' => 't',
185
+ 'html-head-include-scripts' => 't'
186
+ }
187
+ curtheme = project['theme'] || settings['theme']
188
+ return defaults if curtheme.nil? || curtheme == 'default'
189
+ defaults['html-head'] = org_default_html_head
190
+ defaults['html-head-include-default-style'] = 'nil'
191
+ defaults['html-head-include-scripts'] = 'nil'
192
+ defaults
193
+ end
194
+
195
+ def expand_vars_in_html_head(head, project)
196
+ curtheme = project['theme'] || settings['theme']
197
+ # Head may be frozen when coming from settings
198
+ head = head.gsub('__THEME__', curtheme)
199
+ .gsub('__DOMAIN__', settings['domain'])
200
+ return head.gsub('__ATOM_FEED__', '') unless project['is_blog']
201
+ atomfeed = <<~ATOMFEED
202
+ <link rel="alternate" type="application/atom+xml" title="Atom 1.0"
203
+ href="#{settings['domain']}/feeds/index.xml" />
204
+ ATOMFEED
205
+ head.gsub('__ATOM_FEED__', atomfeed)
206
+ end
207
+
208
+ def cast_lisp_value(value)
209
+ return 't' if value.is_a?(TrueClass)
210
+ return 'nil' if value.nil? || value.is_a?(FalseClass)
211
+ value.strip.gsub(/"/, '\"')
212
+ end
213
+
214
+ def build_project_org_headers(project)
215
+ orgtplopts = org_default_html_options(project).merge(
216
+ settings['org-html'] || {}, project['org-html'] || {}
217
+ )
218
+ orgtpl = []
219
+ lisp_keywords = ['t', 'nil', '1', '-1', '0'].freeze
220
+ orgtplopts.each do |k, v|
221
+ v = expand_vars_in_html_head(v, project) if k == 'html-head'
222
+ val = cast_lisp_value(v)
223
+ if lisp_keywords.include? val
224
+ orgtpl << ":#{k} #{val}"
225
+ else
226
+ orgtpl << ":#{k} \"#{val}\""
227
+ end
228
+ end
229
+ orgtpl.join("\n ")
230
+ end
231
+
232
+ def org_generate_projects(with_tags: false)
233
+ projects = {}
234
+ projects_sources = sources
235
+ if with_tags
236
+ tags_conf = build_source('tags')
237
+ tags_conf['recursive'] = false
238
+ projects_sources << tags_conf
239
+ end
240
+ projects_sources.each do |opts|
241
+ opts['org_headers'] = build_project_org_headers(opts)
242
+ projects[opts['name']] = org_project(opts['name'], opts)
243
+ end
244
+ projects
245
+ end
246
+
247
+ def org_default_theme_config
248
+ theme_config = org_theme_config(settings['theme'])
249
+ return theme_config if theme_config == ''
250
+ output = theme_config.split("\n").map do |line|
251
+ " #{line}"
252
+ end
253
+ format("\n%<conf>s", conf: output.join("\n"))
254
+ end
255
+
256
+ def org_theme_config(theme)
257
+ return '' if theme.nil? || theme == 'default'
258
+ workdir = Dir.pwd
259
+ <<~THEMECONFIG
260
+ ("theme-#{theme}"
261
+ :base-directory "#{workdir}/themes/#{theme}"
262
+ :base-extension "jpg\\\\\\|gif\\\\\\|png\\\\\\|js\\\\\\|css\\\\\\|otf\\\\\\|ttf\\\\\\|woff2?"
263
+ :recursive t
264
+ :publishing-directory "#{workdir}/#{settings['public_folder']}/assets/#{theme}"
265
+ :publishing-function org-publish-attachment)
266
+ THEMECONFIG
267
+ end
268
+ end
269
+ end