fronde 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) 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 +17 -0
  5. data/lib/ext/time.rb +49 -0
  6. data/lib/fronde/cli/commands.rb +92 -103
  7. data/lib/fronde/cli/data/Rakefile +8 -0
  8. data/lib/fronde/cli/data/config.yml +13 -0
  9. data/lib/fronde/cli/data/gitignore +7 -0
  10. data/lib/fronde/cli/data/zsh_completion +37 -0
  11. data/lib/fronde/cli/helpers.rb +55 -0
  12. data/lib/fronde/cli/opt_parse.rb +143 -0
  13. data/lib/fronde/cli/throbber.rb +99 -0
  14. data/lib/fronde/cli.rb +41 -42
  15. data/lib/fronde/config/data/org-config.el +24 -0
  16. data/lib/fronde/config/{ox-fronde.el → data/ox-fronde.el} +1 -1
  17. data/lib/fronde/config/helpers.rb +80 -0
  18. data/lib/fronde/config/lisp.rb +70 -0
  19. data/lib/fronde/config.rb +135 -99
  20. data/lib/fronde/emacs.rb +23 -20
  21. data/lib/fronde/index/atom_generator.rb +55 -66
  22. data/lib/fronde/index/data/all_tags.org +14 -0
  23. data/lib/fronde/index/data/template.org +22 -0
  24. data/lib/fronde/index/data/template.xml +37 -0
  25. data/lib/fronde/index/org_generator.rb +70 -88
  26. data/lib/fronde/index.rb +56 -82
  27. data/lib/fronde/org/file.rb +287 -0
  28. data/lib/fronde/org/file_extracter.rb +98 -0
  29. data/lib/fronde/org.rb +103 -0
  30. data/lib/fronde/preview.rb +43 -39
  31. data/lib/fronde/slug.rb +27 -0
  32. data/lib/fronde/source/gemini.rb +39 -0
  33. data/lib/fronde/source/html.rb +67 -0
  34. data/lib/fronde/source.rb +204 -0
  35. data/lib/fronde/templater.rb +94 -71
  36. data/lib/fronde/version.rb +1 -1
  37. data/lib/tasks/cli.rake +33 -0
  38. data/lib/tasks/org.rake +62 -42
  39. data/lib/tasks/site.rake +68 -30
  40. data/lib/tasks/sync.rake +41 -21
  41. data/lib/tasks/tags.rake +11 -7
  42. data/locales/en.yml +60 -14
  43. data/locales/fr.yml +68 -14
  44. metadata +53 -110
  45. data/lib/fronde/config/lisp_config.rb +0 -340
  46. data/lib/fronde/config/org-config.el +0 -19
  47. data/lib/fronde/org_file/class_methods.rb +0 -72
  48. data/lib/fronde/org_file/extracter.rb +0 -72
  49. data/lib/fronde/org_file/htmlizer.rb +0 -43
  50. data/lib/fronde/org_file.rb +0 -298
  51. data/lib/fronde/utils.rb +0 -229
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rainbow'
4
+
5
+ module Fronde
6
+ module CLI
7
+ # Decorations for the command line
8
+ class Throbber
9
+ # @return [Hash] the possible throbber themes
10
+ THROBBER_FRAMES = {
11
+ 'basic' => '-\|/',
12
+ 'basicdots' => '⋯⋱⋮⋰',
13
+ 'moon' => '🌑🌒🌓🌔🌕🌖🌗🌘',
14
+ 'clock' => '🕛🕐🕑🕒🕓🕔🕕🕖🕗🕘🕙🕚',
15
+ 'bricks' => '⣷⣯⣟⡿⢿⣻⣽⣾',
16
+ 'points' => '·⁘⁛⁘',
17
+ 'quadrant' => '▙▛▜▟',
18
+ 'default' => ['⠁ ⠂ ⠄ ⡀ ⠄ ⠂ ⠁', '⠂ ⠁ ⠂ ⠄ ⡀ ⠄ ⠂', '⠄ ⠂ ⠁ ⠂ ⠄ ⡀ ⠄',
19
+ '⡀ ⠄ ⠂ ⠁ ⠂ ⠄ ⡀', '⠄ ⡀ ⠄ ⠂ ⠁ ⠂ ⠄', '⠂ ⠄ ⡀ ⠄ ⠂ ⠁ ⠂']
20
+ }.freeze
21
+
22
+ def initialize(thread, message)
23
+ @frames = select_frames
24
+ @thread = thread
25
+ @thread.abort_on_exception = false
26
+ @thread.report_on_exception = false
27
+ @message = message
28
+ end
29
+
30
+ def run
31
+ term_width = terminal_width
32
+ frames_len = @frames.length
33
+ current = 0
34
+ while @thread.alive?
35
+ sleep 0.1
36
+ frame = @frames[current % frames_len]
37
+ message = "#{@message} #{frame}"
38
+ print "#{message.ljust(term_width)}\r"
39
+ current += 1
40
+ end
41
+ @thread.join # Ensure any inner exception is re-raised
42
+ rescue RuntimeError => e
43
+ show_error
44
+ raise e
45
+ else
46
+ done = Rainbow(R18n.t.fronde.bin.done).green
47
+ puts "#{@message} #{done}".ljust(term_width)
48
+ end
49
+
50
+ class << self
51
+ # Animates strings in the user console to alert him that something
52
+ # is running in the background.
53
+ #
54
+ # The animation is chosen among a bunch of themes, with the
55
+ # configuration option ~throbber~ (retrieved via
56
+ # {Fronde::Config::Store#get}).
57
+ #
58
+ # @example
59
+ # long_stuff = Thread.new { very_long_operation }
60
+ # Fronde::CLI::Throbber.run(long_stuff, 'Computing hard stuff:')
61
+ #
62
+ # @param thread [Thread] the long-running operation to decorate
63
+ # @param message [String] the message to display before the throbber
64
+ # @return [void]
65
+ def run(thread, message)
66
+ throbber = new(thread, message)
67
+ throbber.run
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def terminal_width
74
+ # Not a tty. Docker?
75
+ return 0 unless system('test -t 0')
76
+
77
+ `stty size`.strip.split[1].to_i - 1
78
+ end
79
+
80
+ def show_error
81
+ warn(
82
+ format(
83
+ "%<message>s %<label>s\n%<explanation>s",
84
+ message: @message,
85
+ label: Rainbow(R18n.t.fronde.error.bin.label).bold.red,
86
+ explanation: Rainbow(R18n.t.fronde.error.bin.explanation).bold
87
+ )
88
+ )
89
+ end
90
+
91
+ def select_frames
92
+ model = Fronde::CONFIG.get 'throbber', 'default'
93
+ model = 'default' unless THROBBER_FRAMES.has_key?(model)
94
+
95
+ THROBBER_FRAMES[model]
96
+ end
97
+ end
98
+ end
99
+ end
data/lib/fronde/cli.rb CHANGED
@@ -1,60 +1,59 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rake'
4
- require 'fronde/cli/commands'
4
+ require_relative 'cli/commands'
5
5
 
6
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
7
+ module CLI
8
+ # Fronde CLI app
9
+ class App
10
+ def initialize(opts = {})
11
+ @options = { verbose: false }.merge(opts)
12
+ @command = @rake = nil
13
+ @argv = []
14
+ end
14
15
 
15
- include Fronde::CLICommands
16
+ include Commands
16
17
 
17
- private
18
+ def run(argv)
19
+ @argv = argv
20
+ @command = OptParse.resolve_possible_alias(@argv.shift || 'basic')
18
21
 
19
- def init_required_files
20
- init_rakefile unless File.exist?('Rakefile')
21
- init_gitignore unless File.exist?('.gitignore')
22
- end
22
+ if help_param_given?
23
+ return 2 if @options[:recover_from_error]
24
+ return 0
25
+ end
23
26
 
24
- def init_rake
25
- @rake = Rake.application
26
- Rake.verbose(false) unless @options[:verbose]
27
- @rake.raw_load_rakefile
28
- end
27
+ init_rake if %w[build preview publish].include?(@command)
29
28
 
30
- def init_rakefile
31
- rakefile = <<~RAKE
32
- # frozen_string_literal: true
29
+ method = "fronde_#{@command}".to_sym
30
+ return 2 if method_unknown?(method)
33
31
 
34
- require 'fronde/config'
35
- require 'r18n-core'
32
+ send method
33
+ end
36
34
 
37
- fronde_spec = Gem::Specification.find_by_name 'fronde'
38
- R18n.default_places = "\#{fronde_spec.gem_dir}/locales"
39
- R18n.set(Fronde::Config.get('lang', 'en'))
40
- R18n::Filters.on(:named_variables)
35
+ private
41
36
 
42
- Dir.glob("\#{fronde_spec.gem_dir}/lib/tasks/*.rake").each { |r| import r }
37
+ def init_rake
38
+ @rake = Rake.application
39
+ Rake.verbose(false) unless @options[:verbose]
40
+ @rake.load_rakefile
41
+ end
43
42
 
44
- task default: 'site:build'
45
- RAKE
46
- File.write 'Rakefile', rakefile
47
- end
43
+ def help_param_given?
44
+ return false unless @options[:help]
45
+
46
+ fronde_help
47
+ true
48
+ end
49
+
50
+ def method_unknown?(method)
51
+ return false if respond_to?(method)
48
52
 
49
- def init_gitignore
50
- gitignore = <<~GITIGNORE
51
- .dir-locals.el
52
- Rakefile
53
- lib
54
- public_html
55
- var
56
- GITIGNORE
57
- File.write '.gitignore', gitignore
53
+ warn R18n.t.fronde.error.bin.no_command
54
+ fronde_help
55
+ true
56
+ end
58
57
  end
59
58
  end
60
59
  end
@@ -0,0 +1,24 @@
1
+ ;; Add org-mode to load path
2
+ (add-to-list 'load-path (expand-file-name "org-{{ org_version }}/lisp" "{{ work_dir }}/lib"))
3
+ ;; Load last version of htmlize.el
4
+ (load-file (expand-file-name "htmlize.el" "{{ work_dir }}/lib"))
5
+
6
+ ;; Current project options
7
+ (setq fronde/version "{{ version }}"
8
+ fronde/current-work-dir "{{ work_dir }}"
9
+ user-mail-address "{{ author.email }}"
10
+ user-full-name "{{ author.name }}"
11
+ org-html-metadata-timestamp-format "{{ long_date_fmt }}"
12
+ org-gmi-timestamp-format "{{ long_date_fmt }}"
13
+ org-publish-project-alist
14
+ `({% for project in all_projects -%}
15
+ ("{{ project.name }}"
16
+ {%- for attribute in project.attributes %}
17
+ :{{ attribute[0] }} {{ attribute[1] | cast_lisp_value: attribute[0] }}
18
+ {%- endfor %})
19
+ {% endfor -%}
20
+ ("website" :components ("{{ all_projects | map: 'name' | join: '" "' | remove: '" "tags' }}"))))
21
+
22
+ ;; Load fronde lib
23
+ (load-file (expand-file-name "ox-gmi.el" "{{ work_dir }}/lib"))
24
+ (load-file (expand-file-name "ox-fronde.el" "{{ fronde_data_dir }}"))
@@ -60,7 +60,7 @@ INFO is a plist used as a communication channel."
60
60
  output)
61
61
  (push `(?l . ,(org-export-data (plist-get info :language) info)) output)
62
62
  (push `(?n . ,(format "Fronde %s" fronde/version)) output)
63
- (push `(?N . ,(format "<a href=\"https://git.umaneti.net/fronde/about/\">Fronde</a> %s" fronde/version)) output)
63
+ (push `(?N . ,(format "<a href=\"https://etienne.depar.is/fronde/\">Fronde</a> %s" fronde/version)) output)
64
64
  (push `(?x . ,(org-export-data (plist-get info :description) info)) output)
65
65
  (push `(?X . ,(format "<p>%s</p>"
66
66
  (org-export-data (plist-get info :description) info)))
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'liquid'
4
+ require 'digest/md5'
5
+
6
+ module Fronde
7
+ module Config
8
+ # Various utilitaries methods
9
+ module Helpers
10
+ def self.extract_lang_from_env(default)
11
+ (ENV['LANG'] || default).split('_', 2).first
12
+ end
13
+
14
+ def self.migrate(config)
15
+ return config unless config.has_key?('public_folder')
16
+
17
+ warn R18n.t.fronde.error.config.deprecated_public_folder
18
+ old_pub_folder = config.delete('public_folder')
19
+ return config if config.has_key?('html_public_folder')
20
+
21
+ config['html_public_folder'] = old_pub_folder
22
+ config
23
+ end
24
+
25
+ def self.ensure_expanded_paths(config)
26
+ %w[html gemini].each do |what|
27
+ key = "#{what}_public_folder"
28
+ config[key] = File.expand_path config[key]
29
+ end
30
+ config
31
+ end
32
+
33
+ # Generate emacs directory variables file.
34
+ #
35
+ # This method generate the file ~.dir-locals.el~, which is
36
+ # responsible to load fronde Org settings when visiting an Org file
37
+ # of this fronde instance.
38
+ #
39
+ # @return [Integer] the length written (as returned by the
40
+ # underlying ~File.write~ method call)
41
+ def self.write_dir_locals
42
+ workdir = Dir.pwd
43
+ # rubocop:disable Layout/LineLength
44
+ File.write(
45
+ "#{workdir}/.dir-locals.el",
46
+ "((org-mode . ((eval . (load-file \"#{workdir}/var/lib/org-config.el\")))))"
47
+ )
48
+ # rubocop:enable Layout/LineLength
49
+ end
50
+
51
+ def self.render_liquid_template(content, data)
52
+ template = Liquid::Template.parse(content)
53
+ template.render(data)
54
+ end
55
+ end
56
+
57
+ # Filter for liquid templates
58
+ module Filters
59
+ def cast_lisp_value(value, key)
60
+ return 't' if value.is_a?(TrueClass)
61
+ return 'nil' if value.nil? || value.is_a?(FalseClass)
62
+
63
+ value = value.strip
64
+ lisp_keywords = ['t', 'nil', '1', '-1', '0']
65
+ if key.end_with?('-function') || lisp_keywords.include?(value)
66
+ return value
67
+ end
68
+
69
+ value.gsub!('"', '\"')
70
+ %("#{value}")
71
+ end
72
+
73
+ def md5(value)
74
+ Digest::MD5.hexdigest(value)
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ Liquid::Template.register_filter(Fronde::Config::Filters)
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'open-uri'
5
+ require_relative '../version'
6
+ require_relative '../org'
7
+ require_relative 'helpers'
8
+
9
+ require_relative '../../ext/r18n'
10
+ using R18nPatch
11
+
12
+ module Fronde
13
+ module Config
14
+ # This module contains utilitary methods to ease ~org-config.el~
15
+ # file generation
16
+ module Lisp
17
+ # Generate emacs lisp configuration file for Org and write it.
18
+ #
19
+ # This method saves the generated configuration in the file
20
+ # ~org-config.el~ at the root of your project, overwriting it if it
21
+ # existed already.
22
+ #
23
+ # @return [Integer] the length written (as returned by the
24
+ # underlying ~File.write~ method call)
25
+ # rubocop:disable Metrics/MethodLength
26
+ def write_org_lisp_config
27
+ workdir = Dir.pwd
28
+ all_projects = sources.map(&:org_config).flatten
29
+ all_themes = org_generate_themes(all_projects)
30
+ FileUtils.mkdir_p "#{workdir}/var/lib"
31
+ content = Helpers.render_liquid_template(
32
+ File.read(File.expand_path('./data/org-config.el', __dir__)),
33
+ 'version' => Fronde::VERSION,
34
+ 'work_dir' => workdir,
35
+ 'fronde_data_dir' => File.expand_path('data', __dir__),
36
+ 'org_version' => Fronde::Org.current_version,
37
+ 'long_date_fmt' => R18n.t.full_datetime_format.to_s,
38
+ 'author' => { 'email' => get('author_email', ''),
39
+ 'name' => get('author') },
40
+ 'all_projects' => all_projects + all_themes
41
+ )
42
+ File.write("#{workdir}/var/lib/org-config.el", content)
43
+ end
44
+ # rubocop:enable Metrics/MethodLength
45
+
46
+ private
47
+
48
+ def org_theme_config(theme)
49
+ { 'base-directory' => File.expand_path("themes/#{theme}"),
50
+ # rubocop:disable Layout/LineLength
51
+ 'base-extension' => %w[css js gif jpg png svg otf ttf woff2?].join('\\\\|'),
52
+ 'publishing-directory' => "#{get('html_public_folder')}/assets/#{theme}",
53
+ # rubocop:enable Layout/LineLength
54
+ 'publishing-function' => 'org-publish-attachment',
55
+ 'recursive' => true }
56
+ end
57
+
58
+ def org_generate_themes(projects)
59
+ all_themes = projects.filter_map { |project| project['theme'] }
60
+ all_themes << get('theme')
61
+ all_themes.uniq.compact.filter_map do |theme|
62
+ next if theme == 'default'
63
+
64
+ { 'name' => "theme-#{theme}",
65
+ 'attributes' => org_theme_config(theme) }
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
data/lib/fronde/config.rb CHANGED
@@ -1,44 +1,62 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'yaml'
4
- require 'fronde/config/lisp_config'
4
+ require 'r18n-core'
5
+ require 'singleton'
6
+
7
+ require_relative 'config/lisp'
8
+ require_relative 'source'
5
9
 
6
10
  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.get('author')
28
- # => "Alice Doe"
29
- # #+end_src
30
- class Config
31
- extend Fronde::LispConfig
32
-
33
- class << self
11
+ module Config
12
+ # Wrapper for configuration
13
+ #
14
+ # This class is a singleton interface, which share the static website
15
+ # being build settings among different steps or tasks.
16
+ #
17
+ # It expects the website author to holds their custom settings in a
18
+ # YAML file named ~config.yml~ available at the root of their
19
+ # project.
20
+ #
21
+ # For example, with the given config file:
22
+ #
23
+ # #+begin_src
24
+ # ---
25
+ # title: My website
26
+ # author: Alice Doe
27
+ # #+end_src
28
+ #
29
+ # Settings will be available like this:
30
+ #
31
+ # #+begin_src
32
+ # Fronde::CONFIG.get('author')
33
+ # => "Alice Doe"
34
+ # #+end_src
35
+ class Store
36
+ include Singleton
37
+
38
+ attr_reader :sources
39
+
40
+ def initialize
41
+ @default_settings = {
42
+ 'author' => (ENV['USER'] || ''),
43
+ 'domain' => '',
44
+ 'lang' => Fronde::Config::Helpers.extract_lang_from_env('en'),
45
+ 'html_public_folder' => 'public_html',
46
+ 'gemini_public_folder' => 'public_gmi',
47
+ 'templates' => [], 'theme' => 'default'
48
+ }.freeze
49
+ @org_version = @sources = nil
50
+ @config = load_settings
51
+ # Do not load sources now to avoid dependency loop on config
52
+ end
53
+
54
+ include Fronde::Config::Lisp
55
+
34
56
  # Access the current website settings
35
57
  #
36
- # If necessary, this method will load settings from a config
37
- # file.
38
- #
39
58
  # @return [Hash] the website settings
40
59
  def settings
41
- load_settings
42
60
  @config
43
61
  end
44
62
 
@@ -56,7 +74,6 @@ module Fronde
56
74
  # @param default the default value to use if ~setting~ is absent
57
75
  # @return the setting value or nil
58
76
  def get(setting, default = nil)
59
- load_settings
60
77
  if setting.is_a? Array
61
78
  value = @config.dig(*setting)
62
79
  else
@@ -65,31 +82,6 @@ module Fronde
65
82
  value || default
66
83
  end
67
84
 
68
- # Save the settings given as a parameter to the ~config.yml~ file.
69
- #
70
- # Not only this method overwrite the old settings, but it replace
71
- # the current shared settings with the ones given in
72
- # parameter. Later call to
73
- # {file:Fronde/Config.html#settings-class_method settings}
74
- # will, obviously, use these new settings.
75
- #
76
- # @param new_config [Hash] the settings to save
77
- # @return [Hash] the new settings after save
78
- def save(new_config)
79
- # Do not save obvious default config values. We'll always try to
80
- # save author and lang as they default on system variables,
81
- # which may be different from a system to another. Thus it may
82
- # be confusing if one use fronde on two different computer and
83
- # these params always change.
84
- default_keys = default_settings.keys
85
- new_config.delete_if do |k, v|
86
- default_keys.include?(k) && v == default_settings[k]
87
- end
88
- File.write 'config.yml', new_config.to_yaml
89
- @config = @sources = nil
90
- load_settings # Reload config, taking default settings into account
91
- end
92
-
93
85
  # Reset settings
94
86
  #
95
87
  # This method is handy for testing purpose. Next call to
@@ -99,7 +91,10 @@ module Fronde
99
91
  #
100
92
  # @return nil
101
93
  def reset
102
- @sources = @config = nil
94
+ # Reload config, taking default settings into account
95
+ @config = load_settings
96
+ @org_version = @sources = nil
97
+ @sources = load_sources
103
98
  end
104
99
 
105
100
  # Load the given settings as if they comes from the ~config.yml~ file.
@@ -111,68 +106,109 @@ module Fronde
111
106
  # use these new settings.
112
107
  #
113
108
  # @param config [Hash] the settings to artificially load
114
- # @return [Hash] the new settings
109
+ # @return [Fronde::Config::Store] self
115
110
  def load_test(config)
116
- reset
117
- @config = default_settings.merge config
118
- sources
119
- @config
111
+ @config = @default_settings.merge config
112
+ @org_version = @sources = nil
113
+ @sources = load_sources
114
+ self
120
115
  end
121
116
 
122
117
  # Return the qualified projects sources list.
123
118
  #
124
119
  # @return [Array] the fully qualified projects sources list
125
- def sources
120
+ def load_sources
126
121
  return @sources if @sources
127
- load_settings
128
- default_sources = [{ 'path' => 'src', 'target' => '.' }]
129
- @sources = get('sources', default_sources).map do |s|
130
- build_source(s)
131
- end.compact
122
+
123
+ @sources = remove_inclusion(remove_duplicate(build_sources))
132
124
  end
133
125
 
134
126
  private
135
127
 
136
128
  def load_settings
137
- return @config if @config
138
129
  conf_file = 'config.yml'
130
+ user_conf = {}
139
131
  if File.exist? conf_file
140
- @config = default_settings.merge(YAML.load_file(conf_file)).freeze
141
- else
142
- @config = default_settings
132
+ user_conf = YAML.load_file(conf_file)
133
+ user_conf = Fronde::Config::Helpers.migrate(user_conf)
143
134
  end
135
+ user_conf = @default_settings.merge(user_conf)
136
+ Fronde::Config::Helpers.ensure_expanded_paths(user_conf).freeze
144
137
  end
145
138
 
146
- def extract_lang_from_env(default)
147
- (ENV['LANG'] || default).split('_', 2).first
139
+ def build_sources
140
+ default_sources = [{ 'path' => 'src', 'target' => '.' }]
141
+ get('sources', default_sources).filter_map do |source_conf|
142
+ config = Source.canonical_config source_conf.dup
143
+ unless config['path']
144
+ warn R18n.t.fronde.error.source.no_path(source: config.inspect)
145
+ next
146
+ end
147
+ Source.new_from_config config
148
+ end
148
149
  end
149
150
 
150
- def default_settings
151
- return @default_settings if @default_settings
152
- @default_settings = {
153
- 'author' => (ENV['USER'] || ''),
154
- 'domain' => '',
155
- 'lang' => extract_lang_from_env('en'),
156
- 'public_folder' => 'public_html',
157
- 'templates' => [],
158
- 'theme' => 'default'
159
- }.freeze
151
+ def remove_duplicate(sources)
152
+ check_paths = {}
153
+ sources.each do |source|
154
+ type = source.type
155
+ check_paths[type] ||= {}
156
+ path = source['path']
157
+ # Avoid duplicate
158
+ if check_paths[type].has_key?(path)
159
+ warn(
160
+ R18n.t.fronde.error.source.duplicate(
161
+ source: source['name'], type: type
162
+ )
163
+ )
164
+ next
165
+ end
166
+ check_paths[type][path] = source
167
+ end
168
+ check_paths
160
169
  end
161
170
 
162
- def build_source(seed)
163
- opts = { 'recursive' => true, 'is_blog' => false }
164
- case seed
165
- when String
166
- opts['path'] = seed
167
- when Hash
168
- opts.merge! seed
169
- end
170
- return nil unless opts.has_key?('path')
171
- opts['path'] = File.expand_path(opts['path'])
172
- opts['name'] ||= File.basename(opts['path']).sub(/^\./, '')
173
- opts['target'] ||= opts['name']
174
- opts
171
+ def remove_inclusion(check_paths)
172
+ check_paths.map do |type, sources_by_path|
173
+ skip_paths = []
174
+
175
+ # Check paths in the right order
176
+ sorted_paths = sources_by_path.keys.sort_by(&:length)
177
+ sorted_paths.filter_map do |path|
178
+ next if skip_paths.include?(path)
179
+
180
+ source = sources_by_path[path]
181
+ # If current source is not recursive, there is no possible
182
+ # issue
183
+ next source unless source.recursive?
184
+
185
+ # Ensure that the current source does not embed another one
186
+ possible_matchs = sorted_paths.select do |other_path|
187
+ path != other_path && other_path.start_with?(path)
188
+ end
189
+ next source if possible_matchs.empty?
190
+
191
+ skip_paths += possible_matchs
192
+ possible_matchs.each do |match|
193
+ other_source = sources_by_path[match]
194
+ warn(
195
+ R18n.t.fronde.error.source.inclusion(
196
+ source: other_source['title'], type: type,
197
+ other_source: source['title']
198
+ )
199
+ )
200
+ end
201
+ end
202
+ end.flatten
175
203
  end
176
204
  end
177
205
  end
206
+
207
+ CONFIG = Config::Store.instance
178
208
  end
209
+
210
+ R18n.default_places = File.expand_path('../../locales', __dir__)
211
+ R18n::Filters.on(:named_variables)
212
+ R18n.set Fronde::CONFIG.get('lang')
213
+
214
+ Fronde::CONFIG.load_sources