mdtoc 0.2.0 → 0.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67fab334c40c8a785d1ac0bcca6234a1fc048ad479ed7fb4df093f0eb14c2116
4
- data.tar.gz: c80647e458fd2ceaa60189688d0c7a831ad023a20cb471fcc37d512e382db627
3
+ metadata.gz: dd651a744c265508fe1b78eae797f688f83a417d0710ed70fdff838a05e8e2b9
4
+ data.tar.gz: bfd727b632d7d5076ef17e309e1a2715f5de971a313e74190b9e1facadcade7a
5
5
  SHA512:
6
- metadata.gz: 2e6e9abb89b30d7abed179b42b00a8d871ab7b5ea6b96a4607ea5284bed3a1e1b2bb3355e0258d84498b78c8dba1463ab170f46e9c4c7963f4931b2efa910483
7
- data.tar.gz: a3c414d2472f1f2ad976bc02eca284c9b4f522ee21624ad30534d33563242cb2ac04457cf33a4bc1998f2af232f4aa803c2051ee066f910444d6f881c7b69396
6
+ metadata.gz: 2282ca2fb424b8dc093c856c83d4c13373bc10206d0a0f6c80c05c83329d057d1b3ce2c8dcf1a68ca2a7f303df22b95fccf3a71c1ead0f4c4e6da8049477005e
7
+ data.tar.gz: 714e58db5f76da82a910398f308698ed049bd56c9632b85d7131465d04d247ac57a0648b718f28b37dfa86be275535b0aee31a0cf5362a4f86523ced23037151
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 andornaut
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # mdtoc - Markdown Table of Contents
2
+
3
+ Read Markdown files and output a table of contents.
4
+
5
+ ## Installation
6
+
7
+ Requirements:
8
+
9
+ * [Ruby](https://www.ruby-lang.org/en/) (see [.ruby-version](./.ruby-version))
10
+
11
+ ```bash
12
+ gem install mdtoc
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```bash
18
+ $ mdtoc --help
19
+ Usage: mdtoc [options] files or directories...
20
+ -h, --help Show this message
21
+ -o, --output PATH Update a table of contents in the file at PATH
22
+ -a, --[no-]append Append to the --output file if a <!-- mdtoc --> tag isn't found
23
+ -c, --[no-]create Create the --output file if it does not exist
24
+ ```
25
+
26
+ 1. Add a `<!-- mdtoc -->` tag to a Markdown file.
27
+
28
+ ```bash
29
+ echo '<!-- mdtoc -->' >> README.md
30
+ ```
31
+
32
+ 2. Run `mdtoc` and specify input files or directories (eg. the "test/samples" directory) and an output file (eg. "README.md").
33
+
34
+ ```bash
35
+ mdtoc -aco README.md test/samples
36
+ ```
37
+
38
+ ## Example Rakefile
39
+
40
+ Create a `Rakefile` with the contents below, then run
41
+ [`rake`](https://github.com/ruby/rake) to:
42
+
43
+ * `git pullgem push pkg/mdtoc-0.2.0.gem`
44
+ * `git add` any *.md files
45
+ * Run `mdtoc` to update the generated table of contents in the ./README.md file
46
+ * Git commit and push any changes
47
+
48
+ ```ruby
49
+ task default: %w[mdtoc]
50
+
51
+ desc 'Update Markdown table of contents and push changes to the git repository'
52
+ task :mdtoc do
53
+ command = <<~CMD
54
+ set -e
55
+ if [ -n "$(git diff --name-only --diff-filter=U)" ]; then
56
+ echo 'Error: conflicts exist' >&2
57
+ exit 1
58
+ fi
59
+ mdtoc --append --create --output README.md docs/
60
+ git add *.md **/*.md
61
+ git commit -qm 'Update TOC' || true
62
+ git pull
63
+ git push
64
+ CMD
65
+ sh command, verbose: false do |ok, status|
66
+ unless ok
67
+ fail "Failed with status: #{status.exitstatus}"
68
+ end
69
+ end
70
+ end
71
+ ```
72
+
73
+ See [andornaut/til](https://github.com/andornaut/til/blob/master/Rakefile) for an example.
74
+
75
+ ## Development
76
+
77
+ ### Setup
78
+
79
+ Requirements:
80
+
81
+ * [Bundler](https://bundler.io/)
82
+ * [chruby](https://github.com/postmodern/chruby) (recommended)
83
+
84
+ ```bash
85
+ # Setup development environment
86
+ bin/setup
87
+ ```
88
+
89
+ ### Tasks
90
+
91
+ ```bash
92
+ # List rake tasks
93
+ $ rake -T
94
+ rake build # Build gem into the pkg directory
95
+ rake default # Run the build, rubocop, sorbet and test tasks
96
+ rake install # Build and install gem into system gems
97
+ rake rubocop # Run RuboCop
98
+ rake sorbet # Run the Sorbet type checker
99
+ rake test # Run tests
100
+ ```
101
+ # Run mdtoc with test inputs
102
+ $ ruby -Ilib bin/mdtoc test/samples
103
+
104
+ # Run mdtoc with test inputs, and write to a newly created output file
105
+ $ f=$(mktemp) && ruby -Ilib bin/mdtoc -aco ${f} test/samples ; cat ${f}
106
+ ```
107
+
108
+ ### Publishing
109
+
110
+ ```bash
111
+ rake release
112
+ ```
data/bin/setup ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'fileutils'
5
+
6
+ # path to your application root.
7
+ APP_ROOT = File.expand_path('..', __dir__)
8
+
9
+ def system!(*args)
10
+ system(*args) || abort("
11
+ == Command #{args} failed ==")
12
+ end
13
+
14
+ FileUtils.chdir APP_ROOT do
15
+ # This script is a way to set up a development environment or update your current one.
16
+ # You can run it with `bin/setup`.
17
+
18
+ puts '== Installing dependencies =='
19
+ system! 'gem install bundler --conservative'
20
+ system('bundle check') || system!('bundle install')
21
+
22
+ puts "
23
+ == Removing old RBIs =="
24
+ FileUtils.rm_rf('sorbet/rbi/gems')
25
+ FileUtils.rm_rf('sorbet/rbi/todo.rbi')
26
+
27
+ puts "
28
+ == Generating RBIs =="
29
+ system! 'bundle exec tapioca gems'
30
+
31
+ puts "
32
+ == Initializing Sorbet =="
33
+ system! 'bundle exec srb tc'
34
+
35
+ puts "
36
+ == Setup complete! =="
37
+ end
data/lib/mdtoc/cli.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "optparse"
5
- require "sorbet-runtime"
6
- require "tempfile"
4
+ require 'optparse'
5
+ require 'sorbet-runtime'
6
+ require 'tempfile'
7
7
 
8
8
  module Mdtoc
9
9
  module CLI
@@ -14,19 +14,19 @@ module Mdtoc
14
14
  def parse(args)
15
15
  parser = OptionParser.new do |parser_|
16
16
  parser_.banner = "Usage: #{parser_.program_name} [options] files or directories..."
17
- parser_.on("-h", "--help", "Show this message") do
17
+ parser_.on('-h', '--help', 'Show this message') do
18
18
  puts parser_
19
19
  exit
20
20
  end
21
- parser_.on("-o", "--output PATH", "Update a table of contents in the file at PATH")
22
- parser_.on("-a", "--[no-]append", "Append to the --output file if a <!-- mdtoc --> tag isn't found")
23
- parser_.on("-c", "--[no-]create", "Create the --output file if it does not exist")
21
+ parser_.on('-o', '--output PATH', 'Update a table of contents in the file at PATH')
22
+ parser_.on('-a', '--[no-]append', "Append to the --output file if a <!-- mdtoc --> tag isn't found")
23
+ parser_.on('-c', '--[no-]create', 'Create the --output file if it does not exist')
24
24
  end
25
25
 
26
26
  options = Options.new
27
27
  options.paths = parser.parse(args, into: options)
28
28
  if options.paths.empty?
29
- warn("Specify at least one file or directory to read")
29
+ warn('Specify at least one file or directory to read')
30
30
  exit(1)
31
31
  end
32
32
  options
@@ -0,0 +1,50 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'sorbet-runtime'
5
+
6
+ module Mdtoc
7
+ module Markdown
8
+ module FragmentGenerator
9
+ extend T::Sig
10
+ extend T::Helpers
11
+
12
+ interface!
13
+
14
+ sig { abstract.params(label: String).returns(String) }
15
+ def generate(label); end
16
+
17
+ class GitHub
18
+ extend T::Sig
19
+ include FragmentGenerator
20
+
21
+ sig { void }
22
+ def initialize
23
+ @counts = T.let(Hash.new(0), T::Hash[String, Integer])
24
+ end
25
+
26
+ sig { override.params(label: String).returns(String) }
27
+ def generate(label)
28
+ # GitHub's fragment generation:
29
+ # 1. Downcase
30
+ # 2. Replace spaces with dashes
31
+ # 3. Remove non-alphanumeric characters (keeping dashes, dots and underscores)
32
+ # 4. Collapse multiple dashes
33
+ # 5. Remove leading/trailing dashes and dots (common in many implementations)
34
+ fragment = label.downcase.tr(' ', '-').gsub(/[^\w.-]/, '')
35
+ fragment = fragment.gsub(/-+/, '-')
36
+ fragment = fragment.gsub(/^[.-]+|[.-]+$/, '')
37
+
38
+ count = @counts[fragment]
39
+ @counts[fragment] += 1
40
+
41
+ if count.positive?
42
+ "#{fragment}-#{count}"
43
+ else
44
+ fragment
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,7 +1,8 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
4
+ require 'sorbet-runtime'
5
+ require_relative 'fragment_generator'
5
6
 
6
7
  module Mdtoc
7
8
  module Markdown
@@ -10,9 +11,7 @@ module Mdtoc
10
11
 
11
12
  sig { params(depth: Integer, label: String, url: String).void }
12
13
  def initialize(depth, label, url)
13
- if depth < 0
14
- raise ArgumentError, "Header depth must be >= 0, but was #{depth}"
15
- end
14
+ raise ArgumentError, "Header depth must be >= 0, but was #{depth}" if depth.negative?
16
15
 
17
16
  @depth = depth
18
17
  @label = normalize_label(label)
@@ -21,7 +20,7 @@ module Mdtoc
21
20
 
22
21
  sig { returns(String) }
23
22
  def to_s
24
- prefix = " " * 2 * @depth
23
+ prefix = ' ' * 2 * @depth
25
24
  "#{prefix}* [#{@label}](#{@url})"
26
25
  end
27
26
 
@@ -33,16 +32,23 @@ module Mdtoc
33
32
  private
34
33
 
35
34
  def normalize_label(label)
36
- label = label.strip.tr("\t\n\r", "") # Remove whitespace characters other than spaces.
37
- label.gsub(/\[(.*)\]\(.*\)/, '\1') # Remove links
35
+ label = label.strip.tr("\t\n\r", '') # Remove whitespace characters other than spaces.
36
+ label.gsub(/\[(.*?)\]\(.*?\)/, '\1') # Remove links
38
37
  end
39
38
  end
40
39
 
41
40
  class HeaderWithFragment < Header
42
- sig { params(depth: Integer, label: String, url: String).void }
43
- def initialize(depth, label, url)
44
- super
45
- @url += "##{@label.downcase.tr(" ", "-").gsub(/[^\w\-]/, "")}"
41
+ sig do
42
+ params(
43
+ depth: Integer,
44
+ label: String,
45
+ url: String,
46
+ generator: FragmentGenerator
47
+ ).void
48
+ end
49
+ def initialize(depth, label, url, generator:)
50
+ super(depth, label, url)
51
+ @url += "##{generator.generate(@label)}"
46
52
  end
47
53
  end
48
54
  end
@@ -1,45 +1,104 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
5
- require_relative "header"
4
+ require 'sorbet-runtime'
5
+ require_relative 'header'
6
+ require_relative 'fragment_generator'
6
7
 
7
8
  module Mdtoc
8
9
  module Markdown
9
10
  class Parser
10
11
  extend T::Sig
11
12
 
12
- sig { params(depth: Integer, url: String).void }
13
- def initialize(depth, url)
13
+ sig do
14
+ params(
15
+ depth: Integer,
16
+ url: String,
17
+ generator: FragmentGenerator
18
+ ).void
19
+ end
20
+ def initialize(depth, url, generator: FragmentGenerator::GitHub.new)
14
21
  @depth = depth
15
22
  @url = url
23
+ @generator = generator
24
+ @in_code_block = T.let(false, T::Boolean)
25
+ @in_html_comment = T.let(false, T::Boolean)
16
26
  end
17
27
 
18
28
  sig { params(lines: T::Enumerable[String]).returns(T::Array[Header]) }
19
29
  def headers(lines)
20
- # TODO: Skip headers within multi-line comments.
21
- # TODO: Handle --- and === style headers.
22
- skip = T.let(false, T::Boolean)
23
- lines.filter_map do |line|
24
- # Skip code blocks.
25
- if line.start_with?("```") && !T.must(line[3..]).strip.end_with?("```")
26
- skip = !skip
30
+ @in_code_block = false
31
+ @in_html_comment = false
32
+
33
+ headers = T.let([], T::Array[Header])
34
+ prev_line = T.let(nil, T.nilable(String))
35
+
36
+ lines.each do |line|
37
+ stripped = line.strip
38
+
39
+ if skip_line?(line, stripped)
40
+ prev_line = line
41
+ next
42
+ end
43
+
44
+ if line.start_with?('#')
45
+ headers << header(line)
46
+ elsif (h = process_setext_header(stripped, prev_line))
47
+ headers << h
27
48
  end
28
- next if skip || !line.start_with?("#")
29
49
 
30
- header(line)
50
+ prev_line = line
31
51
  end
52
+
53
+ headers
32
54
  end
33
55
 
34
56
  private
35
57
 
58
+ sig { params(line: String, stripped: String).returns(T::Boolean) }
59
+ def skip_line?(line, stripped)
60
+ html_comment?(stripped) || code_block?(line)
61
+ end
62
+
63
+ sig { params(stripped: String).returns(T::Boolean) }
64
+ def html_comment?(stripped)
65
+ if stripped.start_with?('<!--')
66
+ @in_html_comment = true unless stripped.end_with?('-->')
67
+ return true
68
+ elsif @in_html_comment && stripped.end_with?('-->')
69
+ @in_html_comment = false
70
+ return true
71
+ end
72
+ @in_html_comment
73
+ end
74
+
75
+ sig { params(line: String).returns(T::Boolean) }
76
+ def code_block?(line)
77
+ if line.start_with?('```') && !T.must(line[3..]).strip.end_with?('```')
78
+ @in_code_block = !@in_code_block
79
+ return true
80
+ end
81
+ @in_code_block
82
+ end
83
+
84
+ sig { params(stripped: String, prev_line: T.nilable(String)).returns(T.nilable(Header)) }
85
+ def process_setext_header(stripped, prev_line)
86
+ return nil unless prev_line && !prev_line.strip.empty?
87
+
88
+ if stripped.match?(/^=+$/)
89
+ HeaderWithFragment.new(@depth, prev_line.strip, @url, generator: @generator)
90
+ elsif stripped.match?(/^-+$/)
91
+ HeaderWithFragment.new(@depth + 1, prev_line.strip, @url, generator: @generator)
92
+ end
93
+ end
94
+
36
95
  sig { params(line: String).returns(HeaderWithFragment) }
37
96
  def header(line)
38
97
  m = T.must(line.strip.match(/^(#+)\s*(.*)$/))
39
- num_hashes = m[1]&.count("#") || 1
98
+ num_hashes = m[1]&.count('#') || 1
40
99
  depth = @depth + num_hashes - 1
41
- label = m[2] || ""
42
- HeaderWithFragment.new(depth, label, @url)
100
+ label = m[2] || ''
101
+ HeaderWithFragment.new(depth, label, @url, generator: @generator)
43
102
  end
44
103
  end
45
104
  end
data/lib/mdtoc/node.rb CHANGED
@@ -1,15 +1,16 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "pathname"
5
- require "sorbet-runtime"
6
- require_relative "markdown/header"
7
- require_relative "markdown/parser"
4
+ require 'pathname'
5
+ require 'sorbet-runtime'
6
+ require_relative 'markdown/header'
7
+ require_relative 'markdown/parser'
8
8
 
9
9
  module Mdtoc
10
10
  class Node
11
11
  extend T::Helpers
12
12
  extend T::Sig
13
+
13
14
  abstract!
14
15
 
15
16
  class << self
@@ -18,10 +19,9 @@ module Mdtoc
18
19
  sig { params(path: String, depth: Integer).returns(Node) }
19
20
  def for_path(path, depth = 0)
20
21
  # Ensure that `path` is a relative path, so that all links are relative and therefore portable.
21
- path = Pathname.new(path)
22
- path = path.relative_path_from(Dir.pwd) if path.absolute?
23
- path = path.to_s
24
- File.directory?(path) ? DirNode.new(path, depth) : FileNode.new(path, depth)
22
+ pathname = Pathname.new(path)
23
+ pathname = pathname.relative_path_from(Dir.pwd) if pathname.absolute?
24
+ pathname.directory? ? DirNode.new(pathname, depth) : FileNode.new(pathname, depth)
25
25
  end
26
26
 
27
27
  sig { params(paths: T::Array[String]).returns(String) }
@@ -32,7 +32,7 @@ module Mdtoc
32
32
  end
33
33
  end
34
34
 
35
- sig { params(path: String, depth: Integer).void }
35
+ sig { params(path: Pathname, depth: Integer).void }
36
36
  def initialize(path, depth)
37
37
  @path = path
38
38
  @depth = depth
@@ -43,22 +43,30 @@ module Mdtoc
43
43
 
44
44
  sig { returns(String) }
45
45
  def label
46
- File.basename(@path, File.extname(@path)).gsub(/_+/, " ").gsub(/\s+/, " ").capitalize
46
+ @path.basename(@path.extname).to_s.gsub(/_+/, ' ').gsub(/\s+/, ' ').capitalize
47
47
  end
48
48
 
49
49
  class DirNode < Node
50
50
  sig { override.returns(T::Array[Mdtoc::Markdown::Header]) }
51
51
  def headers
52
- readme_path = T.let(nil, T.nilable(String))
53
- child_headers = Dir
54
- .each_child(@path)
55
- .reject { |path| readme_path = File.join(@path, path) if path.casecmp?("readme.md") }
56
- .sort!
57
- .flat_map { |path| Node.for_path(File.join(@path, path), @depth + 1).headers }
52
+ readme_path = T.let(nil, T.nilable(Pathname))
53
+ children = @path.children.reject do |child|
54
+ if child.basename.to_s.casecmp?('readme.md')
55
+ readme_path = child
56
+ true
57
+ else
58
+ false
59
+ end
60
+ end
61
+
62
+ child_headers = children.sort.flat_map do |child|
63
+ Node.for_path(child.to_s, @depth + 1).headers
64
+ end
65
+
58
66
  return child_headers unless readme_path
59
67
 
60
68
  # Include the headers from the README at the beginning.
61
- readme_headers = FileNode.new(readme_path, @depth).headers
69
+ readme_headers = FileNode.new(T.must(readme_path), @depth).headers
62
70
  readme_headers + child_headers
63
71
  end
64
72
  end
@@ -66,11 +74,12 @@ module Mdtoc
66
74
  class FileNode < Node
67
75
  sig { override.returns(T::Array[Mdtoc::Markdown::Header]) }
68
76
  def headers
69
- parser = Markdown::Parser.new(@depth, @path)
70
- headers = parser.headers(File.foreach(@path))
77
+ path_s = @path.to_s
78
+ parser = Markdown::Parser.new(@depth, path_s)
79
+ headers = parser.headers(@path.each_line)
71
80
  return headers if headers[0]&.top_level?(@depth)
72
81
 
73
- headers.unshift(Mdtoc::Markdown::Header.new(@depth, label, @path))
82
+ headers.unshift(Mdtoc::Markdown::Header.new(@depth, label, path_s))
74
83
  end
75
84
  end
76
85
  end
data/lib/mdtoc/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Mdtoc
5
- VERSION = "0.2.0"
5
+ VERSION = '0.3.1'
6
6
  end
data/lib/mdtoc/writer.rb CHANGED
@@ -1,10 +1,13 @@
1
- # typed: strict
1
+ # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
+ require 'fileutils'
5
+ require 'tempfile'
6
+
4
7
  module Mdtoc
5
8
  module Writer
6
- COMMENT_BEGIN = "<!-- mdtoc -->"
7
- COMMENT_END = "<!-- mdtoc-end -->"
9
+ COMMENT_BEGIN = '<!-- mdtoc -->'
10
+ COMMENT_END = '<!-- mdtoc-end -->'
8
11
 
9
12
  class << self
10
13
  extend T::Sig
@@ -13,8 +16,15 @@ module Mdtoc
13
16
  def write(toc, path, append, create)
14
17
  validate_path(path, create)
15
18
  new_content = content(toc, path, append)
16
- File.open(path, "w") do |f|
17
- f.write(new_content)
19
+
20
+ # Write to a temporary file and rename it to the target path to ensure atomic writing.
21
+ temp = Tempfile.new(File.basename(path), File.dirname(path))
22
+ begin
23
+ temp.write(new_content)
24
+ temp.close
25
+ FileUtils.mv(temp.path, path)
26
+ ensure
27
+ temp.close!
18
28
  end
19
29
  end
20
30
 
@@ -25,17 +35,12 @@ module Mdtoc
25
35
  toc = "#{COMMENT_BEGIN}\n#{toc}\n#{COMMENT_END}"
26
36
 
27
37
  begin
28
- f = File.open(path)
29
- rescue
30
- # If File.open failed because the file didn't exist, then we know that --create
38
+ old_content = File.read(path)
39
+ rescue StandardError
40
+ # If File.read failed because the file didn't exist, then we know that --create
31
41
  # was specified due to the validation in validate_path.
32
42
  return "#{toc}\n"
33
43
  end
34
- begin
35
- old_content = T.must(f.read)
36
- ensure
37
- f.close
38
- end
39
44
 
40
45
  if Regexp.new(Regexp.escape(COMMENT_BEGIN), Regexp::IGNORECASE).match?(old_content)
41
46
  return old_content.gsub(
data/lib/mdtoc.rb CHANGED
@@ -1,11 +1,11 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "optparse"
5
- require "tempfile"
6
- require_relative "mdtoc/cli"
7
- require_relative "mdtoc/node"
8
- require_relative "mdtoc/writer"
4
+ require 'optparse'
5
+ require 'tempfile'
6
+ require_relative 'mdtoc/cli'
7
+ require_relative 'mdtoc/node'
8
+ require_relative 'mdtoc/writer'
9
9
 
10
10
  module Mdtoc
11
11
  class << self
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mdtoc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - andornaut
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-04-29 00:00:00.000000000 Z
11
+ date: 2026-03-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -66,20 +66,6 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.50'
69
- - !ruby/object:Gem::Dependency
70
- name: rubocop-shopify
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: 2.0.0
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: 2.0.0
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: rubocop-sorbet
85
71
  requirement: !ruby/object:Gem::Requirement
@@ -165,7 +151,7 @@ description: |
165
151
  Create a `Rakefile` with the contents below, then run
166
152
  [`rake`](https://github.com/ruby/rake) to:
167
153
 
168
- * `git pull`
154
+ * `git pullgem push pkg/mdtoc-0.2.0.gem`
169
155
  * `git add` any *.md files
170
156
  * Run `mdtoc` to update the generated table of contents in the ./README.md file
171
157
  * Git commit and push any changes
@@ -204,10 +190,11 @@ description: |
204
190
  Requirements:
205
191
 
206
192
  * [Bundler](https://bundler.io/)
193
+ * [chruby](https://github.com/postmodern/chruby) (recommended)
207
194
 
208
195
  ```bash
209
- # Install dependencies
210
- bundle
196
+ # Setup development environment
197
+ bin/setup
211
198
  ```
212
199
 
213
200
  ### Tasks
@@ -215,32 +202,38 @@ description: |
215
202
  ```bash
216
203
  # List rake tasks
217
204
  $ rake -T
218
- rake build # Build mdtoc-0.1.5.gem into the pkg directory
219
- rake default # Run the build, rubocop:autocorrect_all, sorbet and test tasks
220
- rake install # Build and install mdtoc-0.1.5.gem into system gems
221
- rake install:local # Build and install mdtoc-0.1.5.gem into system gems without network access
222
- rake release[remote] # Create tag v0.1.5 and build and push mdtoc-0.1.5.gem to rubygems.org
205
+ rake build # Build gem into the pkg directory
206
+ rake default # Run the build, rubocop, sorbet and test tasks
207
+ rake install # Build and install gem into system gems
223
208
  rake rubocop # Run RuboCop
224
- rake rubocop:autocorrect # Autocorrect RuboCop offenses (only when it's safe)
225
- rake rubocop:autocorrect_all # Autocorrect RuboCop offenses (safe and unsafe)
226
209
  rake sorbet # Run the Sorbet type checker
227
210
  rake test # Run tests
228
-
211
+ ```
229
212
  # Run mdtoc with test inputs
230
213
  $ ruby -Ilib bin/mdtoc test/samples
231
214
 
232
215
  # Run mdtoc with test inputs, and write to a newly created output file
233
216
  $ f=$(mktemp) && ruby -Ilib bin/mdtoc -aco ${f} test/samples ; cat ${f}
234
217
  ```
218
+
219
+ ### Publishing
220
+
221
+ ```bash
222
+ rake release
223
+ ```
235
224
  email:
236
225
  executables:
237
226
  - mdtoc
238
227
  extensions: []
239
228
  extra_rdoc_files: []
240
229
  files:
230
+ - LICENSE
231
+ - README.md
241
232
  - bin/mdtoc
233
+ - bin/setup
242
234
  - lib/mdtoc.rb
243
235
  - lib/mdtoc/cli.rb
236
+ - lib/mdtoc/markdown/fragment_generator.rb
244
237
  - lib/mdtoc/markdown/header.rb
245
238
  - lib/mdtoc/markdown/parser.rb
246
239
  - lib/mdtoc/node.rb
@@ -249,7 +242,8 @@ files:
249
242
  homepage: https://github.com/andornaut/mdtoc
250
243
  licenses:
251
244
  - MIT
252
- metadata: {}
245
+ metadata:
246
+ rubygems_mfa_required: 'true'
253
247
  post_install_message:
254
248
  rdoc_options: []
255
249
  require_paths:
@@ -265,7 +259,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
265
259
  - !ruby/object:Gem::Version
266
260
  version: '0'
267
261
  requirements: []
268
- rubygems_version: 3.2.3
262
+ rubygems_version: 3.4.10
269
263
  signing_key:
270
264
  specification_version: 4
271
265
  summary: Read Markdown files and output a table of contents