neruda 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de0736f77b1fd461b05781a1afee1775cc0e0bf8fea098f14223475a28fb5282
4
- data.tar.gz: c3443f328c833e1591631243f4df6e7ff29dbc2fcceb3889bc0d0f763b41662f
3
+ metadata.gz: bf44dce40376682ca2835d1472ccd7eb2ea47d5e7b892623da09350077bf4a10
4
+ data.tar.gz: fcabfb117f2ac44a76fc325a84982be6dd6e389872f4fb6bfbd10bf05924d882
5
5
  SHA512:
6
- metadata.gz: 6239d642c54843825f9009514772452200acfd70ca3b65ed564b761fc24ae885ec7b0bf852b8bd947c8b24d325013342c3047019b83f33f98067d0162c0b3bef
7
- data.tar.gz: bd3528ca312d8435b360fb34436817e4ffbea7e3bd758a90ea74b5713d7ec457ee1844dc443459705f6c0c94c22aaaad313b8d6b8565cfacc6a4a309c33e63a2
6
+ metadata.gz: 7113b65a71b026c419a6de56d7ba2b84cdf9fbf6b02a977736dbad0484a225c2d97acb4df906c957e51a9c3b968b3260fb748856630fc534c4f30a7d5aa280d7
7
+ data.tar.gz: 1e478aa7c7c9ac80cb9293fb873baeadbfa25864cffb81460ac5e727b46360228f5eeb0f525116e88b35a537a2ae07c94f5b373e037095168c1b929ccc07bb9b
data/bin/pablo CHANGED
@@ -7,8 +7,8 @@ require 'r18n-core'
7
7
  require 'neruda/utils'
8
8
  require 'neruda/version'
9
9
 
10
- R18n.set(Neruda::Config.settings['lang'],
11
- File.expand_path('../locales', __dir__))
10
+ R18n.default_places = File.expand_path('../locales', __dir__)
11
+ R18n.set Neruda::Config.settings['lang']
12
12
 
13
13
  # Pablo commands
14
14
  module PabloCommands
@@ -20,8 +20,8 @@ module PabloCommands
20
20
  Neruda::Config.save(Neruda::Config.settings.merge(cnf))
21
21
  @rake.options.build_all = true
22
22
  @rake.invoke_task('org:install')
23
- o = Neruda::OrgFile.new('src/index.org', @options)
24
- o.write unless File.exist? 'src/index.org'
23
+ return if File.exist? 'src/index.org'
24
+ Neruda::OrgFile.new('src/index.org', @options).write
25
25
  end
26
26
  alias_method :pablo_config, :pablo_init
27
27
 
@@ -37,12 +37,13 @@ module PabloCommands
37
37
  def pablo_preview
38
38
  Thread.new do
39
39
  sleep 1
40
- port = Neruda::Config.settings['server_port'] || 5000
40
+ port = Neruda::Config.settings.dig('preview', 'server_port') || 5000
41
41
  uri = "http://127.0.0.1:#{port}/"
42
42
  current_os = Neruda::Utils.current_os
43
- if current_os == 'windows'
43
+ case current_os
44
+ when 'windows'
44
45
  system 'start', uri
45
- elsif current_os == 'apple'
46
+ when 'apple'
46
47
  system 'open', uri
47
48
  else
48
49
  system 'gio', 'open', uri
@@ -57,8 +58,7 @@ module PabloCommands
57
58
  o = Neruda::OrgFile.new(filename, @options)
58
59
  o.write unless File.exist? filename
59
60
  editor = ENV['EDITOR'] || ENV['VISUAL'] || 'emacs'
60
- cmd = [editor, filename]
61
- cmd.insert(1, '+6') if editor.match?(/^emacs/)
61
+ cmd = [editor, '+6', filename]
62
62
  system(*cmd)
63
63
  end
64
64
  alias_method :pablo_edit, :pablo_open
@@ -67,15 +67,16 @@ module PabloCommands
67
67
  @rake.invoke_task('sync:push')
68
68
  end
69
69
 
70
- def pablo_help(command = 'basic', error = false)
70
+ def pablo_help(command = 'basic', error: false)
71
71
  warn R18n.t.pablo.error.no_command if error
72
72
  cmd = Neruda::Utils.resolve_possible_alias(command)
73
73
  cmd_opt = Neruda::Utils::PABLO_COMMANDS[cmd]
74
74
  label = cmd_opt[:label] || command
75
- warn "Usage: pablo #{label} [options]\n\n"
76
- warn cmd_opt[:desc] + "\n\n" if cmd_opt.has_key?(:desc)
77
- warn "Options\n#{Neruda::Utils.summarize_command(cmd)}\n"
78
- warn "Commands\n#{Neruda::Utils.list_commands}\n" if cmd == 'basic'
75
+ warn format("%<label>s\n\n", label: R18n.t.pablo.usage(label))
76
+ if R18n.t.pablo.commands[cmd].translated?
77
+ warn format("%<label>s\n\n", label: R18n.t.pablo.commands[cmd])
78
+ end
79
+ warn help_command_body(cmd).join("\n")
79
80
  exit 1 if error
80
81
  exit
81
82
  end
@@ -89,6 +90,19 @@ module PabloCommands
89
90
  return "#{filename}.org" unless @options[:directory]
90
91
  "#{filename}/index.org"
91
92
  end
93
+
94
+ def help_command_body(command)
95
+ body = [
96
+ R18n.t.pablo.options.cmd_title,
97
+ Neruda::Utils.summarize_command(command)
98
+ ]
99
+ return body unless command == 'basic'
100
+ body + [
101
+ '',
102
+ R18n.t.pablo.commands.cmd_title,
103
+ Neruda::Utils.list_commands
104
+ ]
105
+ end
92
106
  end
93
107
 
94
108
  # Main pablo class
@@ -117,8 +131,8 @@ class Pablo
117
131
  require 'r18n-core'
118
132
 
119
133
  neruda_spec = Gem::Specification.find_by_name 'neruda'
120
- R18n.set(Neruda::Config.settings['lang'] || 'en',
121
- "\#{neruda_spec.gem_dir}/locales")
134
+ R18n.default_places = "\#{neruda_spec.gem_dir}/locales"
135
+ R18n.set(Neruda::Config.settings['lang'] || 'en')
122
136
  R18n::Filters.on(:named_variables)
123
137
 
124
138
  Dir.glob("\#{neruda_spec.gem_dir}/lib/tasks/*.rake").each { |r| import r }
@@ -153,10 +167,10 @@ command = "pablo_#{ARGV[0]}".to_sym
153
167
  cmd_err = !pablo.respond_to?(command)
154
168
  if params[:help] || cmd_err
155
169
  cmd_err = false if params[:help] && !ARGV[0]
156
- pablo.pablo_help(ARGV[0], cmd_err)
170
+ pablo.pablo_help(ARGV[0], error: cmd_err)
157
171
  end
158
172
  ARGV.shift
159
173
 
160
- init_cmds = ['pablo_init', 'pablo_config']
174
+ init_cmds = [:pablo_init, :pablo_config]
161
175
  pablo.pablo_init unless File.exist?('config.yml') || init_cmds.include?(command)
162
176
  pablo.send command
@@ -53,8 +53,17 @@ module Neruda
53
53
  # @param new_config [Hash] the settings to save
54
54
  # @return [Hash] the new settings after save
55
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 neruda 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
56
65
  IO.write 'config.yml', new_config.to_yaml
57
- @config = new_config.freeze
66
+ load_settings # Reload config, taking default settings into account
58
67
  end
59
68
 
60
69
  # Load the given settings as if they comes from the ~config.yml~ file.
@@ -66,28 +75,62 @@ module Neruda
66
75
  # @param config [Hash] the settings to artificially load
67
76
  # @return [Hash] the new settings
68
77
  def load_test(config)
69
- @config = config
70
- add_default_settings
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
71
91
  end
72
92
 
73
93
  private
74
94
 
75
95
  def load_settings
76
- @config = {}
77
- conf = 'config.yml'
78
- @config = YAML.load_file(conf) if File.exist? conf
79
- add_default_settings
80
- @config.freeze
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
81
119
  end
82
120
 
83
- def add_default_settings
84
- @config['lang'] ||= 'en'
85
- @config['author'] ||= (ENV['USER'] || '')
86
- @config['domain'] ||= ''
87
- @config['public_folder'] ||= 'public_html'
88
- @config['templates'] ||= []
89
- return if @config['blog_path'].nil?
90
- @config['blog_pattern'] ||= '**/*.org'
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
91
134
  end
92
135
  end
93
136
  end
@@ -1,28 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'open-uri'
4
+ require 'neruda/version'
4
5
 
5
6
  module Neruda
6
7
  # This module contains utilitary methods to ease ~org-config.el~
7
8
  # file generation
8
9
  module LispConfig
9
- # Fetch and return the last published version of org mode.
10
+ # Fetch and return the last published version of Org.
10
11
  #
11
- # @return [String] the new x.x.x version string of org mode
12
+ # @return [String] the new x.x.x version string of Org
12
13
  def org_last_version
13
14
  return @org_version if @org_version
14
- index = open('https://orgmode.org/index.html', 'r').read
15
+ if File.exist?('tmp/__last_org_version__')
16
+ @org_version = IO.read('tmp/__last_org_version__')
17
+ return @org_version
18
+ end
19
+ index = URI('https://orgmode.org/index.html').open.read
15
20
  last_ver = index.match(/https:\/\/orgmode\.org\/org-([0-9.]+)\.tar\.gz/)
16
21
  # :nocov:
17
22
  if last_ver.nil?
18
23
  warn 'Org last version not found'
19
24
  return nil
20
25
  end
26
+ FileUtils.mkdir_p 'tmp'
27
+ IO.write('tmp/__last_org_version__', last_ver[1])
21
28
  # :nocov:
22
29
  @org_version = last_ver[1]
23
30
  end
24
31
 
25
- # Generate emacs lisp configuration file for org mode and write it.
32
+ # Generate emacs lisp configuration file for Org and write it.
26
33
  #
27
34
  # This method saves the generated configuration in the file
28
35
  # ~org-config.el~ at the root of your project, overwriting it if it
@@ -30,15 +37,16 @@ module Neruda
30
37
  #
31
38
  # @return [Integer] the length written (as returned by the
32
39
  # underlying ~IO.write~ method call)
33
- def write_org_lisp_config
34
- projects = org_generate_projects
40
+ def write_org_lisp_config(with_tags: false)
41
+ projects = org_generate_projects(with_tags: with_tags)
35
42
  workdir = Dir.pwd
36
43
  content = IO.read(File.expand_path('./org-config.el', __dir__))
44
+ .gsub('__VERSION__', Neruda::VERSION)
37
45
  .gsub('__WORK_DIR__', workdir)
38
46
  .gsub('__NERUDA_DIR__', __dir__)
39
47
  .gsub('__ORG_VER__', org_last_version)
40
- .gsub('__ALL_PROJECTS__', all_projects(projects).strip)
41
- .gsub('__THEME_CONFIG__', org_theme_config.strip)
48
+ .gsub('__ALL_PROJECTS__', all_projects(projects))
49
+ .gsub('__THEME_CONFIG__', org_default_theme_config)
42
50
  .gsub('__ALL_PROJECTS_NAMES__', project_names(projects))
43
51
  .gsub('__LONG_DATE_FMT__', r18n_full_datetime_format)
44
52
  .gsub('__AUTHOR_EMAIL__', settings['author_email'] || '')
@@ -49,8 +57,8 @@ module Neruda
49
57
  # Generate emacs directory variables file.
50
58
  #
51
59
  # 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.
60
+ # responsible to load neruda Org settings when visiting an Org file
61
+ # of this neruda instance.
54
62
  #
55
63
  # @return [Integer] the length written (as returned by the
56
64
  # underlying ~IO.write~ method call)
@@ -76,67 +84,120 @@ module Neruda
76
84
  )
77
85
  end
78
86
 
87
+ def ruby_to_lisp_boolean(value)
88
+ return 't' if value == true
89
+ 'nil'
90
+ end
91
+
79
92
  def project_names(projects)
80
- projects.keys.map { |p| ["\"#{p}\"", "\"#{p}-assets\""] }
81
- .flatten.join(' ')
93
+ names = projects.keys.map do |p|
94
+ ["\"#{p}\"", "\"#{p}-assets\""]
95
+ end.flatten
96
+ names << "\"theme-#{settings['theme']}\""
97
+ sources.each do |s|
98
+ next unless s['theme'] && s['theme'] != settings['theme']
99
+ theme = "\"theme-#{s['theme']}\""
100
+ next if names.include? theme
101
+ names << theme
102
+ end
103
+ names.join(' ')
82
104
  end
83
105
 
84
106
  def all_projects(projects)
85
107
  projects.values.join("\n").strip
86
- .gsub(/\n\n/, "\n")
108
+ .gsub(/\n\s*\n/, "\n")
87
109
  .gsub(/\n/, "\n ")
88
110
  end
89
111
 
90
- def org_project(project_name, opts)
91
- orgtpl = opts['org_headers']
92
- base_directory = File.expand_path(opts['path'])
112
+ # Return the full path to the publication path of a given project
113
+ # configuration.
114
+ #
115
+ # @param project [Hash] a project configuration (as extracted from
116
+ # the ~sources~ key)
117
+ # @return [String] the full path to the target dir of this project
118
+ def publication_path(project)
93
119
  publish_in = [Dir.pwd, settings['public_folder']]
94
- publish_in << project_name unless project_name == 'neruda'
95
- publish_in = publish_in.join('/')
96
- recline = [opts['recursive'] || 't']
97
- default_ex_ptrn = settings['exclude_pattern']
120
+ publish_in << project['target'] unless project['target'] == '.'
121
+ publish_in.join('/')
122
+ end
123
+
124
+ def org_project(project_name, opts)
125
+ publish_in = publication_path(opts)
126
+ other_lines = [
127
+ format(':recursive %<value>s',
128
+ value: ruby_to_lisp_boolean(opts['recursive']))
129
+ ]
98
130
  if opts['exclude']
99
- recline << ":exclude \"#{opts['exclude']}\""
100
- elsif project_name == 'neruda' && default_ex_ptrn
101
- recline << ":exclude \"#{default_ex_ptrn}\""
131
+ other_lines << format(':exclude "%<value>s"',
132
+ value: opts['exclude'])
102
133
  end
134
+ themeconf = org_theme_config(opts['theme']) || ''
103
135
  <<~ORGPROJECT
104
136
  ("#{project_name}"
105
- :base-directory "#{base_directory}"
137
+ :base-directory "#{opts['path']}"
106
138
  :base-extension "org"
107
- :recursive #{recline.join("\n ")}
139
+ #{other_lines.join("\n ")}
108
140
  :publishing-directory "#{publish_in}"
109
141
  :publishing-function org-html-publish-to-html
110
142
  :section-numbers nil
111
143
  :with-toc nil
112
- #{orgtpl})
144
+ #{opts['org_headers']})
113
145
  ("#{project_name}-assets"
114
- :base-directory "#{base_directory}"
146
+ :base-directory "#{opts['path']}"
115
147
  :base-extension "jpg\\\\\\|gif\\\\\\|png\\\\\\|svg\\\\\\|pdf"
116
- :recursive #{recline[0]}
148
+ #{other_lines[0]}
117
149
  :publishing-directory "#{publish_in}"
118
150
  :publishing-function org-publish-attachment)
151
+ #{themeconf}
119
152
  ORGPROJECT
120
153
  end
121
154
 
122
- def org_default_theme_options
123
- postamble = <<~POSTAMBLE
155
+ def org_default_postamble
156
+ <<~POSTAMBLE
124
157
  <p><span class="author">#{R18n.t.neruda.org.postamble.written_by}</span>
125
158
  #{R18n.t.neruda.org.postamble.with_emacs}</p>
126
159
  <p class="date">#{R18n.t.neruda.org.postamble.last_modification}</p>
127
160
  <p class="validation">%v</p>
128
161
  POSTAMBLE
129
- { 'html-head' => build_html_head.strip,
130
- 'html-postamble' => postamble.strip,
162
+ end
163
+
164
+ def org_default_html_head
165
+ <<~HTMLHEAD
166
+ <link rel="stylesheet" type="text/css" media="screen"
167
+ href="__DOMAIN__/assets/__THEME__/css/style.css">
168
+ <link rel="stylesheet" type="text/css" media="screen"
169
+ href="__DOMAIN__/assets/__THEME__/css/htmlize.css">
170
+ __ATOM_FEED__
171
+ HTMLHEAD
172
+ end
173
+
174
+ def org_default_html_options
175
+ { 'html-head' => org_default_html_head,
176
+ 'html-postamble' => org_default_postamble,
131
177
  'html-head-include-default-style' => 't',
132
178
  'html-head-include-scripts' => 'nil' }
133
179
  end
134
180
 
135
- def org_templates
136
- orgtplopts = org_default_theme_options.merge
137
- orgtplopts.merge!(settings['org-html'] || {})
181
+ def expand_vars_in_html_head(head, project)
182
+ curtheme = project['theme'] || settings['theme']
183
+ # Head may be frozen when coming from settings
184
+ head = head.gsub('__THEME__', curtheme)
185
+ .gsub('__DOMAIN__', settings['domain'])
186
+ return head.gsub('__ATOM_FEED__', '') unless project['is_blog']
187
+ atomfeed = <<~ATOMFEED
188
+ <link rel="alternate" type="application/atom+xml" title="Atom 1.0"
189
+ href="#{settings['domain']}/feeds/index.xml" />
190
+ ATOMFEED
191
+ head.gsub('__ATOM_FEED__', atomfeed)
192
+ end
193
+
194
+ def build_project_org_headers(project)
195
+ orgtplopts = org_default_html_options.merge(
196
+ settings['org-html'] || {}, project['org-html'] || {}
197
+ )
138
198
  orgtpl = []
139
199
  orgtplopts.each do |k, v|
200
+ v = expand_vars_in_html_head(v, project) if k == 'html-head'
140
201
  val = v.strip.gsub(/"/, '\"')
141
202
  if ['t', 'nil', '1'].include? val
142
203
  orgtpl << ":#{k} #{val}"
@@ -147,62 +208,47 @@ module Neruda
147
208
  orgtpl.join("\n ")
148
209
  end
149
210
 
150
- def org_external_projects_opts(seed, orgtpl)
151
- opts = { 'org_headers' => orgtpl }
152
- if seed.is_a? String
153
- opts['path'] = seed
154
- elsif seed.is_a? Hash
155
- opts.merge! seed
211
+ def org_generate_projects(with_tags: false)
212
+ projects = {}
213
+ projects_sources = sources
214
+ if with_tags
215
+ tags_conf = build_source('tags')
216
+ tags_conf['recursive'] = false
217
+ projects_sources << tags_conf
156
218
  end
157
- opts
158
- end
159
-
160
- def org_generate_projects
161
- orgtpl = org_templates
162
- default_project = org_project(
163
- 'neruda', 'org_headers' => orgtpl, 'path' => './src'
164
- )
165
- projects = { 'neruda' => default_project }
166
- settings['external_sources']&.each do |s|
167
- opts = org_external_projects_opts(s, orgtpl)
168
- next unless opts.has_key?('path')
169
- pname = File.basename(opts['path']).sub(/^\./, '')
170
- projects[pname] = org_project(pname, opts)
219
+ projects_sources.each do |opts|
220
+ opts['org_headers'] = build_project_org_headers(opts)
221
+ projects[opts['name']] = org_project(opts['name'], opts)
171
222
  end
172
223
  projects
173
224
  end
174
225
 
175
- def org_theme_config
176
- curtheme = settings['theme'] || 'default'
226
+ def org_default_theme_config
227
+ org_theme_config(settings['theme']).split("\n").map do |line|
228
+ if line[0] == '('
229
+ line
230
+ else
231
+ " #{line}"
232
+ end
233
+ end.join("\n")
234
+ end
235
+
236
+ def org_theme_config(theme)
237
+ return nil if theme.nil?
177
238
  workdir = Dir.pwd
178
- if curtheme == 'default'
239
+ if theme == 'default'
179
240
  sourcedir = File.expand_path('../../../', __dir__)
180
241
  else
181
242
  sourcedir = workdir
182
243
  end
183
244
  <<~THEMECONFIG
184
- ("theme"
185
- :base-directory "#{sourcedir}/themes/#{curtheme}"
186
- :base-extension "jpg\\\\\\|gif\\\\\\|png\\\\\\|js\\\\\\|css\\\\\\|otf\\\\\\|ttf\\\\\\|woff2?"
187
- :recursive t
188
- :publishing-directory "#{workdir}/#{settings['public_folder']}/assets"
189
- :publishing-function org-publish-attachment)
245
+ ("theme-#{theme}"
246
+ :base-directory "#{sourcedir}/themes/#{theme}"
247
+ :base-extension "jpg\\\\\\|gif\\\\\\|png\\\\\\|js\\\\\\|css\\\\\\|otf\\\\\\|ttf\\\\\\|woff2?"
248
+ :recursive t
249
+ :publishing-directory "#{workdir}/#{settings['public_folder']}/assets/#{theme}"
250
+ :publishing-function org-publish-attachment)
190
251
  THEMECONFIG
191
252
  end
192
-
193
- def build_html_head
194
- stylesheet = <<~CSS
195
- <link rel="stylesheet" type="text/css" media="screen"
196
- href="#{settings['domain']}/assets/css/style.css">
197
- <link rel="stylesheet" type="text/css" media="screen"
198
- href="#{settings['domain']}/assets/css/htmlize.css">
199
- CSS
200
- return stylesheet if settings['blog_path'].nil?
201
- <<~ATOM
202
- #{stylesheet.strip}
203
- <link rel="alternate" type="application/atom+xml" title="Atom 1.0"
204
- href="#{settings['domain']}/feeds/index.xml" />
205
- ATOM
206
- end
207
253
  end
208
254
  end