utilities.rb 9.71 KB
Newer Older
Sylvester Keil's avatar
Sylvester Keil committed
1
module Jekyll
2
  class Scholar
3

Sylvester Keil's avatar
Sylvester Keil committed
4 5 6 7 8 9 10 11
    # Load styles into static memory.
    # They should be thread safe as long as they are
    # treated as being read-only.
    STYLES = Hash.new do |h, k|
      h[k.to_s] = CSL::Style.load k
    end


12
    # Utility methods used by several Scholar plugins. The methods in this
13
    # module may depend on the presence of #config, #bibtex_files, and
14 15
    # #site readers
    module Utilities
16

17
      attr_reader :config, :site, :query,
18
        :context, :prefix, :text, :max
19 20 21 22 23 24 25 26 27 28

      def split_arguments(arguments)

        tokens = arguments.strip.split(/\s+/)

        args = tokens.take_while { |a| !a.start_with?('-') }
        opts = (tokens - args).join(' ')

        [args, opts]
      end
29 30 31 32 33 34 35 36 37

      def optparse(arguments)
        return if arguments.nil? || arguments.empty?

        parser = OptionParser.new do |opts|
          opts.on('-c', '--cited') do |cited|
            @cited = true
          end

Sylvester Keil's avatar
Sylvester Keil committed
38 39 40 41
          opts.on('-C', '--cited_in_order') do |cited|
            @cited, @skip_sort = true, true
          end

42
          opts.on('-f', '--file FILE') do |file|
43 44
            @bibtex_files ||= []
            @bibtex_files << file
45 46 47 48 49 50 51 52 53 54 55 56 57
          end

          opts.on('-q', '--query QUERY') do |query|
            @query = query
          end

          opts.on('-p', '--prefix PREFIX') do |prefix|
            @prefix = prefix
          end

          opts.on('-t', '--text TEXT') do |text|
            @text = text
          end
58

59 60 61 62
          opts.on('-l', '--locator LOCATOR') do |locator|
            locators << locator
          end

Sylvester Keil's avatar
Sylvester Keil committed
63 64 65 66
          opts.on('-m', '--max MAX') do |max|
            @max = max
          end

67 68
          opts.on('-s', '--style STYLE') do |style|
            @style = style
69
          end
70 71 72 73

          opts.on('-T', '--template TEMPLATE') do |template|
            @bibliography_template = template
          end
74 75
        end

Sylvester Keil's avatar
Sylvester Keil committed
76
        argv = arguments.split(/(\B-[cCfqptTslm]|\B--(?:cited(_in_order)?|file|query|prefix|text|style|template|locator|max|))/)
77 78 79

        parser.parse argv.map(&:strip).reject(&:empty?)
      end
80

81 82 83 84
      def locators
        @locators ||= []
      end

85 86 87 88 89 90 91 92 93
      def bibtex_files
        @bibtex_files ||= [config['bibliography']]
      end

      # :nodoc: backwards compatibility
      def bibtex_file
        bibtex_files[0]
      end

94
      def bibtex_options
95 96 97 98 99
        config['bibtex_options'] ||= {}
      end

      def bibtex_filters
        config['bibtex_filters'] ||= []
100
      end
101

102 103 104 105 106 107 108
      def bibtex_paths
        @bibtex_paths ||= bibtex_files.map { |file|
          extend_path file
        }
      end

      # :nodoc: backwards compatibility
109
      def bibtex_path
110
        bibtex_paths[0]
111
      end
112

113
      def bibliography
114
        unless @bibliography
115 116 117 118
          @bibliography = BibTeX.parse(
            bibtex_paths.reduce('') { |s, p| s << IO.read(p) },
            bibtex_options
          )
119
          @bibliography.replace_strings if replace_strings?
120
          @bibliography.join if join_strings? && replace_strings?
121 122 123
        end

        @bibliography
124 125
      end

126
      def entries
127 128
        sort bibliography[query || config['query']]
      end
129

Sylvester Keil's avatar
Sylvester Keil committed
130 131 132 133
      def limit_entries?
        !max.nil?
      end

134
      def sort(unsorted)
Sylvester Keil's avatar
Sylvester Keil committed
135
        return unsorted if skip_sort?
136

137 138 139
        sorted = unsorted.sort_by { |e| e[config['sort_by']].to_s }
        sorted.reverse! if config['order'] =~ /^(desc|reverse)/i
        sorted
140
      end
141

142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
      def repository?
        !config['repository'].nil? && !config['repository'].empty?
      end

      def repository
        @repository ||= load_repository
      end

      def load_repository
        return {} unless repository?

        Hash[Dir[File.join(repository_path, '**/*.{pdf,ps}')].map { |path|
          [File.basename(path).sub(/\.(pdf|ps)$/, ''), path]
        }]
      end

      def repository_path
        config['repository']
      end

162 163 164 165
      def replace_strings?
        config['replace_strings']
      end

166 167 168 169
      def join_strings?
        config['join_strings']
      end

170 171 172 173
      def cited_only?
        !!@cited
      end

Sylvester Keil's avatar
Sylvester Keil committed
174 175 176 177
      def skip_sort?
        @skip_sort || config['sort_by'] == 'none'
      end

178 179
      def extend_path(name)
        if name.nil? || name.empty?
180
          name = config['bibliography']
181
        end
182

183 184
        # return as is if it is an absolute path
        return name if name.start_with?('/') && File.exists?(name)
185

186 187 188
        p = File.join(config['source'], name)
        p << '.bib' unless File.exists?(p)
        p
189
      end
190

Sylvester Keil's avatar
Sylvester Keil committed
191
      def reference_tag(entry, index = nil)
Sylvester Keil's avatar
Sylvester Keil committed
192
        return missing_reference unless entry
193

194
        entry = entry.convert(*bibtex_filters) unless bibtex_filters.empty?
Sylvester Keil's avatar
Sylvester Keil committed
195
        reference = render_bibliography entry, index
196

Sylvester Keil's avatar
Sylvester Keil committed
197 198 199 200
        content_tag reference_tagname, reference,
          :id => [prefix, entry.key].compact.join('-')
      end

201 202 203 204
      def style
        @style || config['style']
      end

Sylvester Keil's avatar
Sylvester Keil committed
205 206 207 208 209 210 211 212 213
      def missing_reference
        config['missing_reference']
      end

      def reference_tagname
        config['reference_tagname'] || :span
      end

      def bibliography_template
214 215 216 217 218
        @bibliography_template || config['bibliography_template']
      end

      def liquid_template
        return @liquid_template if @liquid_template
219

220
        tmp = bibliography_template
221 222

        case
223
        when tmp.nil?, tmp.empty?
224 225 226 227 228
          tmp = '{{reference}}'
        when site.layouts.key?(tmp)
          tmp = site.layouts[tmp].content
        end

229
        @liquid_template = Liquid::Template.parse(tmp)
Sylvester Keil's avatar
Sylvester Keil committed
230 231
      end

232
      def bibliography_tag(entry, index)
Sylvester Keil's avatar
Sylvester Keil committed
233 234
        return missing_reference unless entry

235
        liquid_template.render({
Sylvester Keil's avatar
Sylvester Keil committed
236
          'entry' => liquidify(entry),
Sylvester Keil's avatar
Sylvester Keil committed
237
          'reference' => reference_tag(entry, index),
238 239 240 241 242
          'key' => entry.key,
          'type' => entry.type,
          'link' => repository_link_for(entry),
          'index' => index
        })
243 244
      end

Sylvester Keil's avatar
Sylvester Keil committed
245 246 247 248 249 250
      def liquidify(entry)
        e = {}

        e['key'] = entry.key
        e['type'] = entry.type

251 252 253 254 255 256 257
        if entry.field?(:abstract)
          tmp = entry.dup
          tmp.delete :abstract
          e['bibtex'] = tmp.to_s
        else
          e['bibtex'] = entry.to_s
        end
Sylvester Keil's avatar
Sylvester Keil committed
258 259 260 261 262 263 264 265 266

        entry.fields.each do |key, value|
          value = value.convert(*bibtex_filters) unless bibtex_filters.empty?
          e[key.to_s] = value.to_s
        end

        e
      end

267 268 269
      def generate_details?
        site.layouts.key?(File.basename(config['details_layout'], '.html'))
      end
270

271 272
      def details_file_for(entry)
        name = entry.key.to_s.dup
273

274
        name.gsub!(/[:\s]+/, '_')
275

276 277
        [name, 'html'].join('.')
      end
278

279 280 281 282 283 284 285
      def repository_link_for(entry, base = base_url)
        url = repository[entry.key]
        return unless url

        File.join(base, url)
      end

Sylvester Keil's avatar
Sylvester Keil committed
286
      def details_link_for(entry, base = base_url)
287
        File.join(base, details_path, details_file_for(entry))
Sylvester Keil's avatar
Sylvester Keil committed
288
      end
289

Sylvester Keil's avatar
Sylvester Keil committed
290
      def base_url
291
        @base_url ||= site.config['baseurl'] || site.config['base_url'] || ''
292
      end
293

294 295 296
      def details_path
        config['details_dir']
      end
297

Sylvester Keil's avatar
Sylvester Keil committed
298 299 300 301
      def renderer
        @renderer ||= CiteProc::Ruby::Renderer.new :format => 'html',
          :style => style, :locale => config['locale']
      end
302

303 304
      def render_citation(items)
        renderer.render items.zip(locators).map { |entry, locator|
Sylvester Keil's avatar
Sylvester Keil committed
305
          cited_keys << entry.key
306

Sylvester Keil's avatar
Sylvester Keil committed
307
          item = citation_item_for entry, citation_number
308
          item.locator = locator
309

310 311
          item
        }, STYLES[style].citation
Sylvester Keil's avatar
Sylvester Keil committed
312 313
      end

Sylvester Keil's avatar
Sylvester Keil committed
314 315 316
      def render_bibliography(entry, index = nil)
        renderer.render citation_item_for(entry, index),
          STYLES[style].bibliography
Sylvester Keil's avatar
Sylvester Keil committed
317 318
      end

Sylvester Keil's avatar
Sylvester Keil committed
319
      def citation_item_for(entry, citation_number = nil)
Sylvester Keil's avatar
Sylvester Keil committed
320 321
        CiteProc::CitationItem.new id: entry.id do |c|
          c.data = CiteProc::Item.new entry.to_citeproc
Sylvester Keil's avatar
Sylvester Keil committed
322
          c.data[:'citation-number'] = citation_number
Sylvester Keil's avatar
Sylvester Keil committed
323 324 325
        end
      end

326
      def cited_keys
327
        context['cited'] ||= []
328
      end
329

Sylvester Keil's avatar
Sylvester Keil committed
330 331 332 333 334 335
      def citation_number
        number = context['citation_number'] || 1
        context['citation_number'] = number.succ
        number
      end

336 337 338 339 340 341 342 343
      def cite(keys)
        items = keys.map do |key|
          if bibliography.key?(key)
            entry = bibliography[key]
            entry = entry.convert(*bibtex_filters) unless bibtex_filters.empty?
          else
            return missing_reference
          end
Sylvester Keil's avatar
Sylvester Keil committed
344
        end
345 346

        link_to "##{[prefix, keys[0]].compact.join('-')}", render_citation(items)
Sylvester Keil's avatar
Sylvester Keil committed
347
      end
Sylvester Keil's avatar
Sylvester Keil committed
348

349
      def cite_details(key, text)
Hiren Patel's avatar
Hiren Patel committed
350
        if bibliography.key?(key)
Sylvester Keil's avatar
Sylvester Keil committed
351
          link_to details_link_for(bibliography[key]), text || config['details_link']
Hiren Patel's avatar
Hiren Patel committed
352
        else
353
          missing_reference
Hiren Patel's avatar
Hiren Patel committed
354 355
        end
      end
356

Sylvester Keil's avatar
Sylvester Keil committed
357 358 359 360 361 362
      def content_tag(name, content_or_attributes, attributes = {})
        if content_or_attributes.is_a?(Hash)
          content, attributes = nil, content_or_attributes
        else
          content = content_or_attributes
        end
363

Sylvester Keil's avatar
Sylvester Keil committed
364
        attributes = attributes.map { |k,v| %Q(#{k}="#{v}") }
365

Sylvester Keil's avatar
Sylvester Keil committed
366 367 368 369 370 371
        if content.nil?
          "<#{[name, attributes].flatten.compact.join(' ')}/>"
        else
          "<#{[name, attributes].flatten.compact.join(' ')}>#{content}</#{name}>"
        end
      end
372

Sylvester Keil's avatar
Sylvester Keil committed
373 374 375
      def link_to(href, content, attributes = {})
        content_tag :a, content || href, attributes.merge(:href => href)
      end
376

377 378 379 380
      def cited_references
        context && context['cited'] || []
      end

381 382
      def keys
        # De-reference keys (in case they are variables)
Sylvester Keil's avatar
Sylvester Keil committed
383 384
        # We need to do this every time, to support for loops,
        # where the context can change for each invocation.
385 386 387 388 389
        Array(@keys).map do |key|
          context.send(:resolve, key) || key
        end
      end

Sylvester Keil's avatar
Sylvester Keil committed
390
      def set_context_to(context)
391
        @context, @site, = context, context.registers[:site]
Sylvester Keil's avatar
Sylvester Keil committed
392
        config.merge!(site.config['scholar'] || {})
393
        self
Sylvester Keil's avatar
Sylvester Keil committed
394
      end
395
    end
396

397
  end
Hiren Patel's avatar
Hiren Patel committed
398
end