fronde 0.3.3 → 0.4.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.
- checksums.yaml +4 -4
 - data/bin/fronde +15 -30
 - data/lib/ext/nil_time.rb +25 -0
 - data/lib/ext/r18n.rb +17 -0
 - data/lib/ext/time.rb +49 -0
 - data/lib/fronde/cli/commands.rb +92 -103
 - data/lib/fronde/cli/data/Rakefile +8 -0
 - data/lib/fronde/cli/data/config.yml +13 -0
 - data/lib/fronde/cli/data/gitignore +7 -0
 - data/lib/fronde/cli/data/zsh_completion +37 -0
 - data/lib/fronde/cli/helpers.rb +55 -0
 - data/lib/fronde/cli/opt_parse.rb +143 -0
 - data/lib/fronde/cli/throbber.rb +99 -0
 - data/lib/fronde/cli.rb +41 -42
 - data/lib/fronde/config/data/org-config.el +24 -0
 - data/lib/fronde/config/{ox-fronde.el → data/ox-fronde.el} +1 -1
 - data/lib/fronde/config/helpers.rb +80 -0
 - data/lib/fronde/config/lisp.rb +70 -0
 - data/lib/fronde/config.rb +135 -99
 - data/lib/fronde/emacs.rb +23 -20
 - data/lib/fronde/index/atom_generator.rb +55 -66
 - data/lib/fronde/index/data/all_tags.org +14 -0
 - data/lib/fronde/index/data/template.org +22 -0
 - data/lib/fronde/index/data/template.xml +37 -0
 - data/lib/fronde/index/org_generator.rb +70 -88
 - data/lib/fronde/index.rb +56 -82
 - data/lib/fronde/org/file.rb +287 -0
 - data/lib/fronde/org/file_extracter.rb +98 -0
 - data/lib/fronde/org.rb +103 -0
 - data/lib/fronde/preview.rb +43 -39
 - data/lib/fronde/slug.rb +27 -0
 - data/lib/fronde/source/gemini.rb +39 -0
 - data/lib/fronde/source/html.rb +67 -0
 - data/lib/fronde/source.rb +204 -0
 - data/lib/fronde/templater.rb +94 -71
 - data/lib/fronde/version.rb +1 -1
 - data/lib/tasks/cli.rake +33 -0
 - data/lib/tasks/org.rake +63 -43
 - data/lib/tasks/site.rake +68 -30
 - data/lib/tasks/sync.rake +41 -21
 - data/lib/tasks/tags.rake +11 -7
 - data/locales/en.yml +60 -14
 - data/locales/fr.yml +68 -14
 - metadata +57 -156
 - data/lib/fronde/config/lisp_config.rb +0 -340
 - data/lib/fronde/config/org-config.el +0 -19
 - data/lib/fronde/org_file/class_methods.rb +0 -72
 - data/lib/fronde/org_file/extracter.rb +0 -72
 - data/lib/fronde/org_file/htmlizer.rb +0 -43
 - data/lib/fronde/org_file.rb +0 -298
 - data/lib/fronde/utils.rb +0 -229
 
| 
         @@ -0,0 +1,287 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'time'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative '../../ext/nil_time'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require_relative '../../ext/time'
         
     | 
| 
      
 6 
     | 
    
         
            +
            using TimePatch
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            require 'nokogiri'
         
     | 
| 
      
 9 
     | 
    
         
            +
            require 'fileutils'
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            require_relative '../config'
         
     | 
| 
      
 12 
     | 
    
         
            +
            require_relative '../version'
         
     | 
| 
      
 13 
     | 
    
         
            +
            require_relative '../slug'
         
     | 
| 
      
 14 
     | 
    
         
            +
            require_relative 'file_extracter'
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            module Fronde
         
     | 
| 
      
 17 
     | 
    
         
            +
              module Org
         
     | 
| 
      
 18 
     | 
    
         
            +
                # Handles org files.
         
     | 
| 
      
 19 
     | 
    
         
            +
                #
         
     | 
| 
      
 20 
     | 
    
         
            +
                # This class is responsible for reading or writing existing or new
         
     | 
| 
      
 21 
     | 
    
         
            +
                # org files, and formating their content to be used on the generated
         
     | 
| 
      
 22 
     | 
    
         
            +
                # website.
         
     | 
| 
      
 23 
     | 
    
         
            +
                class File
         
     | 
| 
      
 24 
     | 
    
         
            +
                  # @return [String] the relative path to the source of this
         
     | 
| 
      
 25 
     | 
    
         
            +
                  #   document.
         
     | 
| 
      
 26 
     | 
    
         
            +
                  attr_reader :file
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  # @return [Hash] the project owning this document.
         
     | 
| 
      
 29 
     | 
    
         
            +
                  attr_reader :project
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  include FileExtracter
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  # Prepares the file named by ~file_name~ for read and write
         
     | 
| 
      
 34 
     | 
    
         
            +
                  #   operations.
         
     | 
| 
      
 35 
     | 
    
         
            +
                  #
         
     | 
| 
      
 36 
     | 
    
         
            +
                  # If the file ~file_name~ does not exist, the new instance may be
         
     | 
| 
      
 37 
     | 
    
         
            +
                  # populated by data given in the ~opts~ parameter.
         
     | 
| 
      
 38 
     | 
    
         
            +
                  #
         
     | 
| 
      
 39 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 40 
     | 
    
         
            +
                  #     File.exist? './test.org'
         
     | 
| 
      
 41 
     | 
    
         
            +
                  #     => true
         
     | 
| 
      
 42 
     | 
    
         
            +
                  #     o = Fronde::Org::File.new('./test.org')
         
     | 
| 
      
 43 
     | 
    
         
            +
                  #     => #<Fronde::Org::File @file='./test.org'...>
         
     | 
| 
      
 44 
     | 
    
         
            +
                  #     o.title
         
     | 
| 
      
 45 
     | 
    
         
            +
                  #     => "This is an existing test file"
         
     | 
| 
      
 46 
     | 
    
         
            +
                  #     File.exist? '/tmp/does_not_exist.org'
         
     | 
| 
      
 47 
     | 
    
         
            +
                  #     => false
         
     | 
| 
      
 48 
     | 
    
         
            +
                  #     o = Fronde::Org::File.new('/tmp/does_not_exist.org')
         
     | 
| 
      
 49 
     | 
    
         
            +
                  #     => #<Fronde::Org::File @file='/tmp/does_not_exist.org'...>
         
     | 
| 
      
 50 
     | 
    
         
            +
                  #     o.title
         
     | 
| 
      
 51 
     | 
    
         
            +
                  #     => ""
         
     | 
| 
      
 52 
     | 
    
         
            +
                  #     File.exist? '/tmp/other.org'
         
     | 
| 
      
 53 
     | 
    
         
            +
                  #     => false
         
     | 
| 
      
 54 
     | 
    
         
            +
                  #     o = Fronde::Org::File.new('/tmp/other.org', title: 'New file')
         
     | 
| 
      
 55 
     | 
    
         
            +
                  #     => #<Fronde::Org::File @file='/tmp/other.org'...>
         
     | 
| 
      
 56 
     | 
    
         
            +
                  #     o.title
         
     | 
| 
      
 57 
     | 
    
         
            +
                  #     => "New file"
         
     | 
| 
      
 58 
     | 
    
         
            +
                  #
         
     | 
| 
      
 59 
     | 
    
         
            +
                  # @param file_name [String] path to the corresponding Org file
         
     | 
| 
      
 60 
     | 
    
         
            +
                  # @param opts [Hash] optional data to initialize new Org file
         
     | 
| 
      
 61 
     | 
    
         
            +
                  # @option opts [String] title ('') the title of the new Org file
         
     | 
| 
      
 62 
     | 
    
         
            +
                  # @option opts [String] author (system user or '') the author of
         
     | 
| 
      
 63 
     | 
    
         
            +
                  #   the document
         
     | 
| 
      
 64 
     | 
    
         
            +
                  # @return [Fronde::Org::File] the new instance of
         
     | 
| 
      
 65 
     | 
    
         
            +
                  #   Fronde::Org::File
         
     | 
| 
      
 66 
     | 
    
         
            +
                  def initialize(file_name, opts = {})
         
     | 
| 
      
 67 
     | 
    
         
            +
                    file_name ||= ''
         
     | 
| 
      
 68 
     | 
    
         
            +
                    @file = ::File.expand_path file_name
         
     | 
| 
      
 69 
     | 
    
         
            +
                    @options = opts
         
     | 
| 
      
 70 
     | 
    
         
            +
                    @project = find_source
         
     | 
| 
      
 71 
     | 
    
         
            +
                    @data = {}
         
     | 
| 
      
 72 
     | 
    
         
            +
                    if ::File.file?(@file)
         
     | 
| 
      
 73 
     | 
    
         
            +
                      extract_data
         
     | 
| 
      
 74 
     | 
    
         
            +
                    else
         
     | 
| 
      
 75 
     | 
    
         
            +
                      init_empty_file
         
     | 
| 
      
 76 
     | 
    
         
            +
                    end
         
     | 
| 
      
 77 
     | 
    
         
            +
                  end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                  # Returns a String representation of the document date, which aims
         
     | 
| 
      
 80 
     | 
    
         
            +
                  #   to be used to sort several Org::Files.
         
     | 
| 
      
 81 
     | 
    
         
            +
                  #
         
     | 
| 
      
 82 
     | 
    
         
            +
                  # The format used for the key is ~%Y%m%d%H%M%S~. If the current
         
     | 
| 
      
 83 
     | 
    
         
            +
                  # Org::File instance does not have a date, this mehod return
         
     | 
| 
      
 84 
     | 
    
         
            +
                  # ~00000000000000~. If the current Org::File instance does not
         
     | 
| 
      
 85 
     | 
    
         
            +
                  # have time information, the date is padded with zeros.
         
     | 
| 
      
 86 
     | 
    
         
            +
                  #
         
     | 
| 
      
 87 
     | 
    
         
            +
                  # @example with the org header ~#+date: <2019-07-03 Wed 20:52:49>~
         
     | 
| 
      
 88 
     | 
    
         
            +
                  #     org_file.date
         
     | 
| 
      
 89 
     | 
    
         
            +
                  #     => #<Time: 2019-07-03T20:52:49+02:00...>
         
     | 
| 
      
 90 
     | 
    
         
            +
                  #     org_file.timekey
         
     | 
| 
      
 91 
     | 
    
         
            +
                  #     => "20190703205349"
         
     | 
| 
      
 92 
     | 
    
         
            +
                  #
         
     | 
| 
      
 93 
     | 
    
         
            +
                  # @example with the org header ~#+date: <2019-07-03 Wed>~
         
     | 
| 
      
 94 
     | 
    
         
            +
                  #     org_file.date
         
     | 
| 
      
 95 
     | 
    
         
            +
                  #     => #<Time: 2019-07-03T00:00:00+02:00...>
         
     | 
| 
      
 96 
     | 
    
         
            +
                  #     org_file.timekey
         
     | 
| 
      
 97 
     | 
    
         
            +
                  #     => "20190703000000"
         
     | 
| 
      
 98 
     | 
    
         
            +
                  #
         
     | 
| 
      
 99 
     | 
    
         
            +
                  # @example with no date header in the org file
         
     | 
| 
      
 100 
     | 
    
         
            +
                  #     org_file.date
         
     | 
| 
      
 101 
     | 
    
         
            +
                  #     => nil
         
     | 
| 
      
 102 
     | 
    
         
            +
                  #     org_file.timekey
         
     | 
| 
      
 103 
     | 
    
         
            +
                  #     => "00000000000000"
         
     | 
| 
      
 104 
     | 
    
         
            +
                  #
         
     | 
| 
      
 105 
     | 
    
         
            +
                  # @return [String] the document key
         
     | 
| 
      
 106 
     | 
    
         
            +
                  def timekey
         
     | 
| 
      
 107 
     | 
    
         
            +
                    return '00000000000000' if @data[:date].is_a? NilTime
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                    @data[:date].strftime('%Y%m%d%H%M%S')
         
     | 
| 
      
 110 
     | 
    
         
            +
                  end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                  # Formats given ~string~ with values of the current Org::File.
         
     | 
| 
      
 113 
     | 
    
         
            +
                  #
         
     | 
| 
      
 114 
     | 
    
         
            +
                  # This method expects to find percent-tags in the given ~string~
         
     | 
| 
      
 115 
     | 
    
         
            +
                  # and replace them by their corresponding value.
         
     | 
| 
      
 116 
     | 
    
         
            +
                  #
         
     | 
| 
      
 117 
     | 
    
         
            +
                  # It reuses the same tags than the ~org-html-format-spec~ method.
         
     | 
| 
      
 118 
     | 
    
         
            +
                  #
         
     | 
| 
      
 119 
     | 
    
         
            +
                  # *** Format:
         
     | 
| 
      
 120 
     | 
    
         
            +
                  #
         
     | 
| 
      
 121 
     | 
    
         
            +
                  # - %a :: the raw author name
         
     | 
| 
      
 122 
     | 
    
         
            +
                  # - %A :: the HTML rendering of the author name, equivalent to
         
     | 
| 
      
 123 
     | 
    
         
            +
                  #         ~<span class="author">%a</span>~
         
     | 
| 
      
 124 
     | 
    
         
            +
                  # - %d :: the ~:short~ date HTML representation, equivalent
         
     | 
| 
      
 125 
     | 
    
         
            +
                  #         to ~<time datetime="%I">%i</time>~
         
     | 
| 
      
 126 
     | 
    
         
            +
                  # - %D :: the ~:full~ date and time HTML representation
         
     | 
| 
      
 127 
     | 
    
         
            +
                  # - %i :: the raw ~:short~ date and time
         
     | 
| 
      
 128 
     | 
    
         
            +
                  # - %I :: the raw ~:iso8601~ date and time
         
     | 
| 
      
 129 
     | 
    
         
            +
                  # - %k :: the keywords separated by a comma
         
     | 
| 
      
 130 
     | 
    
         
            +
                  # - %K :: the HTML list rendering of the keywords
         
     | 
| 
      
 131 
     | 
    
         
            +
                  # - %l :: the lang of the document
         
     | 
| 
      
 132 
     | 
    
         
            +
                  # - %L :: the license information, taken from the
         
     | 
| 
      
 133 
     | 
    
         
            +
                  #         {Fronde::Config#settings}
         
     | 
| 
      
 134 
     | 
    
         
            +
                  # - %n :: the Fronde name and version
         
     | 
| 
      
 135 
     | 
    
         
            +
                  # - %N :: the Fronde name and version with a link to the project
         
     | 
| 
      
 136 
     | 
    
         
            +
                  #         home on the name
         
     | 
| 
      
 137 
     | 
    
         
            +
                  # - %s :: the subtitle of the document
         
     | 
| 
      
 138 
     | 
    
         
            +
                  # - %t :: the title of the document
         
     | 
| 
      
 139 
     | 
    
         
            +
                  # - %u :: the URL to the related published HTML document
         
     | 
| 
      
 140 
     | 
    
         
            +
                  # - %x :: the raw description (eXcerpt)
         
     | 
| 
      
 141 
     | 
    
         
            +
                  # - %X :: the description, enclosed in an HTML ~p~ tag, equivalent
         
     | 
| 
      
 142 
     | 
    
         
            +
                  #         to ~<p>%x</p>~
         
     | 
| 
      
 143 
     | 
    
         
            +
                  #
         
     | 
| 
      
 144 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 145 
     | 
    
         
            +
                  #     org_file.format("Article written by %a the %d")
         
     | 
| 
      
 146 
     | 
    
         
            +
                  #     => "Article written by Alice Smith the Wednesday 3rd July"
         
     | 
| 
      
 147 
     | 
    
         
            +
                  #
         
     | 
| 
      
 148 
     | 
    
         
            +
                  # @return [String] the given ~string~ after replacement occurs
         
     | 
| 
      
 149 
     | 
    
         
            +
                  # rubocop:disable Metrics/MethodLength
         
     | 
| 
      
 150 
     | 
    
         
            +
                  # rubocop:disable Layout/LineLength
         
     | 
| 
      
 151 
     | 
    
         
            +
                  def format(string)
         
     | 
| 
      
 152 
     | 
    
         
            +
                    string.gsub('%a', @data[:author])
         
     | 
| 
      
 153 
     | 
    
         
            +
                          .gsub('%A', "<span class=\"author\">#{@data[:author]}</span>")
         
     | 
| 
      
 154 
     | 
    
         
            +
                          .gsub('%d', @data[:date].l18n_short_date_html)
         
     | 
| 
      
 155 
     | 
    
         
            +
                          .gsub('%D', @data[:date].l18n_long_date_html)
         
     | 
| 
      
 156 
     | 
    
         
            +
                          .gsub('%i', @data[:date].l18n_short_date_string)
         
     | 
| 
      
 157 
     | 
    
         
            +
                          .gsub('%I', @data[:date].xmlschema)
         
     | 
| 
      
 158 
     | 
    
         
            +
                          .gsub('%k', @data[:keywords].join(', '))
         
     | 
| 
      
 159 
     | 
    
         
            +
                          .gsub('%K', keywords_to_html)
         
     | 
| 
      
 160 
     | 
    
         
            +
                          .gsub('%l', @data[:lang])
         
     | 
| 
      
 161 
     | 
    
         
            +
                          .gsub('%L', Fronde::CONFIG.get('license', '').gsub(/\s+/, ' ').strip)
         
     | 
| 
      
 162 
     | 
    
         
            +
                          .gsub('%n', "Fronde #{Fronde::VERSION}")
         
     | 
| 
      
 163 
     | 
    
         
            +
                          .gsub('%N', "<a href=\"https://git.umaneti.net/fronde/about/\">Fronde</a> #{Fronde::VERSION}")
         
     | 
| 
      
 164 
     | 
    
         
            +
                          .gsub('%s', @data[:subtitle])
         
     | 
| 
      
 165 
     | 
    
         
            +
                          .gsub('%t', @data[:title])
         
     | 
| 
      
 166 
     | 
    
         
            +
                          .gsub('%u', @data[:url] || '')
         
     | 
| 
      
 167 
     | 
    
         
            +
                          .gsub('%x', @data[:excerpt])
         
     | 
| 
      
 168 
     | 
    
         
            +
                          .gsub('%X', "<p>#{@data[:excerpt]}</p>")
         
     | 
| 
      
 169 
     | 
    
         
            +
                  end
         
     | 
| 
      
 170 
     | 
    
         
            +
                  # rubocop:enable Layout/LineLength
         
     | 
| 
      
 171 
     | 
    
         
            +
                  # rubocop:enable Metrics/MethodLength
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                  # Writes the current Org::File content to the underlying file.
         
     | 
| 
      
 174 
     | 
    
         
            +
                  #
         
     | 
| 
      
 175 
     | 
    
         
            +
                  # The intermediate parent folders are created if necessary.
         
     | 
| 
      
 176 
     | 
    
         
            +
                  #
         
     | 
| 
      
 177 
     | 
    
         
            +
                  # @return [Integer] the length written (as returned by the
         
     | 
| 
      
 178 
     | 
    
         
            +
                  #   underlying ~File.write~ method call)
         
     | 
| 
      
 179 
     | 
    
         
            +
                  def write
         
     | 
| 
      
 180 
     | 
    
         
            +
                    if ::File.directory? @file
         
     | 
| 
      
 181 
     | 
    
         
            +
                      if @data[:title] == ''
         
     | 
| 
      
 182 
     | 
    
         
            +
                        raise R18n.t.fronde.error.org_file.no_file_or_title
         
     | 
| 
      
 183 
     | 
    
         
            +
                      end
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
                      @file = ::File.join @file, "#{Slug.slug(@data[:title])}.org"
         
     | 
| 
      
 186 
     | 
    
         
            +
                    else
         
     | 
| 
      
 187 
     | 
    
         
            +
                      file_dir = ::File.dirname @file
         
     | 
| 
      
 188 
     | 
    
         
            +
                      FileUtils.mkdir_p file_dir
         
     | 
| 
      
 189 
     | 
    
         
            +
                    end
         
     | 
| 
      
 190 
     | 
    
         
            +
                    ::File.write @file, @data[:content]
         
     | 
| 
      
 191 
     | 
    
         
            +
                  end
         
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
      
 193 
     | 
    
         
            +
                  def method_missing(method_name, *args, &block)
         
     | 
| 
      
 194 
     | 
    
         
            +
                    reader_method = method_name.to_s.delete_suffix('=').to_sym
         
     | 
| 
      
 195 
     | 
    
         
            +
                    if @data.has_key? reader_method
         
     | 
| 
      
 196 
     | 
    
         
            +
                      return @data[reader_method] if reader_method == method_name
         
     | 
| 
      
 197 
     | 
    
         
            +
             
     | 
| 
      
 198 
     | 
    
         
            +
                      return @data[reader_method] = args.first
         
     | 
| 
      
 199 
     | 
    
         
            +
                    end
         
     | 
| 
      
 200 
     | 
    
         
            +
                    super
         
     | 
| 
      
 201 
     | 
    
         
            +
                  end
         
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
                  def respond_to_missing?(method_name, include_private = false)
         
     | 
| 
      
 204 
     | 
    
         
            +
                    return true if @data.has_key? method_name
         
     | 
| 
      
 205 
     | 
    
         
            +
             
     | 
| 
      
 206 
     | 
    
         
            +
                    reader_method = method_name.to_s.delete_suffix('=').to_sym
         
     | 
| 
      
 207 
     | 
    
         
            +
                    return true if @data.has_key? reader_method
         
     | 
| 
      
 208 
     | 
    
         
            +
             
     | 
| 
      
 209 
     | 
    
         
            +
                    super
         
     | 
| 
      
 210 
     | 
    
         
            +
                  end
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
                  def to_h
         
     | 
| 
      
 213 
     | 
    
         
            +
                    fields = %w[author excerpt keywords timekey title url]
         
     | 
| 
      
 214 
     | 
    
         
            +
                    data = fields.to_h { |key| [key, send(key)] }
         
     | 
| 
      
 215 
     | 
    
         
            +
                    data['published_body'] = extract_published_body
         
     | 
| 
      
 216 
     | 
    
         
            +
                    pub_date = @data[:date]
         
     | 
| 
      
 217 
     | 
    
         
            +
                    data['published'] = pub_date.l18n_long_date_string(with_year: false)
         
     | 
| 
      
 218 
     | 
    
         
            +
                    data['published_xml'] = pub_date.xmlschema
         
     | 
| 
      
 219 
     | 
    
         
            +
                    data['updated_xml'] = @data[:updated]&.xmlschema
         
     | 
| 
      
 220 
     | 
    
         
            +
                    data
         
     | 
| 
      
 221 
     | 
    
         
            +
                  end
         
     | 
| 
      
 222 
     | 
    
         
            +
             
     | 
| 
      
 223 
     | 
    
         
            +
                  private
         
     | 
| 
      
 224 
     | 
    
         
            +
             
     | 
| 
      
 225 
     | 
    
         
            +
                  def find_source
         
     | 
| 
      
 226 
     | 
    
         
            +
                    if ::File.extname(@file) == '.org'
         
     | 
| 
      
 227 
     | 
    
         
            +
                      source = find_source_for_org_file
         
     | 
| 
      
 228 
     | 
    
         
            +
                    else
         
     | 
| 
      
 229 
     | 
    
         
            +
                      source = find_source_for_publication_file
         
     | 
| 
      
 230 
     | 
    
         
            +
                    end
         
     | 
| 
      
 231 
     | 
    
         
            +
                    warn R18n.t.fronde.error.org_file.no_project(file: @file) unless source
         
     | 
| 
      
 232 
     | 
    
         
            +
                    source
         
     | 
| 
      
 233 
     | 
    
         
            +
                  end
         
     | 
| 
      
 234 
     | 
    
         
            +
             
     | 
| 
      
 235 
     | 
    
         
            +
                  def find_source_for_org_file
         
     | 
| 
      
 236 
     | 
    
         
            +
                    Fronde::CONFIG.sources.find do |project|
         
     | 
| 
      
 237 
     | 
    
         
            +
                      project.source_for? @file
         
     | 
| 
      
 238 
     | 
    
         
            +
                    end
         
     | 
| 
      
 239 
     | 
    
         
            +
                  end
         
     | 
| 
      
 240 
     | 
    
         
            +
             
     | 
| 
      
 241 
     | 
    
         
            +
                  def find_source_for_publication_file
         
     | 
| 
      
 242 
     | 
    
         
            +
                    Fronde::CONFIG.sources.find do |project|
         
     | 
| 
      
 243 
     | 
    
         
            +
                      org_file = project.source_for @file
         
     | 
| 
      
 244 
     | 
    
         
            +
                      next unless org_file
         
     | 
| 
      
 245 
     | 
    
         
            +
             
     | 
| 
      
 246 
     | 
    
         
            +
                      @file = org_file
         
     | 
| 
      
 247 
     | 
    
         
            +
                    end
         
     | 
| 
      
 248 
     | 
    
         
            +
                  end
         
     | 
| 
      
 249 
     | 
    
         
            +
             
     | 
| 
      
 250 
     | 
    
         
            +
                  def init_empty_file
         
     | 
| 
      
 251 
     | 
    
         
            +
                    @data = {
         
     | 
| 
      
 252 
     | 
    
         
            +
                      title: @options[:title] || '', subtitle: '', excerpt: '',
         
     | 
| 
      
 253 
     | 
    
         
            +
                      date: Time.now,
         
     | 
| 
      
 254 
     | 
    
         
            +
                      author: @options[:author] || Fronde::CONFIG.get('author'),
         
     | 
| 
      
 255 
     | 
    
         
            +
                      keywords: [],
         
     | 
| 
      
 256 
     | 
    
         
            +
                      lang: @options[:lang] || Fronde::CONFIG.get('lang'),
         
     | 
| 
      
 257 
     | 
    
         
            +
                      pub_file: nil, url: nil
         
     | 
| 
      
 258 
     | 
    
         
            +
                    }
         
     | 
| 
      
 259 
     | 
    
         
            +
                    body = @options[:content] || ''
         
     | 
| 
      
 260 
     | 
    
         
            +
                    @data[:content] = @options[:raw_content] || <<~ORG
         
     | 
| 
      
 261 
     | 
    
         
            +
                      #+title: #{@data[:title]}
         
     | 
| 
      
 262 
     | 
    
         
            +
                      #+date: <#{@data[:date].strftime('%Y-%m-%d %a. %H:%M:%S')}>
         
     | 
| 
      
 263 
     | 
    
         
            +
                      #+author: #{@data[:author]}
         
     | 
| 
      
 264 
     | 
    
         
            +
                      #+language: #{@data[:lang]}
         
     | 
| 
      
 265 
     | 
    
         
            +
             
     | 
| 
      
 266 
     | 
    
         
            +
                      #{body}
         
     | 
| 
      
 267 
     | 
    
         
            +
                    ORG
         
     | 
| 
      
 268 
     | 
    
         
            +
                  end
         
     | 
| 
      
 269 
     | 
    
         
            +
             
     | 
| 
      
 270 
     | 
    
         
            +
                  # Format {Fronde::Org::File#keywords} list in an HTML listing.
         
     | 
| 
      
 271 
     | 
    
         
            +
                  #
         
     | 
| 
      
 272 
     | 
    
         
            +
                  # @return [String] the HTML keywords list
         
     | 
| 
      
 273 
     | 
    
         
            +
                  def keywords_to_html
         
     | 
| 
      
 274 
     | 
    
         
            +
                    domain = Fronde::CONFIG.get('domain')
         
     | 
| 
      
 275 
     | 
    
         
            +
                    # Allow a nil project, mainly for tests purpose. Should never
         
     | 
| 
      
 276 
     | 
    
         
            +
                    # happen in reality
         
     | 
| 
      
 277 
     | 
    
         
            +
                    pub_path = @project&.public_absolute_path || '/'
         
     | 
| 
      
 278 
     | 
    
         
            +
                    klist = @data[:keywords].map do |k|
         
     | 
| 
      
 279 
     | 
    
         
            +
                      %(<li class="keyword">
         
     | 
| 
      
 280 
     | 
    
         
            +
                        <a href="#{domain}#{pub_path}tags/#{Slug.slug(k)}.html">#{k}</a>
         
     | 
| 
      
 281 
     | 
    
         
            +
                          </li>)
         
     | 
| 
      
 282 
     | 
    
         
            +
                    end.join
         
     | 
| 
      
 283 
     | 
    
         
            +
                    %(<ul class="keywords-list">#{klist}</ul>)
         
     | 
| 
      
 284 
     | 
    
         
            +
                  end
         
     | 
| 
      
 285 
     | 
    
         
            +
                end
         
     | 
| 
      
 286 
     | 
    
         
            +
              end
         
     | 
| 
      
 287 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,98 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            using TimePatch
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Fronde
         
     | 
| 
      
 6 
     | 
    
         
            +
              module Org
         
     | 
| 
      
 7 
     | 
    
         
            +
                # This module holds extracter methods for the {Fronde::Org::File}
         
     | 
| 
      
 8 
     | 
    
         
            +
                #   class.
         
     | 
| 
      
 9 
     | 
    
         
            +
                module FileExtracter
         
     | 
| 
      
 10 
     | 
    
         
            +
                  private
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  # Main method, which will call the other to initialize an
         
     | 
| 
      
 13 
     | 
    
         
            +
                  #   {Fronde::Org::File} instance.
         
     | 
| 
      
 14 
     | 
    
         
            +
                  def extract_data
         
     | 
| 
      
 15 
     | 
    
         
            +
                    @data = { content: ::File.read(@file), pub_file: nil, url: nil }
         
     | 
| 
      
 16 
     | 
    
         
            +
                    %i[title subtitle date author keywords lang excerpt].each do |param|
         
     | 
| 
      
 17 
     | 
    
         
            +
                      @data[param] = send("extract_#{param}".to_sym)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    end
         
     | 
| 
      
 19 
     | 
    
         
            +
                    return unless @project
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                    @data[:updated] = ::File.mtime(@file)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    @data[:pub_file] = @project.target_for @file
         
     | 
| 
      
 23 
     | 
    
         
            +
                    @data[:url] = Fronde::CONFIG.get('domain') + @data[:pub_file]
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  def extract_date
         
     | 
| 
      
 27 
     | 
    
         
            +
                    timerx = '([0-9:]{5})(?::([0-9]{2}))?'
         
     | 
| 
      
 28 
     | 
    
         
            +
                    daterx = /^#\+date: *<([0-9-]{10}) [\w.]+(?: #{timerx})?> *$/i
         
     | 
| 
      
 29 
     | 
    
         
            +
                    match = daterx.match(@data[:content])
         
     | 
| 
      
 30 
     | 
    
         
            +
                    return NilTime.new if match.nil?
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                    notime = match[2].nil?
         
     | 
| 
      
 33 
     | 
    
         
            +
                    if notime
         
     | 
| 
      
 34 
     | 
    
         
            +
                      time = '00:00:00'
         
     | 
| 
      
 35 
     | 
    
         
            +
                    else
         
     | 
| 
      
 36 
     | 
    
         
            +
                      time = "#{match[2]}:#{match[3] || '00'}"
         
     | 
| 
      
 37 
     | 
    
         
            +
                    end
         
     | 
| 
      
 38 
     | 
    
         
            +
                    date = Time.strptime("#{match[1]} #{time}", '%Y-%m-%d %H:%M:%S')
         
     | 
| 
      
 39 
     | 
    
         
            +
                    date.no_time = notime
         
     | 
| 
      
 40 
     | 
    
         
            +
                    date
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  def extract_title
         
     | 
| 
      
 44 
     | 
    
         
            +
                    match = /^#\+title:(.+)$/i.match(@data[:content])
         
     | 
| 
      
 45 
     | 
    
         
            +
                    if match.nil?
         
     | 
| 
      
 46 
     | 
    
         
            +
                      # Avoid to leak absolute path
         
     | 
| 
      
 47 
     | 
    
         
            +
                      project_relative_path = @file.sub(/^#{Dir.pwd}\//, '')
         
     | 
| 
      
 48 
     | 
    
         
            +
                      return project_relative_path
         
     | 
| 
      
 49 
     | 
    
         
            +
                    end
         
     | 
| 
      
 50 
     | 
    
         
            +
                    match[1].strip
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                  def extract_subtitle
         
     | 
| 
      
 54 
     | 
    
         
            +
                    match = /^#\+subtitle:(.+)$/i.match(@data[:content])
         
     | 
| 
      
 55 
     | 
    
         
            +
                    (match && match[1].strip) || ''
         
     | 
| 
      
 56 
     | 
    
         
            +
                  end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                  def extract_author
         
     | 
| 
      
 59 
     | 
    
         
            +
                    match = /^#\+author:(.+)$/i.match(@data[:content])
         
     | 
| 
      
 60 
     | 
    
         
            +
                    (match && match[1].strip) || Fronde::CONFIG.get('author')
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                  def extract_keywords
         
     | 
| 
      
 64 
     | 
    
         
            +
                    match = /^#\+keywords:(.+)$/i.match(@data[:content])
         
     | 
| 
      
 65 
     | 
    
         
            +
                    (match && match[1].split(',').map(&:strip)) || []
         
     | 
| 
      
 66 
     | 
    
         
            +
                  end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                  def extract_lang
         
     | 
| 
      
 69 
     | 
    
         
            +
                    match = /^#\+language:(.+)$/i.match(@data[:content])
         
     | 
| 
      
 70 
     | 
    
         
            +
                    (match && match[1].strip) || Fronde::CONFIG.get('lang')
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                  def extract_excerpt
         
     | 
| 
      
 74 
     | 
    
         
            +
                    @data[:content].scan(/^#\+description:(.+)$/i).map do |line|
         
     | 
| 
      
 75 
     | 
    
         
            +
                      line.first.strip
         
     | 
| 
      
 76 
     | 
    
         
            +
                    end.join(' ')
         
     | 
| 
      
 77 
     | 
    
         
            +
                  end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                  def extract_published_body
         
     | 
| 
      
 80 
     | 
    
         
            +
                    pub_file = @data[:pub_file]
         
     | 
| 
      
 81 
     | 
    
         
            +
                    # Always return something, even when not published yet
         
     | 
| 
      
 82 
     | 
    
         
            +
                    return @data[:excerpt] unless pub_file && @project
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                    project_type = @project['type']
         
     | 
| 
      
 85 
     | 
    
         
            +
                    pub_folder = Fronde::CONFIG.get("#{project_type}_public_folder")
         
     | 
| 
      
 86 
     | 
    
         
            +
                    file_name = pub_folder + pub_file
         
     | 
| 
      
 87 
     | 
    
         
            +
                    return @data[:excerpt] unless ::File.exist? file_name
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                    return ::File.read(file_name) if project_type == 'gemini'
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                    dom = ::File.open(file_name, 'r') { |file| Nokogiri::HTML file }
         
     | 
| 
      
 92 
     | 
    
         
            +
                    body = dom.css('div#content')
         
     | 
| 
      
 93 
     | 
    
         
            +
                    body.css('header').unlink # Remove the main title
         
     | 
| 
      
 94 
     | 
    
         
            +
                    body.to_s
         
     | 
| 
      
 95 
     | 
    
         
            +
                  end
         
     | 
| 
      
 96 
     | 
    
         
            +
                end
         
     | 
| 
      
 97 
     | 
    
         
            +
              end
         
     | 
| 
      
 98 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/fronde/org.rb
    ADDED
    
    | 
         @@ -0,0 +1,103 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Fronde
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Everything related to Org mode
         
     | 
| 
      
 5 
     | 
    
         
            +
              #
         
     | 
| 
      
 6 
     | 
    
         
            +
              # The module itself wraps code necessary to download the last version
         
     | 
| 
      
 7 
     | 
    
         
            +
              # of the Emacs package. It also serves as a namespace for the class
         
     | 
| 
      
 8 
     | 
    
         
            +
              # responsible for handling Org files: {Fronde::Org::File}.
         
     | 
| 
      
 9 
     | 
    
         
            +
              module Org
         
     | 
| 
      
 10 
     | 
    
         
            +
                class << self
         
     | 
| 
      
 11 
     | 
    
         
            +
                  def current_version
         
     | 
| 
      
 12 
     | 
    
         
            +
                    # Do not crash if Org is not yet installed (and thus return nil)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    Dir['lib/org-*'].first&.delete_prefix('lib/org-')
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  # Fetch and return the last published version of Org.
         
     | 
| 
      
 17 
     | 
    
         
            +
                  #
         
     | 
| 
      
 18 
     | 
    
         
            +
                  # To be nice with Org servers, this method will keep the fetched
         
     | 
| 
      
 19 
     | 
    
         
            +
                  # version number in a cache file. You can bypass it by using the
         
     | 
| 
      
 20 
     | 
    
         
            +
                  # force parameter.
         
     | 
| 
      
 21 
     | 
    
         
            +
                  #
         
     | 
| 
      
 22 
     | 
    
         
            +
                  # @param force [Boolean] Whether we should first remove the guard
         
     | 
| 
      
 23 
     | 
    
         
            +
                  #   file if it exists
         
     | 
| 
      
 24 
     | 
    
         
            +
                  # @param destination [String] Where to store the cookie file to
         
     | 
| 
      
 25 
     | 
    
         
            +
                  #   remember the last version number
         
     | 
| 
      
 26 
     | 
    
         
            +
                  # @return [String] the new x.x.x version string of Org
         
     | 
| 
      
 27 
     | 
    
         
            +
                  def last_version(force: false, cookie_dir: 'var/tmp')
         
     | 
| 
      
 28 
     | 
    
         
            +
                    cookie = "#{cookie_dir}/last_org_version"
         
     | 
| 
      
 29 
     | 
    
         
            +
                    return ::File.read cookie if !force && ::File.exist?(cookie)
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                    org_version = fetch_version_number
         
     | 
| 
      
 32 
     | 
    
         
            +
                    raise 'No remote Org version found' unless org_version
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                    FileUtils.mkdir_p cookie_dir
         
     | 
| 
      
 35 
     | 
    
         
            +
                    ::File.write cookie, org_version
         
     | 
| 
      
 36 
     | 
    
         
            +
                    org_version
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  def fetch_version_number
         
     | 
| 
      
 40 
     | 
    
         
            +
                    # Retrieve last org version from git repository tags page.
         
     | 
| 
      
 41 
     | 
    
         
            +
                    tag_rx = Regexp.new(
         
     | 
| 
      
 42 
     | 
    
         
            +
                      '<a href=\'/cgit/emacs/org-mode.git/tag/\?h=' \
         
     | 
| 
      
 43 
     | 
    
         
            +
                      '(?<tag>release_(?<number>[^\']+))\'>\k<tag></a>'
         
     | 
| 
      
 44 
     | 
    
         
            +
                    )
         
     | 
| 
      
 45 
     | 
    
         
            +
                    versions = URI(
         
     | 
| 
      
 46 
     | 
    
         
            +
                      'https://git.savannah.gnu.org/cgit/emacs/org-mode.git/refs/'
         
     | 
| 
      
 47 
     | 
    
         
            +
                    ).open.readlines.map do |line|
         
     | 
| 
      
 48 
     | 
    
         
            +
                      line.match(tag_rx) { |matchdata| matchdata[:number] }
         
     | 
| 
      
 49 
     | 
    
         
            +
                    end
         
     | 
| 
      
 50 
     | 
    
         
            +
                    versions.compact.first
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                  # Download latest org-mode tarball.
         
     | 
| 
      
 54 
     | 
    
         
            +
                  #
         
     | 
| 
      
 55 
     | 
    
         
            +
                  # @param destination [String] where to save the org-mode tarball
         
     | 
| 
      
 56 
     | 
    
         
            +
                  # @return [String] the downloaded org-mode version
         
     | 
| 
      
 57 
     | 
    
         
            +
                  def download(destination = 'var/tmp')
         
     | 
| 
      
 58 
     | 
    
         
            +
                    # Remove version number in dest file to allow easy rake file
         
     | 
| 
      
 59 
     | 
    
         
            +
                    # task naming
         
     | 
| 
      
 60 
     | 
    
         
            +
                    dest_file = ::File.expand_path('org.tar.gz', destination)
         
     | 
| 
      
 61 
     | 
    
         
            +
                    org_last_version = last_version(force: false, cookie_dir: destination)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    tarball = "org-mode-release_#{org_last_version}.tar.gz"
         
     | 
| 
      
 63 
     | 
    
         
            +
                    uri = URI("https://git.savannah.gnu.org/cgit/emacs/org-mode.git/snapshot/#{tarball}")
         
     | 
| 
      
 64 
     | 
    
         
            +
                    # Will crash on purpose if anything goes wrong
         
     | 
| 
      
 65 
     | 
    
         
            +
                    Net::HTTP.start(uri.host) do |http|
         
     | 
| 
      
 66 
     | 
    
         
            +
                      request = Net::HTTP::Get.new uri
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                      http.request request do |response|
         
     | 
| 
      
 69 
     | 
    
         
            +
                        ::File.open(dest_file, 'w') do |io|
         
     | 
| 
      
 70 
     | 
    
         
            +
                          response.read_body { |chunk| io.write chunk }
         
     | 
| 
      
 71 
     | 
    
         
            +
                        end
         
     | 
| 
      
 72 
     | 
    
         
            +
                      end
         
     | 
| 
      
 73 
     | 
    
         
            +
                    end
         
     | 
| 
      
 74 
     | 
    
         
            +
                    org_last_version
         
     | 
| 
      
 75 
     | 
    
         
            +
                  end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                  def make_org_cmd(org_dir, target, verbose: false)
         
     | 
| 
      
 78 
     | 
    
         
            +
                    make = ['make', '-C', org_dir, target]
         
     | 
| 
      
 79 
     | 
    
         
            +
                    return make.join(' ') if verbose
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                    make.insert(3, '-s')
         
     | 
| 
      
 82 
     | 
    
         
            +
                    make << 'EMACSQ="emacs -Q --eval \'(setq inhibit-message t)\'"'
         
     | 
| 
      
 83 
     | 
    
         
            +
                    make.join(' ')
         
     | 
| 
      
 84 
     | 
    
         
            +
                  end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                  # Compile downloaded Org package
         
     | 
| 
      
 87 
     | 
    
         
            +
                  #
         
     | 
| 
      
 88 
     | 
    
         
            +
                  # @param source [String] path to the org-mode tarball to install
         
     | 
| 
      
 89 
     | 
    
         
            +
                  # @param version [String] version of the org package to install
         
     | 
| 
      
 90 
     | 
    
         
            +
                  # @param target [String] path to the final install directory
         
     | 
| 
      
 91 
     | 
    
         
            +
                  # @param verbose [Boolean] whether the process should be verbose
         
     | 
| 
      
 92 
     | 
    
         
            +
                  def compile(source, version, target, verbose: false)
         
     | 
| 
      
 93 
     | 
    
         
            +
                    untar_cmd = ['tar', '-xzf', source]
         
     | 
| 
      
 94 
     | 
    
         
            +
                    system(*untar_cmd)
         
     | 
| 
      
 95 
     | 
    
         
            +
                    FileUtils.mv "org-mode-release_#{version}", target
         
     | 
| 
      
 96 
     | 
    
         
            +
                    # Fix a weird unknown package version
         
     | 
| 
      
 97 
     | 
    
         
            +
                    ::File.write("#{target}/mk/version.mk", "ORGVERSION ?= #{version}")
         
     | 
| 
      
 98 
     | 
    
         
            +
                    system(*make_org_cmd(target, 'compile', verbose: verbose))
         
     | 
| 
      
 99 
     | 
    
         
            +
                    system(*make_org_cmd(target, 'autoloads', verbose: verbose))
         
     | 
| 
      
 100 
     | 
    
         
            +
                  end
         
     | 
| 
      
 101 
     | 
    
         
            +
                end
         
     | 
| 
      
 102 
     | 
    
         
            +
              end
         
     | 
| 
      
 103 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/fronde/preview.rb
    CHANGED
    
    | 
         @@ -1,54 +1,58 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            require 'webrick'
         
     | 
| 
       4 
     | 
    
         
            -
             
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
     | 
    
         
            -
            module Fronde # rubocop:disable Style/Documentation
         
     | 
| 
       7 
     | 
    
         
            -
              # A tiny preview server, which main goal is to replace references to
         
     | 
| 
       8 
     | 
    
         
            -
              # the target domain by localhost.
         
     | 
| 
       9 
     | 
    
         
            -
              class PreviewServlet < WEBrick::HTTPServlet::AbstractServlet
         
     | 
| 
       10 
     | 
    
         
            -
                include WEBrick::HTTPUtils
         
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
                def do_GET(request, response) # rubocop:disable Naming/MethodName
         
     | 
| 
       13 
     | 
    
         
            -
                  file = local_path(request.path)
         
     | 
| 
       14 
     | 
    
         
            -
                  response.body = parse_body(file, "http://#{request.host}:#{request.port}")
         
     | 
| 
       15 
     | 
    
         
            -
                  response.status = 200
         
     | 
| 
       16 
     | 
    
         
            -
                  response.content_type = mime_type(file, DefaultMimeTypes)
         
     | 
| 
       17 
     | 
    
         
            -
                end
         
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative 'config'
         
     | 
| 
       18 
5 
     | 
    
         | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Fronde
         
     | 
| 
      
 7 
     | 
    
         
            +
              module Preview # rubocop:disable Style/Documentation
         
     | 
| 
      
 8 
     | 
    
         
            +
                # A tiny preview server, which main goal is to replace references to
         
     | 
| 
      
 9 
     | 
    
         
            +
                #   the target domain by localhost.
         
     | 
| 
      
 10 
     | 
    
         
            +
                class Servlet < WEBrick::HTTPServlet::AbstractServlet
         
     | 
| 
      
 11 
     | 
    
         
            +
                  include WEBrick::HTTPUtils
         
     | 
| 
       20 
12 
     | 
    
         | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
       26 
     | 
    
         
            -
                    local_path = format(
         
     | 
| 
       27 
     | 
    
         
            -
                      '%<path>s/index.html', path: local_path.delete_suffix('/')
         
     | 
| 
       28 
     | 
    
         
            -
                    )
         
     | 
| 
      
 13 
     | 
    
         
            +
                  def do_GET(request, response) # rubocop:disable Naming/MethodName
         
     | 
| 
      
 14 
     | 
    
         
            +
                    file = local_path(request.path)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    response.body = parse_body(file, "http://#{request.host}:#{request.port}")
         
     | 
| 
      
 16 
     | 
    
         
            +
                    response.status = 200
         
     | 
| 
      
 17 
     | 
    
         
            +
                    response.content_type = mime_type(file, DefaultMimeTypes)
         
     | 
| 
       29 
18 
     | 
    
         
             
                  end
         
     | 
| 
       30 
     | 
    
         
            -
                  return local_path if File.exist? local_path
         
     | 
| 
       31 
     | 
    
         
            -
                  raise WEBrick::HTTPStatus::NotFound, 'Not found.'
         
     | 
| 
       32 
     | 
    
         
            -
                end
         
     | 
| 
       33 
19 
     | 
    
         | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
                   
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
      
 20 
     | 
    
         
            +
                  private
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  def local_path(requested_path)
         
     | 
| 
      
 23 
     | 
    
         
            +
                    routes = Fronde::CONFIG.get(%w[preview routes], {})
         
     | 
| 
      
 24 
     | 
    
         
            +
                    return routes[requested_path] if routes.has_key? requested_path
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                    local_path = Fronde::CONFIG.get('html_public_folder') + requested_path
         
     | 
| 
      
 27 
     | 
    
         
            +
                    if File.directory? local_path
         
     | 
| 
      
 28 
     | 
    
         
            +
                      local_path = format(
         
     | 
| 
      
 29 
     | 
    
         
            +
                        '%<path>s/index.html', path: local_path.delete_suffix('/')
         
     | 
| 
      
 30 
     | 
    
         
            +
                      )
         
     | 
| 
      
 31 
     | 
    
         
            +
                    end
         
     | 
| 
      
 32 
     | 
    
         
            +
                    return local_path if File.exist? local_path
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                    raise WEBrick::HTTPStatus::NotFound, 'Not found.'
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  def parse_body(local_path, local_host)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    body = File.read local_path
         
     | 
| 
      
 39 
     | 
    
         
            +
                    return body unless local_path.match?(/\.(?:ht|x)ml\z/)
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                    domain = Fronde::CONFIG.get('domain')
         
     | 
| 
      
 42 
     | 
    
         
            +
                    return body if domain == ''
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                    host_repl = %("#{local_host})
         
     | 
| 
      
 45 
     | 
    
         
            +
                    body.gsub('"file://', host_repl).gsub(%("#{domain}), host_repl)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
       41 
47 
     | 
    
         
             
                end
         
     | 
| 
       42 
     | 
    
         
            -
              end
         
     | 
| 
       43 
48 
     | 
    
         | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
       45 
     | 
    
         
            -
                def start_preview
         
     | 
| 
      
 49 
     | 
    
         
            +
                def self.start
         
     | 
| 
       46 
50 
     | 
    
         
             
                  # Inspired by ruby un.rb library, which allows normally to start a
         
     | 
| 
       47 
51 
     | 
    
         
             
                  # webrick server in one line: ruby -run -e httpd public_html -p 5000
         
     | 
| 
       48 
     | 
    
         
            -
                  port = Fronde:: 
     | 
| 
      
 52 
     | 
    
         
            +
                  port = Fronde::CONFIG.get(%w[preview server_port], 5000)
         
     | 
| 
       49 
53 
     | 
    
         
             
                  s = WEBrick::HTTPServer.new(Port: port)
         
     | 
| 
       50 
     | 
    
         
            -
                  s.mount '/',  
     | 
| 
       51 
     | 
    
         
            -
                  [ 
     | 
| 
      
 54 
     | 
    
         
            +
                  s.mount '/', Servlet
         
     | 
| 
      
 55 
     | 
    
         
            +
                  %w[TERM QUIT INT].each { |sig| trap(sig, proc { s.shutdown }) }
         
     | 
| 
       52 
56 
     | 
    
         
             
                  s.start
         
     | 
| 
       53 
57 
     | 
    
         
             
                end
         
     | 
| 
       54 
58 
     | 
    
         
             
              end
         
     | 
    
        data/lib/fronde/slug.rb
    ADDED
    
    | 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Fronde
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Contains method to generate URL compatible strings
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Slug
         
     | 
| 
      
 6 
     | 
    
         
            +
                class << self
         
     | 
| 
      
 7 
     | 
    
         
            +
                  def slug(title)
         
     | 
| 
      
 8 
     | 
    
         
            +
                    title.downcase.tr(' ', '-')
         
     | 
| 
      
 9 
     | 
    
         
            +
                         .encode('ascii', fallback: ->(k) { translit(k) })
         
     | 
| 
      
 10 
     | 
    
         
            +
                         .gsub(/[^\w-]/, '').delete_suffix('-')
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  def translit(char)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    return 'a' if %w[á à â ä ǎ ã å].include?(char)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    return 'e' if %w[é è ê ë ě ẽ].include?(char)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    return 'i' if %w[í ì î ï ǐ ĩ].include?(char)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    return 'o' if %w[ó ò ô ö ǒ õ].include?(char)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    return 'u' if %w[ú ù û ü ǔ ũ].include?(char)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    return 'y' if %w[ý ỳ ŷ ÿ ỹ].include?(char)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    return 'c' if char == 'ç'
         
     | 
| 
      
 21 
     | 
    
         
            +
                    return 'n' if char == 'ñ'
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                    '-'
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,39 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Fronde
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Source
         
     | 
| 
      
 5 
     | 
    
         
            +
                # Specific settings for Gemini {Fronde::Source}
         
     | 
| 
      
 6 
     | 
    
         
            +
                class Gemini < Source
         
     | 
| 
      
 7 
     | 
    
         
            +
                  def blog?
         
     | 
| 
      
 8 
     | 
    
         
            +
                    # TODO: See how to support blog/indexes with gemini
         
     | 
| 
      
 9 
     | 
    
         
            +
                    false
         
     | 
| 
      
 10 
     | 
    
         
            +
                  end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  class << self
         
     | 
| 
      
 13 
     | 
    
         
            +
                    def org_default_postamble
         
     | 
| 
      
 14 
     | 
    
         
            +
                      format(
         
     | 
| 
      
 15 
     | 
    
         
            +
                        "📅 %<date>s\n📝 %<author>s %<creator>s",
         
     | 
| 
      
 16 
     | 
    
         
            +
                        author: R18n.t.fronde.org.postamble.written_by,
         
     | 
| 
      
 17 
     | 
    
         
            +
                        creator: R18n.t.fronde.org.postamble.with_emacs,
         
     | 
| 
      
 18 
     | 
    
         
            +
                        date: R18n.t.fronde.org.postamble.last_modification
         
     | 
| 
      
 19 
     | 
    
         
            +
                      )
         
     | 
| 
      
 20 
     | 
    
         
            +
                    end
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  private
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  def fill_in_specific_config
         
     | 
| 
      
 26 
     | 
    
         
            +
                    @config.merge!(
         
     | 
| 
      
 27 
     | 
    
         
            +
                      'type' => 'gemini', 'ext' => '.gmi', 'mime_type' => 'text/gemini',
         
     | 
| 
      
 28 
     | 
    
         
            +
                      'folder' => CONFIG.get('gemini_public_folder')
         
     | 
| 
      
 29 
     | 
    
         
            +
                    )
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  def org_default_options
         
     | 
| 
      
 33 
     | 
    
         
            +
                    { 'publishing-function' => 'org-gmi-publish-to-gemini',
         
     | 
| 
      
 34 
     | 
    
         
            +
                      'gemini-head' => '',
         
     | 
| 
      
 35 
     | 
    
         
            +
                      'gemini-postamble' => Gemini.org_default_postamble }
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
              end
         
     | 
| 
      
 39 
     | 
    
         
            +
            end
         
     |