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.new() do |t2,args|
    # Delete this file as it's never used (index.html is an exact copy)
    YardGhurt.rm_exist(File.join(t2.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' : '../../..'

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

Using All Options

YardGhurt::GFMFixTask.new(:yard_fix) do |task|
  task.anchor_db           = {'tests' => 'Testing'} # #tests => #Testing
  task.arg_names          << :name # Custom args
  task.css_styles         << '<link rel="stylesheet" href="css/my_css.css" />' # Inserted at </head>
  task.css_styles         << '<style>body{ background-color: linen; }</style>'
  task.custom_gsub         = Proc.new() {|line| !line.gsub!('YardGhurt','YARD GHURT!').nil?()}
  task.custom_gsubs       << [/newline/i,'Do you smell what The Rock is cooking?']
  task.deps               << :yard # Custom dependencies
  task.description         = 'Fix it'
  task.doc_dir             = 'doc'
  task.dry_run             = false
  task.exclude_code_langs  = Set['ruby']
  task.fix_anchor_links    = true
  task.fix_code_langs      = true
  task.fix_file_links      = true
  task.js_scripts         << '<script src="js/my_js.js"></script>' # Inserted at </body>
  task.js_scripts         << '<script>document.write("Hello World!");</script>'
  task.md_files            = ['index.html']
  task.verbose             = false

  task.before = Proc.new() {|task,args| puts "Hi, #{args.name}!"}
  task.during = Proc.new() {|task,args,file| puts "#{args.name} can haz #{file}?"}
  task.after  = Proc.new() {|task,args| puts "Goodbye, #{args.name}!"}
end

Author:

  • Jonathan Bradley Whited

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



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 262

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.new 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



102
103
104
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 102

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



113
114
115
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 113

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



116
117
118
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 116

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.new 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



129
130
131
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 129

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



135
136
137
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 135

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.new 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



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

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



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

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



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

def deps
  @deps
end

#descriptionString

Returns the description of this task (customizable).

Returns:

  • (String)

    the description of this task (customizable)

Since:

  • 1.1.0



180
181
182
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 180

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



183
184
185
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 183

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



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

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.new 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



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

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



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

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



208
209
210
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 208

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



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

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



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

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



229
230
231
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 229

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



236
237
238
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 236

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



242
243
244
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 242

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



245
246
247
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 245

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



248
249
250
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 248

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



251
252
253
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 251

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



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

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



464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 464

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



333
334
335
336
337
338
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
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 333

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



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 299

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



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
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 369

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



493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 493

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 %Q(  #{@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

        %Q(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



540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 540

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

      %Q(#{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



564
565
566
567
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 564

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



577
578
579
580
581
582
583
584
585
586
587
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 577

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



593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 593

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

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

  return has_change
end

#reset_per_fileObject

Reset certain instance vars per file.

Since:

  • 1.1.0



291
292
293
294
295
296
# File 'lib/yard_ghurt/gfm_fix_task.rb', line 291

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