Class: YardGhurt::GFMFixTask

Inherits:
Rake::TaskLib
  • Object
show all
Defined in:
lib/yard_ghurt/gfm_fix_task.rb

Overview

Fix (find & replace) text in the GitHub Flavored Markdown (GFM) files in the YARDoc directory, for differences between the two formats.

You can set #dry_run on the command line:

rake yard_gfm_fix dryrun=true

Examples:

What I Use

YardGhurt::GFMFixTask.new do |task|
  task.arg_names = [:dev]
  task.dry_run = false
  task.fix_code_langs = true
  task.md_files = ['index.html']

  task.before = proc do |task2,args|
    # Delete this file as it's never used (`index.html` is an exact copy).
    YardGhurt.rm_exist(File.join(task2.doc_dir,'file.README.html'))

    # Root dir of my GitHub Page for CSS/JS.
    ghp_root_dir = YardGhurt.to_bool(args.dev) ? '../../esotericpig.github.io' : '../../..'

    task2.css_styles << %(
      <link rel="stylesheet" type="text/css" href="#{ghp_root_dir}/css/prism.css" />
    )
    task2.js_scripts << %(<script src="#{ghp_root_dir}/js/prism.js"></script>)
  end
end

Since:

  • 1.1.0

Constant Summary collapse

CSS_COMMENT =

This is important so that a subsequent call to this task will not write the CSS again.

Returns:

  • (String)

    the comment tag of where to place #css_styles

See Also:

Since:

  • 1.1.0

"<!-- #{self} CSS - Do NOT remove this comment! -->"
JS_COMMENT =

This is important so that a subsequent call to this task will not write the JS again.

Returns:

  • (String)

    the comment tag of where to place #js_scripts

See Also:

Since:

  • 1.1.0

"<!-- #{self} JS - Do NOT remove this comment! -->"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name = :yard_gfm_fix) {|_self| ... } ⇒ GFMFixTask

Returns a new instance of GFMFixTask.

Parameters:

  • name (Symbol) (defaults to: :yard_gfm_fix)

    the name of this task to use on the command line with rake

Yields:

  • (_self)

Yield Parameters:

Since:

  • 1.1.0



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 232

def initialize(name = :yard_gfm_fix)
  super()

  @after = nil
  @anchor_db = {}
  @arg_names = []
  @before = nil
  @css_styles = []
  @custom_gsub = nil
  @custom_gsubs = []
  @deps = []
  @description = 'Fix (find & replace) text in the YARDoc GitHub Flavored Markdown files'
  @doc_dir = 'doc'
  @dry_run = false
  @during = nil
  @exclude_code_langs = Set['ruby']
  @fix_anchor_links = true
  @fix_code_langs = false
  @fix_file_links = true
  @js_scripts = []
  @md_files = ['file.README.html','index.html']
  @name = name
  @verbose = true

  yield(self) if block_given?
  define
end

Instance Attribute Details

#afterProc?

Returns the Proc ( respond_to?(:call) ) to call at the end of this task or nil; default: nil.

Examples:

task.arg_names = [:dev]

# @param task [self]
# @param args [Rake::TaskArguments] the args specified by {arg_names}
task.after = proc do |task,args|
  puts args.dev
end

Returns:

  • (Proc, nil)

    the Proc ( respond_to?(:call) ) to call at the end of this task or nil; default: nil

Since:

  • 1.1.0



72
73
74
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 72

def after
  @after
end

#anchor_dbHash

The anchor links to override in the database.

The keys are GFM anchor IDs and the values are their equivalent YARDoc anchor IDs.

Returns:

  • (Hash)

    the custom database (key-value pairs) of GFM anchor links to YARDoc anchor links; default: {}

See Also:

Since:

  • 1.1.0



83
84
85
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 83

def anchor_db
  @anchor_db
end

#arg_namesArray<Symbol>, Symbol

Returns the custom arg(s) for this task; default: [].

Returns:

  • (Array<Symbol>, Symbol)

    the custom arg(s) for this task; default: []

Since:

  • 1.1.0



86
87
88
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 86

def arg_names
  @arg_names
end

#beforeProc?

Returns the Proc ( respond_to?(:call) ) to call at the beginning of this task or nil; default: nil.

Examples:

task.arg_names = [:dev]

# @param task [self]
# @param args [Rake::TaskArguments] the args specified by {arg_names}
task.before = proc do |task,args|
  puts args.dev
end

Returns:

  • (Proc, nil)

    the Proc ( respond_to?(:call) ) to call at the beginning of this task or nil; default: nil

Since:

  • 1.1.0



99
100
101
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 99

def before
  @before
end

#css_stylesArray<String>

Returns the CSS styles to add to each file; default: [].

Examples:

task.css_styles << '<link rel="stylesheet" type="text/css" href="css/prism.css" />'

Returns:

  • (Array<String>)

    the CSS styles to add to each file; default: []

Since:

  • 1.1.0



105
106
107
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 105

def css_styles
  @css_styles
end

#custom_gsubProc?

Returns the custom Proc ( respond_to?(:call) ) to call to gsub! each line for each file.

Examples:

# +gsub!()+ (and other mutable methods) must be used
# as the return value must be +true+ or +false+.
#
# @param line [String] the current line being processed from the current file
#
# @return [true,false] whether there was a change
task.custom_gsub = proc do |line|
  has_change = false

  has_change = !line.gsub!('dev','prod').nil? || has_change
  # More changes...

  return has_change
end

Returns:

  • (Proc, nil)

    the custom Proc ( respond_to?(:call) ) to call to gsub! each line for each file

Since:

  • 1.1.0



124
125
126
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 124

def custom_gsub
  @custom_gsub
end

#custom_gsubsArray<[Regexp,String]>

Returns the custom args to use in gsub on each line for each file.

Examples:

task.custom_gsubs = [
  ['dev','prod'],
  [/href="#[^"]*"/,'href="#contents"']
]

# Internal code:
# ---
# @custom_gsubs.each do |custom_gsub|
#   line.gsub!(custom_gsub[0],custom_gsub[1])
# end

Returns:

  • (Array<[Regexp,String]>)

    the custom args to use in gsub on each line for each file

Since:

  • 1.1.0



139
140
141
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 139

def custom_gsubs
  @custom_gsubs
end

#depsArray<Symbol>, Symbol

Returns the custom dependencies for this task; default: [].

Examples:

task.deps = :yard
# or...
task.deps = [:clobber,:yard]

Returns:

  • (Array<Symbol>, Symbol)

    the custom dependencies for this task; default: []

Since:

  • 1.1.0



147
148
149
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 147

def deps
  @deps
end

#descriptionString

Returns the description of this task (customizable).

Returns:

  • (String)

    the description of this task (customizable)

Since:

  • 1.1.0



150
151
152
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 150

def description
  @description
end

#doc_dirString

Returns the directory of generated YARDoc files; default: doc.

Returns:

  • (String)

    the directory of generated YARDoc files; default: doc

Since:

  • 1.1.0



153
154
155
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 153

def doc_dir
  @doc_dir
end

#dry_runtrue, false Also known as: dry_run?

Returns whether to run a dry run (no writing to the files); default: false.

Returns:

  • (true, false)

    whether to run a dry run (no writing to the files); default: false

Since:

  • 1.1.0



156
157
158
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 156

def dry_run
  @dry_run
end

#duringProc?

Returns the Proc to call ( respond_to?(:call) ) at the beginning of processing each file or nil; default: nil.

Examples:

task.arg_names = [:dev]

# @param task [self]
# @param args [Rake::TaskArguments] the args specified by {arg_names}
# @param file [String] the current file being processed
task.during = proc do |task,args,file|
  puts args.dev
end

Returns:

  • (Proc, nil)

    the Proc to call ( respond_to?(:call) ) at the beginning of processing each file or nil; default: nil

Since:

  • 1.1.0



170
171
172
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 170

def during
  @during
end

#exclude_code_langsSet<String>

Returns the case-sensitive code languages to not fix; default: Set[ ‘ruby’ ].

Returns:

  • (Set<String>)

    the case-sensitive code languages to not fix; default: Set[ ‘ruby’ ]

See Also:

Since:

  • 1.1.0



175
176
177
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 175

def exclude_code_langs
  @exclude_code_langs
end

Returns whether to fix anchor links; default: true.

Returns:

  • (true, false)

    whether to fix anchor links; default: true

Since:

  • 1.1.0



178
179
180
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 178

def fix_anchor_links
  @fix_anchor_links
end

#fix_code_langstrue, false Also known as: fix_code_langs?

If true, language- will be added to code classes, except for #exclude_code_langs.

For example, code class=“ruby” will be changed to code class=“language-ruby”.

Returns:

  • (true, false)

    whether to fix code languages; default: false

Since:

  • 1.1.0



185
186
187
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 185

def fix_code_langs
  @fix_code_langs
end

If true, local file links (if the local file exists), will be changed to file.{filename}.html.

This is useful for README.md, LICENSE.txt, etc.

Returns:

  • (true, false)

    whether to fix local file links; default: true

Since:

  • 1.1.0



192
193
194
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 192

def fix_file_links
  @fix_file_links
end

#has_css_commenttrue, false Also known as: has_css_comment?

This is an internal flag meant to be changed internally.

Returns:

  • (true, false)

    whether CSS_COMMENT has been seen/added; default: false

See Also:

Since:

  • 1.1.0



199
200
201
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 199

def has_css_comment
  @has_css_comment
end

#has_js_commenttrue, false Also known as: has_js_comment?

This is an internal flag meant to be changed internally.

Returns:

  • (true, false)

    whether JS_COMMENT has been seen/added; default: false

See Also:

Since:

  • 1.1.0



206
207
208
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 206

def has_js_comment
  @has_js_comment
end

#js_scriptsArray<String>

Returns the JS scripts to add to each file; default: [].

Examples:

task.js_scripts << '<script src="js/prism.js"></script>'

Returns:

  • (Array<String>)

    the JS scripts to add to each file; default: []

Since:

  • 1.1.0



212
213
214
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 212

def js_scripts
  @js_scripts
end

#md_filesArray<String>

Returns the (GFM) Markdown files to fix; default: [‘file.README.html’,‘index.html’].

Returns:

  • (Array<String>)

    the (GFM) Markdown files to fix; default: [‘file.README.html’,‘index.html’]

Since:

  • 1.1.0



215
216
217
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 215

def md_files
  @md_files
end

#nameString

Returns the name of this task (customizable); default: yard_gfm_fix.

Returns:

  • (String)

    the name of this task (customizable); default: yard_gfm_fix

Since:

  • 1.1.0



218
219
220
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 218

def name
  @name
end

#verbosetrue, false Also known as: verbose?

Returns whether to output each change to stdout; default: true.

Returns:

  • (true, false)

    whether to output each change to stdout; default: true

Since:

  • 1.1.0



221
222
223
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 221

def verbose
  @verbose
end

Instance Method Details

#add_css_styles!(line) ⇒ Object

Add CSS_COMMENT & #css_styles to line if it is </head>, unless CSS_COMMENT has already been found or #has_css_comment.

Parameters:

  • line (String)

    the line from the file to check if </head>

Since:

  • 1.1.0



405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 405

def add_css_styles!(line)
  return false if @has_css_comment || @css_styles.empty?

  if line.strip == CSS_COMMENT
    @has_css_comment = true

    return false
  end

  return false unless line =~ %r{^\s*</head>\s*$}i

  line.slice!(0,line.length)
  line << "    #{CSS_COMMENT}"

  @css_styles.each do |css_style|
    line << "\n    #{css_style}"
  end

  line << "\n\n  </head>"

  @has_css_comment = true

  return true
end

#add_js_scripts!(line) ⇒ Object

Add JS_COMMENT & #js_scripts to line if it is </body>, unless JS_COMMENT has already been found or #has_js_comment.

Parameters:

  • line (String)

    the line from the file to check if </body>

Since:

  • 1.1.0



434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 434

def add_js_scripts!(line)
  return false if @has_js_comment || @js_scripts.empty?

  if line.strip == JS_COMMENT
    @has_js_comment = true

    return false
  end

  return false unless line =~ %r{^\s*</body>\s*$}i

  line.slice!(0,line.length)
  line << "\n    #{JS_COMMENT}"

  @js_scripts.each do |js_script|
    line << "\n    #{js_script}"
  end

  line << "\n\n  </body>"

  @has_js_comment = true

  return true
end

Convert each HTML header tag in md_file to a GFM & YARDoc anchor link and build a database using them.

Parameters:

  • md_file (String)

    the file (no dir) to build the anchor links database with, will be joined to #doc_dir

See Also:

Since:

  • 1.1.0



303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 303

def build_anchor_links_db(md_file)
  filename = File.join(@doc_dir,md_file)

  return unless File.exist?(filename)

  File.open(filename,'r') do |file|
    file.each_line do |line|
      # +<h3 id="..."+ or +<h3...+
      # - +yard_id+ was added for YARD v0.9.25+.
      match = line.match(/<\s*h\d+.*?id\s*=\s*["'](?<yard_id>[^"']+)["']|<\s*h\d+[\s>]/ui)

      next unless match

      yard_id = nil
      caps = match.named_captures

      if caps.key?('yard_id')
        yard_id = caps['yard_id'].to_s.strip
        yard_id = nil if yard_id.empty?
      end

      line.gsub!(/<[^>]+>/,'') # Remove tags: <...>
      line.strip!

      next if line.empty?

      @anchor_links.add_anchor(line,yard_id: yard_id)
    end
  end

  @anchor_links.merge_anchor_ids!(@anchor_db)
end

#defineObject

Define the Rake task and description using the instance variables.

Since:

  • 1.1.0



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 269

def define
  desc @description
  task @name,Array(@arg_names) => Array(@deps) do |_task,args|
    env_dryrun = ENV['dryrun']

    if !env_dryrun.nil? && !(env_dryrun = env_dryrun.to_s.strip).empty?
      @dry_run = Util.to_bool(env_dryrun)
    end

    @before.call(self,args) if @before.respond_to?(:call)

    @md_files.each do |md_file|
      reset_per_file
      build_anchor_links_db(md_file)

      @during.call(self,args,md_file) if @during.respond_to?(:call)

      fix_md_file(md_file)
    end

    @after.call(self,args) if @after.respond_to?(:call)
  end

  return self
end

#fix_md_file(md_file) ⇒ Object

Fix (find & replace) text in md_file. Calls all add_* & gsub_* methods.

Parameters:

  • md_file (String)

    the file (no dir) to fix, will be joined to #doc_dir

Since:

  • 1.1.0



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 339

def fix_md_file(md_file)
  filename = File.join(@doc_dir,md_file)

  puts "[#{filename}]:"

  if !File.exist?(filename)
    puts '! File does not exist'

    return
  end

  changes = 0
  lines = []

  File.open(filename,'r') do |file|
    file.each_line do |line|
      if line.strip.empty?
        lines << line

        next
      end

      has_change = false

      # Standard.
      has_change = add_css_styles!(line) || has_change
      has_change = add_js_scripts!(line) || has_change
      has_change = gsub_anchor_links!(line) || has_change
      has_change = gsub_code_langs!(line) || has_change
      has_change = gsub_local_file_links!(line) || has_change

      # Custom.
      has_change = gsub_customs!(line) || has_change
      has_change = gsub_custom!(line) || has_change

      if has_change
        puts "+ #{line}" if @verbose

        changes += 1
      end

      lines << line
    end
  end

  print '= '

  if changes > 0
    if @dry_run
      puts 'Nothing written (dry run)'
    else
      File.open(filename,'w') do |file|
        file.puts lines
      end

      puts "#{changes} changes written"
    end
  else
    puts 'Nothing written (up-to-date)'
  end
end

#gsub_anchor_links!(line) ⇒ Object

Replace GFM anchor links with their equivalent YARDoc anchor links, using #build_anchor_links_db & #anchor_db, if #fix_anchor_links.

Parameters:

  • line (String)

    the line from the file to fix

Since:

  • 1.1.0



463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 463

def gsub_anchor_links!(line)
  return false unless @fix_anchor_links

  has_change = false

  # +href="#..."+ or +href='#...'+
  line.gsub!(/href\s*=\s*["']\s*#\s*(?<link>[^"']+)["']/ui) do |href|
    link = Regexp.last_match[:link].to_s.strip # Same as +$~[:link]+.

    if link.empty? || @anchor_links.yard_anchor_id?(link)
      href
    else
      yard_link = @anchor_links[link]

      if yard_link.nil?
        # Either the GFM link is wrong [check with @anchor_links.to_github_anchor_id()]
        #   or the internal code is broken [check with @anchor_links.to_s()].
        puts "! YARDoc anchor link for GFM anchor link [#{link}] does not exist"

        if !@has_verbose_anchor_links
          if @verbose
            puts '  GFM anchor link in the Markdown file is wrong?'
            puts '  Please check the generated links:'
            puts %(  #{@anchor_links.to_s.strip.gsub("\n","\n  ")})
          else
            puts "  Turn on #{self.class}.verbose for more info"
          end

          @has_verbose_anchor_links = true
        end

        href
      else
        has_change = true

        %(href="##{yard_link}")
      end
    end
  end

  return has_change
end

#gsub_code_langs!(line) ⇒ Object

Add language- to code class languages and down case them, if #fix_code_langs and the language is not in #exclude_code_langs.

Parameters:

  • line (String)

    the line from the file to fix

Since:

  • 1.1.0



510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 510

def gsub_code_langs!(line)
  return false unless @fix_code_langs

  has_change = false
  tag = 'code class="'

  line.gsub!(Regexp.new(%(#{Regexp.quote(tag)}[^"]*"))) do |code_class|
    lang = code_class[tag.length..-2].strip

    if lang.empty? || lang =~ /^language-/ || @exclude_code_langs.include?(lang)
      code_class
    else
      has_change = true

      %(#{tag}language-#{lang.downcase}")
    end
  end

  return has_change
end

#gsub_custom!(line) ⇒ Object

Call the custom Proc #custom_gsub (if it responds to :call) on line.

Parameters:

  • line (String)

    the line from the file to fix

Since:

  • 1.1.0



534
535
536
537
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 534

def gsub_custom!(line)
  return false unless @custom_gsub.respond_to?(:call)
  return @custom_gsub.call(line)
end

#gsub_customs!(line) ⇒ Object

Call gsub!() on line with each #custom_gsubs, which is an Array of pairs of arguments:

task.custom_gsubs = [
  ['dev','prod'],
  [/href="#[^"]*"/,'href="#contents"']
]

Parameters:

  • line (String)

    the line from the file to fix

Since:

  • 1.1.0



547
548
549
550
551
552
553
554
555
556
557
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 547

def gsub_customs!(line)
  return false if @custom_gsubs.empty?

  has_change = false

  @custom_gsubs.each do |custom_gsub|
    has_change = !line.gsub!(custom_gsub[0],custom_gsub[1]).nil? || has_change
  end

  return has_change
end

#gsub_local_file_links!(line) ⇒ Object

Replace local file links (that exist) to be file.{filename}.html, if #fix_file_links.

Parameters:

  • line (String)

    the line from the file to fix

Since:

  • 1.1.0



563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 563

def gsub_local_file_links!(line)
  return false unless @fix_file_links

  has_change = false
  tag = 'href="'

  line.gsub!(Regexp.new(%(#{Regexp.quote(tag)}[^#][^"]*"))) do |href|
    link = href[tag.length..-2].strip

    if link.empty? || !File.exist?(link)
      href
    else
      link = File.basename(link,'.*')
      has_change = true

      %(#{tag}file.#{link}.html")
    end
  end

  return has_change
end

#reset_per_fileObject

Reset certain instance vars per file.

Since:

  • 1.1.0



261
262
263
264
265
266
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 261

def reset_per_file
  @anchor_links = AnchorLinks.new
  @has_css_comment = false
  @has_js_comment = false
  @has_verbose_anchor_links = false
end