fronde 0.3.0

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