readme_yard 0.2.0 → 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.
data/README_YARD.md CHANGED
@@ -1,18 +1,29 @@
1
1
  # Readme Yard 🌿
2
2
  [![Gem Version](https://badge.fury.io/rb/readme_yard.svg)](https://badge.fury.io/rb/readme_yard)
3
- [![Maintainability](https://api.codeclimate.com/v1/badges/9fe0012930c3886dbe00/maintainability)](https://codeclimate.com/github/mattruzicka/readme_yard/maintainability)
3
+
4
+ **Code Version: {@string ReadmeYard::VERSION}**
4
5
 
5
6
  {@readme ReadmeYard}
6
7
 
7
8
  ---
8
9
 
10
+ ⚠️ **Generated file warning** – Edit README_YARD.md, not README.md. Changes to README.md will be lost when running `readme build`.
11
+
12
+ ### Future Work
13
+ - Implement safeguards to prevent accidental edits to README.md
14
+ - Support bidirectional editing through git integration
15
+
16
+ [PRs are welcome](#contributing) for these improvements.
17
+
18
+ ---
19
+
9
20
  ## Table of Contents
10
21
  - [Installation](#installation)
11
22
  - [Getting Started](#getting-started)
12
23
  - [Command Line Usage](#command-line-usage)
13
24
  - [Tag Usage](#tag-usage)
14
25
  - [Readme Tag](#readme-tag)
15
- - [Readme YARD Tags](#readme-yard-tags)
26
+ - [Standalone Tag Usage](#standalone-tag-usage)
16
27
  - [Example Tag](#example-tag)
17
28
  - [Contributing](#contributing)
18
29
 
@@ -20,13 +31,17 @@
20
31
 
21
32
  ## Installation
22
33
 
23
- Add [gem "readme_yard"](https://rubygems.org/gems/readme_yard) to your application's Gemfile and run `bundle install` or install it yourself with: `gem install readme_yard`
34
+ Add [gem "readme_yard"](https://rubygems.org/gems/readme_yard) to your Gemfile and run `bundle install` or install it yourself with: `gem install readme_yard`
35
+
36
+ **Note:** As of version 0.3.0, Readme Yard requires Ruby 3.0 or higher.
24
37
 
25
38
  ## Getting Started
26
39
 
27
- Run `readme build` at the command line. That will create a README_YARD.md file if there isnt one by copying your exisiting README.md file.
40
+ Run `readme build` at the command line. This creates a README_YARD.md file if there isn't one by copying your existing README.md file.
28
41
 
29
- README_YARD.md is the template from which `readme build` generates the README. It augments your markdown with tagging capabilities as described in the section on [Tag Usage](#tag-usage).
42
+ README_YARD.md is the template from which `readme build` generates the README. Readme Yard adds the ability to embed and reference your source code in your README via README_YARD.md.
43
+
44
+ See [Tag Usage](#tag-usage).
30
45
 
31
46
  ---
32
47
 
@@ -36,80 +51,139 @@ README_YARD.md is the template from which `readme build` generates the README. I
36
51
 
37
52
  ## Tag Usage
38
53
 
39
- Readme Yard uses **README tags** and **YARD tags** in order to find, format, and embed Ruby source code inside README.md.
54
+ Readme Yard uses YARD tags and custom markdown tags. YARD tags live inside Ruby source code. The markdown tags live inside README_YARD.md.
40
55
 
41
- **README tags** live inside README_YARD.md.
56
+ When the Readme Yard build process encounters a tag in README_YARD.md, it searches the Ruby source code for its YARD tag counterpart, formats the output, and embeds it in the README file.
42
57
 
43
- **YARD tags** live inside Ruby source code.
58
+ ### Tag Reference Table
44
59
 
45
- When the Readme Yard build process encounters a tag in README_YARD.md, it searches the Ruby source code for its YARD tag counterpart, formats the output, and embeds it in the README file.
60
+ | Tag Type | YARD Syntax (in source code) | Markdown Syntax (in README_YARD.md) | Standalone Tag* | Purpose |
61
+ |----------|------------------------------|-------------------------------------|----------------|---------|
62
+ | Readme | `@readme` | `{@readme ObjectPath}` | N/A | General purpose tag to embed content from source code |
63
+ | Readme (comment) | `@readme comment` | `{@readme ObjectPath}` | `{@comment ObjectPath}` | Embeds only the comment from source code |
64
+ | Readme (code) | `@readme code` | `{@readme ObjectPath}` | `{@code ObjectPath}` | Embeds only code implementation |
65
+ | Readme (source) | `@readme source` | `{@readme ObjectPath}` | `{@source ObjectPath}` | Embeds both comments and code |
66
+ | Readme (value) | `@readme value` | `{@readme ObjectPath}` | `{@value ObjectPath}` | Embeds a Ruby value as a Ruby code block |
67
+ | Readme (string) | `@readme string` | `{@readme ObjectPath}` | `{@string ObjectPath}` | Embeds a Ruby string as normal text |
68
+ | Example | `@example` | `{@example ObjectPath}` | N/A | Embeds example code from YARD @example tags |
69
+
70
+ > *Standalone tags allow embedding content without requiring corresponding YARD tags in source code. See [Standalone Tag Usage](#standalone-tag-usage) for details.
46
71
 
47
72
  ### Examples
48
73
 
49
- This project's [README_YARD.md](https://github.com/mattruzicka/readme_yard/blob/main/README_YARD.md) has `{@readme ReadmeYard.hello_world}` just below this paragraph, unless you're looking at the [README]((https://github.com/mattruzicka/readme_yard/blob/main/README.md)) where you should instead see a code example which was generated from running `readme build`.
74
+ The next line is a code snippet if you're looking at the [README](https://github.com/mattruzicka/readme_yard/blob/main/README.md) and `{@readme ReadmeYard::ExampleTag.hello_world}` if you're looking at [README_YARD.md](https://github.com/mattruzicka/readme_yard/blob/main/README_YARD.md).
50
75
 
51
- {@readme ReadmeYard.hello_world}
76
+ {@readme ReadmeYard::ExampleTag.hello_world}
52
77
 
53
- The README tag tells Readme Yard to parse the `@readme` YARD tag located above the `hello_world` class method located in [lib/readme_yard.rb](https://github.com/mattruzicka/readme_yard/blob/main/lib/readme_yard.rb).
78
+ The markdown tag tells Readme Yard to parse the `@readme` tag located above the `hello_world` class method located in [lib/readme_yard/example_tag.rb](https://github.com/mattruzicka/readme_yard/blob/main/lib/readme_yard/example_tag.rb).
54
79
 
55
80
  To use another "meta" example, `{@readme ReadmeYard}` is used at the top of this project's README_YARD.md file to generate the first few sentences of this README. `ReadmeYard` references the class located in [lib/readme_yard.rb](https://github.com/mattruzicka/readme_yard/blob/main/lib/readme_yard.rb).
56
81
 
57
- Last one, `{@readme ReadmeYard#command_line_usage}` is used to generate the "Command Line Usage" section above from the comments located above. `ReadmeYard#command_line_usage` references the instance method `command_line_usage` located in [lib/readme_yard.rb](https://github.com/mattruzicka/readme_yard/blob/main/lib/readme_yard.rb).
82
+ Last one, `{@readme ReadmeYard#command_line_usage}` is used to generate the "Command Line Usage" section above from the comments of the `command_line_usage` instance method located in [lib/readme_yard.rb](https://github.com/mattruzicka/readme_yard/blob/main/lib/readme_yard.rb). This method is extra meta: it returns the result of formatting its own comments as markdown. In this way, the usage instructions in the comments, the README, and as printed at the command line will always be in sync.
58
83
 
59
84
  ---
60
85
 
61
86
  ## Readme Tag
62
87
 
63
- **README Tag** syntax: `{@readme ObjectPath}`
88
+ **Markdown** syntax: `{@readme ObjectPath}`
64
89
 
65
- **YARD Tag** syntax: `@example <name>`
90
+ **YARD** syntax: `@example <name>`
66
91
 
67
92
  {@readme ReadmeYard::ReadmeTag}
68
93
 
69
- The above two sentences were generated via `{@readme ReadmeYard::ReadmeTag}` in README_YARD.md and the @readme tag at the top of [ReadmeYard::ReadmeTag class](https://github.com/mattruzicka/readme_yard/blob/main/lib/readme_yard/readme_tag.rb).
94
+ ### {@readme ReadmeYard::CommentTag}
95
+
96
+ **Usage:**
97
+
98
+ {@example ReadmeYard::CommentTag}
99
+
100
+ This example [@readme comment](https://github.com/mattruzicka/readme_yard/blob/main/lib/readme_yard/comment_tag.rb) tag embeds the below code snippet via the `{@readme ReadmeYard::CommentTag.format_tag}` markdown tag.
101
+
102
+ {@readme ReadmeYard::CommentTag.format_tag}
103
+
104
+ ### {@readme ReadmeYard::CodeTag}
105
+
106
+ **Usage:**
70
107
 
71
- ## Readme YARD Tags
108
+ {@example ReadmeYard::CodeTag}
72
109
 
110
+ This example [@readme code](https://github.com/mattruzicka/readme_yard/blob/main/lib/readme_yard/code_tag.rb) tag embeds the below code snippet via the `{@readme ReadmeYard::CodeTag.format_tag}` markdown tag.
73
111
 
74
- ### Comment Tag
112
+ {@readme ReadmeYard::CodeTag.format_tag}
75
113
 
76
- {@readme ReadmeYard::CommentTag}
114
+ ### {@readme ReadmeYard::SourceTag}
77
115
 
78
- [This @readme comment YARD tag](https://github.com/mattruzicka/readme_yard/blob/main/lib/readme_yard/comment_tag.rb) embeds the below code snippet because `{@readme ReadmeYard::CommentTag.format_tag_markdown}` is in README_YARD.md.
116
+ **Usage:**
79
117
 
80
- {@readme ReadmeYard::CommentTag.format_tag_markdown}
118
+ {@example ReadmeYard::SourceTag}
81
119
 
82
- ### Source Tag
120
+ This example [@readme source](https://github.com/mattruzicka/readme_yard/blob/main/lib/readme_yard/source_tag.rb) tag embeds the below code snippet via the `{@readme ReadmeYard::SourceTag.format_tag}` markdown tag.
83
121
 
84
- {@readme ReadmeYard::SourceTag}
122
+ {@readme ReadmeYard::SourceTag.format_tag}
85
123
 
86
- [This @readme source YARD tag](https://github.com/mattruzicka/readme_yard/blob/main/lib/readme_yard/readme_tag.rb) embeds the below code snippet because `{@readme ReadmeYard::SourceTag.format_tag_markdown}` is in README_YARD.md.
124
+ ### {@readme ReadmeYard::ValueTag}
87
125
 
88
- {@readme ReadmeYard::SourceTag.format_tag_markdown}
126
+ **Usage:**
89
127
 
128
+ {@example ReadmeYard::ValueTag}
90
129
 
91
- ### Object Tag
130
+ This example [@readme value](https://github.com/mattruzicka/readme_yard/blob/main/lib/readme_yard/value_tag.rb) tag embeds the below code snippet via the `{@value ReadmeYard::ValueTag::EXAMPLE}` markdown tag.
92
131
 
93
- {@readme ReadmeYard::ObjectTag}
132
+ {@value ReadmeYard::ValueTag::EXAMPLE}
94
133
 
95
- [This @readme object YARD tag](https://github.com/mattruzicka/readme_yard/blob/main/lib/readme_yard/readme_tag.rb) embeds the below code snippet because `{@readme ReadmeYard::ObjectTag.format_tag_markdown}` is in README_YARD.md.
134
+ ### {@readme ReadmeYard::StringTag}
96
135
 
97
- {@readme ReadmeYard::ObjectTag.format_tag_markdown}
136
+ **Usage:**
98
137
 
138
+ Because a [@readme string](https://github.com/mattruzicka/readme_yard/blob/main/lib/readme_yard/string_tag.rb) tag:
139
+
140
+ {@example ReadmeYard::StringTag::XZAMPLE}
141
+
142
+ Is located above this constant:
143
+
144
+ {@code ReadmeYard::StringTag::XZAMPLE}
145
+
146
+ We see can see its string value as simple text below:
147
+
148
+ {@string ReadmeYard::StringTag::XZAMPLE}
149
+
150
+ ---
151
+
152
+ ## Standalone Tag Usage
153
+
154
+ While using the `@readme` tag in your source code is recommended because it makes the README's dependency on source code explicit, sometimes it's useful to embed source code snippets directly without it. This is especially valuable when a source object can only contain one `@readme` tag, but you want to highlight multiple aspects of the object.
155
+
156
+ You can use any of these tags directly in README_YARD.md without requiring a corresponding `@readme` tag in the source code:
157
+
158
+ - `{@comment ObjectPath}` - Embeds comments only
159
+ - `{@code ObjectPath}` - Embeds code only
160
+ - `{@source ObjectPath}` - Embeds both comments and code
161
+ - `{@value ObjectPath}` - Embeds a Ruby value as a Ruby code block
162
+ - `{@string ObjectPath}` - Embeds a Ruby string as plain text
163
+
164
+ For example, in the StringTag section above, we used both:
165
+ - `{@code ReadmeYard::StringTag::XZAMPLE}` to show the constant definition
166
+ - `{@string ReadmeYard::StringTag::XZAMPLE}` to display the string value as text
167
+
168
+ The standalone tag usage provides more flexibility when documenting your code and doesn't require modifications to the source files.
99
169
 
100
170
  ---
101
171
 
102
172
  ## Example Tag
103
173
 
104
- **README Tag** syntax: `{@example ObjectPath}`
174
+ **Markdown** syntax: `{@example ObjectPath}`
175
+
176
+ **YARD** syntax: `@example`
105
177
 
106
- **YARD Tag** example: `@example`
178
+ {@readme ReadmeYard::ExampleTag}
107
179
 
108
- {@readme ReadmeYard.hello_world}
180
+ **Usage:**
109
181
 
110
- Given that the above comment is for the `hello_world` class method, the below example code is generated from placing `{@example ReadmeYard.hello_world}` in README_YARD.md.
182
+ {@source ReadmeYard::ExampleTag.hello_world}
111
183
 
112
- {@example ReadmeYard.hello_world}
184
+ The below example code is generated from `{@example ReadmeYard::ExampleTag.hello_world}` because, as you can see above, the "hello_world" class method has an `@example` tag.
185
+
186
+ {@example ReadmeYard::ExampleTag.hello_world}
113
187
 
114
188
  ---
115
189
 
@@ -117,6 +191,4 @@ Given that the above comment is for the `hello_world` class method, the below ex
117
191
 
118
192
  Bug reports and pull requests are welcome on GitHub at https://github.com/mattruzicka/yard-readme.
119
193
 
120
- Thanks for taking the time to think about me, the README.
121
-
122
- 🌿 🥏 🌱 ⚽
194
+ Thanks for reading me, the README that documents how to document the README with code that documents itself 🤯
data/bin/readme CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  require "bundler/setup"
5
5
  require "readme_yard"
6
- require "tty-markdown"
7
6
 
8
- arg, options = ARGV
7
+ arg, *arg_options = ARGV.dup
8
+ options = arg_options.join(" ") unless arg_options.empty?
9
9
  ReadmeYard.call(arg, options)
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ReadmeYard
4
+ #
5
+ # @readme
6
+ # Embed Ruby code
7
+ #
8
+ # @example
9
+ # # @readme code
10
+ #
11
+ class CodeTag
12
+ class << self
13
+ #
14
+ # The code for this method is in the README because
15
+ # `@readme code` is below (in the source code).
16
+ #
17
+ # @readme code
18
+ #
19
+ def format_tag(yard_object, _tag)
20
+ ExampleTag.format_ruby(yard_object.source)
21
+ end
22
+
23
+ def format_yard_object(yard_object)
24
+ format_tag(yard_object, nil)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,21 +1,30 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ReadmeYard
2
4
  #
3
5
  # @readme
4
- # ```@readme comment``` - Embeds the comment.
6
+ # Embed comments
7
+ #
8
+ # @example
9
+ # # @readme comment
5
10
  #
6
11
  class CommentTag
7
12
  class << self
8
13
  #
9
14
  # This comment is in the README because `@readme comment`
10
- # is below, in the source code.
15
+ # is below (in the source code).
11
16
  #
12
17
  # @readme comment
13
18
  #
14
- def format_tag_markdown(yard_object, _tag)
19
+ def format_tag(yard_object, _tag)
15
20
  comment = format_docstring_as_comment(yard_object)
16
21
  ExampleTag.format_ruby(comment)
17
22
  end
18
23
 
24
+ def format_yard_object(yard_object)
25
+ format_tag(yard_object, nil)
26
+ end
27
+
19
28
  #
20
29
  # @see https://rubydoc.info/gems/yard/YARD%2FDocstring:to_raw
21
30
  #
@@ -29,12 +38,12 @@ class ReadmeYard
29
38
  comment << line
30
39
  end
31
40
  last_line = yard_object.docstring.all.lines.last
32
- comment << "#" if last_line.match?(/\n$/)
41
+ comment << "#" if last_line&.match?(/\n$/)
33
42
  comment
34
43
  end
35
44
 
36
45
  def named_readme_tag_regex
37
- @named_readme_tag_regex ||= /(\n|^)@readme\s(#{YARDReadme::DocstringParser.readme_tag_names.join("|")})\n/
46
+ @named_readme_tag_regex ||= /(\n|^)@readme\s(#{ReadmeYard::TagRegistry.tag_names.join("|")})\n/
38
47
  end
39
48
  end
40
49
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ReadmeYard
2
4
  class Error < StandardError
3
5
  class << self
@@ -1,11 +1,29 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ReadmeYard
4
+ #
5
+ # @readme
6
+ # The Example Tag leverages YARD's standard `@example` tag syntax, allowing you to
7
+ # include example code in your README directly from source files. This saves time and
8
+ # ensures your README stays in sync with your YARD documentation
9
+ #
2
10
  class ExampleTag
3
11
  class << self
4
- def format_markdown(yard_object, yard_tags)
5
- yard_tags.map { |tag| format_tag_markdown(yard_object, tag) }.join("\n")
12
+ #
13
+ # @readme source
14
+ #
15
+ # @example
16
+ # ReadmeYard::ExampleTag.hello_world #=> "Hello 🌎 🌍 🌏"
17
+ #
18
+ def hello_world
19
+ "Hello 🌎 🌍 🌏"
20
+ end
21
+
22
+ def format_tags(yard_object, yard_tags)
23
+ yard_tags.map { |tag| format_tag(yard_object, tag) }.join("\n")
6
24
  end
7
25
 
8
- def format_tag_markdown(yard_object, tag)
26
+ def format_tag(yard_object, tag)
9
27
  text = tag.text.empty? ? yard_object.source : tag.text
10
28
  format_ruby(text)
11
29
  end
@@ -1,9 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ReadmeYard
2
4
  class Logger
3
5
  class << self
4
6
  def warn(msg)
7
+ msg = "Warning: #{msg}"
8
+ puts_md(msg)
9
+ end
10
+
11
+ def puts_md(msg)
5
12
  puts TTY::Markdown.parse(msg)
6
13
  end
14
+
15
+ def puts_text(msg)
16
+ puts msg
17
+ end
7
18
  end
8
19
  end
9
- end
20
+ end
@@ -1,19 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ReadmeYard
2
4
  #
3
5
  # @readme
4
- # By default, only the text nested under the @readme tag
5
- # will be embedded in the final output.
6
- #
7
- # Different embed options are provided via the following
8
- # name options: `comment`, `source`, and `object`.
6
+ # By default, only the text nested under a @readme tag
7
+ # will be embedded in the final output. The default
8
+ # embed behavior can be changed through the use of tag names.
9
9
  #
10
10
  # @see ReadmeYard::CommentTag
11
+ # @see ReadmeYard::CodeTag
11
12
  # @see ReadmeYard::SourceTag
12
- # @see ReadmeYard::ObjectTag
13
+ # @see ReadmeYard::ValueTag
14
+ # @see ReadmeYard::StringTag
13
15
  #
14
16
  class ReadmeTag
15
17
  class << self
16
- def format_markdown(yard_object, yard_tags)
18
+ def format_tags(yard_object, yard_tags)
17
19
  md = +""
18
20
  yard_tags.each do |tag|
19
21
  res = format_yard_tag(yard_object, tag)
@@ -22,7 +24,7 @@ class ReadmeYard
22
24
  md
23
25
  end
24
26
 
25
- def format_tag_markdown(_yard_object, tag)
27
+ def format_tag(_yard_object, tag)
26
28
  "#{tag.text}\n"
27
29
  end
28
30
 
@@ -30,10 +32,12 @@ class ReadmeYard
30
32
 
31
33
  def format_yard_tag(yard_object, tag)
32
34
  if tag.name && !tag.name.empty?
33
- tag_class = ReadmeYard.lookup_tag_class(tag.name)
34
- tag_class&.format_tag_markdown(yard_object, tag)
35
+ tag_class = TagRegistry.find_class(tag.name)
36
+ tag_class&.format_tag(yard_object, tag)
35
37
  elsif tag.text && !tag.text.empty?
36
- format_tag_markdown(yard_object, tag)
38
+ format_tag(yard_object, tag)
39
+ else
40
+ Logger.warn("Empty `@readme` tag found in `#{yard_object}`.")
37
41
  end
38
42
  end
39
43
  end
@@ -1,17 +1,29 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ReadmeYard
2
4
  #
3
5
  # @readme
4
- # ```@readme source``` - Embeds the source.
6
+ # Embed Ruby comments and code
7
+ #
8
+ # @example
9
+ # # @readme source
5
10
  #
6
11
  class SourceTag
7
12
  class << self
8
13
  #
9
- # The following tag embeds this method's source.
14
+ # The comment and code for ReadmeYard::SourceTag#format_tag
15
+ # is in the README because `@readme source` is below (in the source code).
10
16
  #
11
17
  # @readme source
12
18
  #
13
- def format_tag_markdown(yard_object, _tag)
14
- ExampleTag.format_ruby(yard_object.source)
19
+ def format_tag(yard_object, _tag)
20
+ text = CommentTag.format_docstring_as_comment(yard_object)
21
+ text << "\n#{yard_object.source}"
22
+ ExampleTag.format_ruby(text)
23
+ end
24
+
25
+ def format_yard_object(yard_object)
26
+ format_tag(yard_object, nil)
15
27
  end
16
28
  end
17
29
  end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ReadmeYard
4
+ #
5
+ # @readme
6
+ # Embed a Ruby string as normal text
7
+ #
8
+ class StringTag
9
+ #
10
+ # @example
11
+ # # @readme string
12
+ #
13
+ # @readme string
14
+ #
15
+ XZAMPLE = <<~STRING
16
+ I heard you like self-documenting Ruby, so I wrote
17
+ self-documenting Ruby for your self-documenting Ruby.
18
+ STRING
19
+
20
+ class << self
21
+ def format_tag(yard_object, _tag)
22
+ if yard_object.respond_to?(:value)
23
+ normalize_yard_value(yard_object.value)
24
+ else
25
+ Logger.warn("Cannot parse `@readme string`: #{yard_object.class.name} lacks `value` method.")
26
+ end
27
+ end
28
+
29
+ def format_yard_object(yard_object)
30
+ format_tag(yard_object, nil)
31
+ end
32
+
33
+ def normalize_yard_value(string_value)
34
+ return "" if string_value.nil? || string_value.empty?
35
+
36
+ string_value = string_value.dup
37
+
38
+ # Handle heredoc format
39
+ return normalize_value_for_heredoc(string_value) if string_value.strip.start_with?("<<~")
40
+
41
+ # Handle regular string format
42
+ string_value.delete_prefix!('"')
43
+ string_value.delete_suffix!('"')
44
+
45
+ # Replace different line continuation patterns
46
+ # This handles patterns like: " \\\n" and \\\n"
47
+ string_value.gsub!(/" \\\n"/, "")
48
+ string_value.gsub!(/\\\n\s*"/, "")
49
+ string_value.gsub!(/"\\\n"/, "")
50
+ string_value.gsub!(/"\\\n/, "")
51
+
52
+ # Unescape common escape sequences
53
+ string_value.gsub!(/\\n/, "\n")
54
+ string_value.gsub!(/\\t/, "\t")
55
+ string_value.gsub!(/\\r/, "\r")
56
+ string_value.gsub!(/\\"/, '"')
57
+ string_value.gsub!(/\\'/, "'")
58
+ string_value.gsub!(/\\\\/, "\\")
59
+ string_value
60
+ end
61
+
62
+ private
63
+
64
+ def normalize_value_for_heredoc(string_value)
65
+ # Extract the content between the heredoc delimiters
66
+ lines = string_value.lines
67
+ delimiter = lines.first.strip.gsub(/<<~/, "").strip
68
+ content_lines = []
69
+
70
+ in_content = false
71
+ lines.each do |line|
72
+ if in_content && line.strip == delimiter
73
+ break
74
+ elsif in_content
75
+ content_lines << line
76
+ elsif line.strip.start_with?("<<~")
77
+ in_content = true
78
+ end
79
+ end
80
+
81
+ # Return the joined content with proper indentation removed
82
+ content_lines.join.gsub(/^\s+/, "").strip
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ReadmeYard
4
+ class TagRegistry
5
+ TAGS = { "readme" => ReadmeTag,
6
+ "example" => ExampleTag,
7
+ "code" => CodeTag,
8
+ "source" => SourceTag,
9
+ "comment" => CommentTag,
10
+ "value" => ValueTag,
11
+ "string" => StringTag }.freeze
12
+
13
+ def self.find_class(tag_name)
14
+ TAGS[tag_name.downcase]
15
+ end
16
+
17
+ def self.tag_names
18
+ TAGS.keys
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ReadmeYard
4
+ #
5
+ # @readme
6
+ # Embed a Ruby value as a Ruby code block
7
+ #
8
+ # @example
9
+ # # @readme value
10
+ #
11
+ class ValueTag
12
+ #
13
+ # @readme value
14
+ #
15
+ EXAMPLE = { key: "value" }.freeze
16
+
17
+ class << self
18
+ def format_tag(yard_object, _tag)
19
+ if yard_object.respond_to?(:value)
20
+ "```ruby\n#{yard_object.value}\n```"
21
+ else
22
+ Logger.warn("Cannot parse `@readme value`: #{yard_object.class.name} lacks `value` method.")
23
+ end
24
+ end
25
+
26
+ def format_yard_object(yard_object)
27
+ format_tag(yard_object, nil)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class ReadmeYard
4
- VERSION = "0.2.0"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ReadmeYard
4
+ class YardOptsManager
5
+ class << self
6
+ YARDOPTS_FILE = ".yardopts"
7
+
8
+ def upsert_yardopts
9
+ File.exist?(YARDOPTS_FILE) ? update_yardopts_file : create_yardopts_file
10
+ end
11
+
12
+ def update_yardopts_file
13
+ text = File.read(YARDOPTS_FILE)
14
+ text_addition = build_yardopts_text_addition(text)
15
+ File.open(YARDOPTS_FILE, "a") { |f| f.write(text_addition) } if text_addition
16
+ end
17
+
18
+ def build_yardopts_text_addition(yardopts_text)
19
+ return if yardopts_text.match?(/\s*--plugin\s+readme\W/)
20
+
21
+ readme_plugin_opts = default_readme_plugin_opts(yardopts_text)
22
+ case yardopts_text
23
+ when /\s*--markup\s+markdown/, /\s*-m\s+markdown/
24
+ readme_plugin_opts
25
+ when /\s*--markup\s/, /\s*-m\s/
26
+ warn_about_supported_markdown
27
+ readme_plugin_opts
28
+ else
29
+ readme_plugin_opts << "--markup markdown\n"
30
+ end
31
+ end
32
+
33
+ def default_readme_plugin_opts(yardopts_text)
34
+ readme_opts = +""
35
+ readme_opts << "\n" unless yardopts_text.lines.last.include?("\n")
36
+ readme_opts << "--plugin readme\n"
37
+ end
38
+
39
+ def create_yardopts_file
40
+ File.write(YARDOPTS_FILE, "--plugin readme\n--markup markdown\n")
41
+ end
42
+
43
+ def warn_about_supported_markdown
44
+ Logger.warn "*Readme Yard* works best with markdown. " \
45
+ "Consider adding `--markup markdown` to your `.yardopts` file."
46
+ end
47
+ end
48
+ end
49
+ end