md_transformer 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +11 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +55 -0
- data/LICENSE.txt +21 -0
- data/README.md +158 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/md_transformer/markdown/section.rb +86 -0
- data/lib/md_transformer/markdown.rb +219 -0
- data/lib/md_transformer/version.rb +4 -0
- data/lib/md_transformer.rb +33 -0
- data/md_transformer.gemspec +41 -0
- metadata +136 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b604c25943a7737b8dc80a7a8846f39ddf30f0fb6a73090b57e4589684cb3f3d
|
4
|
+
data.tar.gz: 2f928958268e5521405d36ccb57edbbe68defea3c8d041dd7ab7f05f3d5bed66
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a988fd9844215362d7ceb0042257e4d5065d3e8a5bd30df22f6847d3a7deca84f6bcbea77e74df95669aee43624c13bc75d9fa19e90d37516d8340e1e8c16745
|
7
|
+
data.tar.gz: 023a3e3a12ef3d94b426f074eeebc512f5b2dd9093914155514499e102538bc584c865597e3d8fce2efb494a163aa447c7994601c78cde9bcb9befab7f56e184
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
md_transformer (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
ast (2.4.0)
|
10
|
+
diff-lcs (1.3)
|
11
|
+
jaro_winkler (1.5.2)
|
12
|
+
parallel (1.17.0)
|
13
|
+
parser (2.6.2.1)
|
14
|
+
ast (~> 2.4.0)
|
15
|
+
psych (3.1.0-x64-mingw32)
|
16
|
+
rainbow (3.0.0)
|
17
|
+
rake (10.5.0)
|
18
|
+
rspec (3.8.0)
|
19
|
+
rspec-core (~> 3.8.0)
|
20
|
+
rspec-expectations (~> 3.8.0)
|
21
|
+
rspec-mocks (~> 3.8.0)
|
22
|
+
rspec-core (3.8.0)
|
23
|
+
rspec-support (~> 3.8.0)
|
24
|
+
rspec-expectations (3.8.2)
|
25
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
26
|
+
rspec-support (~> 3.8.0)
|
27
|
+
rspec-mocks (3.8.0)
|
28
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
29
|
+
rspec-support (~> 3.8.0)
|
30
|
+
rspec-support (3.8.0)
|
31
|
+
rubocop (0.67.2)
|
32
|
+
jaro_winkler (~> 1.5.1)
|
33
|
+
parallel (~> 1.10)
|
34
|
+
parser (>= 2.5, != 2.5.1.1)
|
35
|
+
psych (>= 3.1.0)
|
36
|
+
rainbow (>= 2.2.2, < 4.0)
|
37
|
+
ruby-progressbar (~> 1.7)
|
38
|
+
unicode-display_width (>= 1.4.0, < 1.6)
|
39
|
+
ruby-progressbar (1.10.0)
|
40
|
+
unicode-display_width (1.5.0)
|
41
|
+
yard (0.9.19)
|
42
|
+
|
43
|
+
PLATFORMS
|
44
|
+
x64-mingw32
|
45
|
+
|
46
|
+
DEPENDENCIES
|
47
|
+
bundler (~> 2.0)
|
48
|
+
md_transformer!
|
49
|
+
rake (~> 10.0)
|
50
|
+
rspec (~> 3.0)
|
51
|
+
rubocop (~> 0.67)
|
52
|
+
yard (~> 0.9)
|
53
|
+
|
54
|
+
BUNDLED WITH
|
55
|
+
2.0.1
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2019 wheatevo
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
# md_transformer
|
2
|
+
|
3
|
+
The md_transformer gem provides a way to read, modify, and write Markdown much as a hash with header keys and markdown content values.
|
4
|
+
|
5
|
+
This code:
|
6
|
+
```ruby
|
7
|
+
require 'md_transformer'
|
8
|
+
|
9
|
+
# Create a new markdown object and manipulate it
|
10
|
+
md = MdTransformer.markdown("# md_transformer\nThe md_transformer gem...\n")
|
11
|
+
md['md_transformer']['Installation'] = "Add this line to your application's Gemfile"
|
12
|
+
md['md_transformer']['Usage'] = ''
|
13
|
+
md['md_transformer']['Usage']['Creating a Markdown object'] = 'The `MdTransformer`...'
|
14
|
+
md.write('my_new_readme.md')
|
15
|
+
```
|
16
|
+
|
17
|
+
Generates a Markdown file at `my_new_readme.md` with the following content:
|
18
|
+
```md
|
19
|
+
# md_transformer
|
20
|
+
The md_transformer gem...
|
21
|
+
## Installation
|
22
|
+
Add this line to your application's Gemfile
|
23
|
+
## Usage
|
24
|
+
### Creating a Markdown object
|
25
|
+
The `MdTransformer`...
|
26
|
+
```
|
27
|
+
|
28
|
+
## Installation
|
29
|
+
|
30
|
+
Add this line to your application's Gemfile:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
gem 'md_transformer'
|
34
|
+
```
|
35
|
+
|
36
|
+
And then execute:
|
37
|
+
|
38
|
+
$ bundle
|
39
|
+
|
40
|
+
Or install it yourself as:
|
41
|
+
|
42
|
+
$ gem install md_transformer
|
43
|
+
|
44
|
+
## Usage
|
45
|
+
|
46
|
+
### Creating a Markdown object
|
47
|
+
The `MdTransformer` module has several helper methods for creating a new `MdTransformer::Markdown` object.
|
48
|
+
|
49
|
+
#### Create from Content
|
50
|
+
Use the `MdTransformer.markdown` method to create a new `MdTransformer::Markdown` object from a string.
|
51
|
+
```ruby
|
52
|
+
md_object = MdTransformer.markdown("# My document\nInformation is good.\n\n## More information\n> Detailed data\n")
|
53
|
+
```
|
54
|
+
|
55
|
+
> The `md` method is also available as an alias to `markdown`
|
56
|
+
|
57
|
+
#### Create from File
|
58
|
+
Use the `MdTransformer.markdown_file` method to create a new `MdTransformer::Markdown` object from a file.
|
59
|
+
```ruby
|
60
|
+
md_object = MdTransformer.markdown_file('README.md')
|
61
|
+
md_object = MdTransformer.markdown_file('/full/path/to/README.md')
|
62
|
+
```
|
63
|
+
> The `md_file` method is also available as an alias to `markdown_file`
|
64
|
+
|
65
|
+
### Manipulating the Markdown document
|
66
|
+
The `MdTransformer::Markdown` object can be used similarly to a `Hash`. Headers are treated as keys that refer to sections of the Markdown document.
|
67
|
+
|
68
|
+
#### Markdown can be Accessed as a Hash
|
69
|
+
```ruby
|
70
|
+
# Create Markdown object from a string
|
71
|
+
md1 = MdTransformer.markdown("# Title\nContent\n## Sub-heading\nSub-content\n## Sub-heading 2\nMore content\n")
|
72
|
+
|
73
|
+
# Get all heading keys for the Title heading
|
74
|
+
md1['Title'].keys
|
75
|
+
# => ["Sub-heading", "Sub-heading 2"]
|
76
|
+
|
77
|
+
# Get the content of the Title heading
|
78
|
+
md1['Title'].content
|
79
|
+
# => "Content\n"
|
80
|
+
|
81
|
+
# Get the content of the Sub-heading
|
82
|
+
md1['Title']['Sub-heading'].content
|
83
|
+
# => "Sub-content\n"
|
84
|
+
|
85
|
+
# Dig for a key
|
86
|
+
md1.dig('Title', 'Sub-heading').content
|
87
|
+
# => "Sub-content\n"
|
88
|
+
|
89
|
+
# Output a section as a string
|
90
|
+
md1['Title'].to_s
|
91
|
+
# => "# Title\nContent\n## Sub-heading\nSub-content\n## Sub-heading 2\nMore content\n"
|
92
|
+
|
93
|
+
# Update the content of a section
|
94
|
+
md1['Title']['Sub-heading'] = "Here is new content!\n"
|
95
|
+
md1['Title']['Sub-heading'].content
|
96
|
+
# => "Here is new content!"
|
97
|
+
```
|
98
|
+
|
99
|
+
#### Markdown can be Compared
|
100
|
+
```ruby
|
101
|
+
# Create Markdown objects from a string
|
102
|
+
md1 = MdTransformer.markdown("# Title\nContent\n## Sub-heading\nSub-content\n")
|
103
|
+
md2 = MdTransformer.markdown("# Title\nContent\n## Sub-heading\nSub-content 2\n")
|
104
|
+
|
105
|
+
md1 == md2
|
106
|
+
# => false
|
107
|
+
|
108
|
+
# Set the ## Sub-heading content on md2 to match md1
|
109
|
+
md2['Title']['Sub-heading'] = 'Sub-content'
|
110
|
+
|
111
|
+
md1 == md2
|
112
|
+
# => true
|
113
|
+
```
|
114
|
+
|
115
|
+
#### Markdown can be Enumerated
|
116
|
+
```ruby
|
117
|
+
# Create Markdown object from a string
|
118
|
+
md1 = MdTransformer.markdown("# Title\nContent\n## Sub-heading\nSub-content\n## Sub-heading 2\nMore content\n")
|
119
|
+
|
120
|
+
md1['Title'].map { |k, v| "#{k}!" }
|
121
|
+
# => ["Sub-heading!", "Sub-heading 2!"]
|
122
|
+
```
|
123
|
+
|
124
|
+
### Rendering the Markdown document
|
125
|
+
|
126
|
+
#### Viewing the content of the document
|
127
|
+
Use the `to_s` method to convert the `MdTransformer::Markdown` object into valid Markdown content.
|
128
|
+
```ruby
|
129
|
+
# Show the current markdown document
|
130
|
+
puts md_object
|
131
|
+
|
132
|
+
# Assign the markdown document content to a string
|
133
|
+
string_content = md_object.to_s
|
134
|
+
```
|
135
|
+
|
136
|
+
#### Writing the document to a file
|
137
|
+
Use the `write` method to write the current `MdTransformer::Markdown` object's content to a file.
|
138
|
+
```ruby
|
139
|
+
md_object.write('test.md')
|
140
|
+
md_object.write('/my/long/directory/README.md')
|
141
|
+
```
|
142
|
+
|
143
|
+
### Need More Help?
|
144
|
+
Please take a look at the [API documentation](https://www.rubydoc.info/github/wheatevo/md_transformer/master).
|
145
|
+
|
146
|
+
## Development
|
147
|
+
|
148
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
149
|
+
|
150
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
151
|
+
|
152
|
+
## Contributing
|
153
|
+
|
154
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/wheatevo/md_transformer.
|
155
|
+
|
156
|
+
## License
|
157
|
+
|
158
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'md_transformer'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
module MdTransformer
|
2
|
+
class Markdown
|
3
|
+
# Class representing a section of a markdown file (header with content)
|
4
|
+
class Section
|
5
|
+
# @return [String] the title of the section
|
6
|
+
attr_reader :title
|
7
|
+
|
8
|
+
# @return [Integer] the precedence level of the section
|
9
|
+
attr_reader :level
|
10
|
+
|
11
|
+
# @return [Range] the section header's location in the original content
|
12
|
+
attr_reader :header_location
|
13
|
+
|
14
|
+
# @return [String] the section's content
|
15
|
+
attr_reader :content
|
16
|
+
|
17
|
+
# Creates the Section object
|
18
|
+
# @param title [String] the title of the section
|
19
|
+
# @param options [Hash] the options hash
|
20
|
+
# @option options [Integer] :level (0) the precedence level of the section
|
21
|
+
# @option options [Range] :header_location (0..0) the section header's location in the original content
|
22
|
+
# @option options [String] :content ('') the content of the section
|
23
|
+
# @return [MdTransformer::Markdown::Section] the new Section object
|
24
|
+
def initialize(title, options = {})
|
25
|
+
@title = title
|
26
|
+
@level = options[:level] || 0
|
27
|
+
@header_location = options[:header_location].nil? ? 0..0 : options[:header_location]
|
28
|
+
@content = options[:content] || ''
|
29
|
+
end
|
30
|
+
|
31
|
+
# Generates an array of Section objects from string Markdown content
|
32
|
+
# @param content [String] the markdown content to parse
|
33
|
+
# @return [Array] the array of generated Section objects
|
34
|
+
def self.generate_sections(content)
|
35
|
+
headers = header_locations(content)
|
36
|
+
headers.each_with_index do |h, i|
|
37
|
+
content_end = content.length
|
38
|
+
headers[i] = { header: h, content: ((h.end + 1)..(content_end - 1)) }
|
39
|
+
end
|
40
|
+
headers.map! { |h| create_section_from_header(h, content) }
|
41
|
+
end
|
42
|
+
|
43
|
+
class << self
|
44
|
+
private
|
45
|
+
|
46
|
+
# Gathers header locations for given content
|
47
|
+
# @param content [String] the markdown content to parse
|
48
|
+
# @return [Array] the array of all header locations given as ranges
|
49
|
+
def header_locations(content)
|
50
|
+
code_ranges = code_locations(content)
|
51
|
+
hdr_regex = /^(\#{1,6}\s+.*)$/
|
52
|
+
hdr_ranges = content.enum_for(:scan, hdr_regex).map { Regexp.last_match.begin(0)..Regexp.last_match.end(0) }
|
53
|
+
hdr_ranges.reject { |hdr_range| code_ranges.any? { |code_range| code_range.include?(hdr_range.begin) } }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Gathers code block locations for given content
|
57
|
+
# @param content [String] the markdown content to parse
|
58
|
+
# @return [Array] the array of all code block locations given as ranges
|
59
|
+
def code_locations(content)
|
60
|
+
block_regex = /^([`~]{3}.*?^[`~]{3})$/m
|
61
|
+
content.enum_for(:scan, block_regex).map { Regexp.last_match.begin(0)..Regexp.last_match.end(0) }
|
62
|
+
end
|
63
|
+
|
64
|
+
# Creates a new Section object from a header hash
|
65
|
+
# @param header [Hash] hash containing :header and :content keys representing header and content locations
|
66
|
+
# @param content [String] the markdown content to parse
|
67
|
+
# @return [MdTransformer::Markdown::Section] the new Section object
|
68
|
+
def create_section_from_header(header, content)
|
69
|
+
Section.new(
|
70
|
+
content[header[:header]].match(/^#+\s+(.*)$/)[1],
|
71
|
+
level: header_level(content[header[:header]]),
|
72
|
+
header_location: header[:header].begin..header[:content].end,
|
73
|
+
content: content[header[:content]]
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Determines the precedence level of a header
|
78
|
+
# @param header [String] the header string to parse
|
79
|
+
# @return [Integer] the calculated precedence level
|
80
|
+
def header_level(header)
|
81
|
+
header.split.first.count('#')
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'md_transformer/markdown/section'
|
3
|
+
|
4
|
+
module MdTransformer
|
5
|
+
# Class representing a parsed markdown file
|
6
|
+
class Markdown
|
7
|
+
include Comparable
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
# @return [String] the title of the Markdown object
|
11
|
+
attr_accessor :title
|
12
|
+
|
13
|
+
# @return [String] the content of the Markdown object
|
14
|
+
attr_reader :content
|
15
|
+
|
16
|
+
# @return [Array] the array of child objects
|
17
|
+
attr_reader :children
|
18
|
+
|
19
|
+
# @return [MdTransformer::Markdown, nil] nil or the parent of the current Markdown object
|
20
|
+
attr_reader :parent
|
21
|
+
|
22
|
+
# The lowest valid precedence of a header, allows for up to H6 (###### Header)
|
23
|
+
LOWEST_PRECEDENCE = 6
|
24
|
+
|
25
|
+
# Creates a new Markdown object
|
26
|
+
# @param source [String] the markdown content or path to a markdown file
|
27
|
+
# @param options [Hash] the options hash
|
28
|
+
# @option options [Boolean] :file whether to treat the passed source as a file path
|
29
|
+
# @option options [MdTransformer::Markdown] :parent the parent of the new object
|
30
|
+
# @option options [String] :title the title of the new object
|
31
|
+
# @return [MdTransformer::Markdown] the new Markdown object
|
32
|
+
def initialize(source = '', options = {})
|
33
|
+
@parent = options[:parent]
|
34
|
+
@title = options[:title] || ''
|
35
|
+
if options[:file]
|
36
|
+
raise InvalidMarkdownPath, "Could not find markdown file at #{source}" unless File.exist?(source)
|
37
|
+
|
38
|
+
source = File.read(source)
|
39
|
+
@title ||= options[:file]
|
40
|
+
end
|
41
|
+
parse!(source)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Updates the current object's Markdown content
|
45
|
+
# @param value [String] the new content
|
46
|
+
# @return [String] the newly added content
|
47
|
+
def content=(value)
|
48
|
+
# Reflow the content headers based on the child's levels (raise exception if level exceeds 6)
|
49
|
+
m = Markdown.new(value.to_s)
|
50
|
+
|
51
|
+
# Reassign the parent of the children to the current object and ensure the new depth is valid
|
52
|
+
@children = m.children
|
53
|
+
@children.each do |c|
|
54
|
+
c.instance_variable_set(:@parent, self)
|
55
|
+
validate_levels(c)
|
56
|
+
end
|
57
|
+
|
58
|
+
@content = m.content
|
59
|
+
end
|
60
|
+
|
61
|
+
# Gets all child object keys
|
62
|
+
# @return [Array] the array of child keys
|
63
|
+
def keys
|
64
|
+
@children.map(&:title)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Checks for whether a child object has a given key
|
68
|
+
# @param key [String] the key of the child object
|
69
|
+
# @return [Boolean] whether the passed key exists
|
70
|
+
def key?(key)
|
71
|
+
!self[key].nil?
|
72
|
+
end
|
73
|
+
|
74
|
+
# Gets all child object values
|
75
|
+
# @return [Array] the array of child objects
|
76
|
+
def values
|
77
|
+
@children
|
78
|
+
end
|
79
|
+
|
80
|
+
# Checks for whether a child object has a given value
|
81
|
+
# @param value [String] the value to check for
|
82
|
+
# @return [Boolean] whether the passed value exists
|
83
|
+
def value?(value)
|
84
|
+
!@children.find { |c| c == value }.nil?
|
85
|
+
end
|
86
|
+
|
87
|
+
# Digs through the hash for the child object at the given nested key(s)
|
88
|
+
# @param key [String] the first key to check
|
89
|
+
# @param rest [Array<String>] any number of nested string keys for which to find
|
90
|
+
# @return [MdTransformer::Markdown, nil] the found child object or nil
|
91
|
+
def dig(key, *rest)
|
92
|
+
value = self[key]
|
93
|
+
return value if value.nil? || rest.empty?
|
94
|
+
|
95
|
+
value.dig(*rest)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Retrieves the child object at a given key if it exists
|
99
|
+
# @param key [String] the key of the child object
|
100
|
+
# @return [MdTransformer::Markdown, nil] the found child object or nil
|
101
|
+
def [](key)
|
102
|
+
@children.find { |c| c.title == key }
|
103
|
+
end
|
104
|
+
|
105
|
+
# Sets the value of the child object at a given key. If the key does not exist, a new child object is created.
|
106
|
+
# @param key [String] the key of the child object
|
107
|
+
# @param value [String] the new value of the child object
|
108
|
+
# @return [String] the newly assigned value
|
109
|
+
def []=(key, value)
|
110
|
+
child = self[key] || Markdown.new('', title: key, parent: self)
|
111
|
+
child.content = value.to_s
|
112
|
+
@children.push(child) unless key?(key)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Creates a string representing the markdown document's content from current content and all child content
|
116
|
+
# @param options [Hash] the options hash
|
117
|
+
# @option options [Boolean] :title (true) whether to include the title of the current object in the output
|
118
|
+
# @return [String] the constructed Markdown string
|
119
|
+
def to_s(options = { title: true })
|
120
|
+
title_str = root? ? '' : "#{'#' * level} #{@title}\n"
|
121
|
+
md_string = "#{options[:title] ? title_str : ''}#{@content}#{@children.map(&:to_s).join}"
|
122
|
+
md_string << "\n" unless md_string.end_with?("\n")
|
123
|
+
md_string
|
124
|
+
end
|
125
|
+
|
126
|
+
# Writes the current markdown object to a file
|
127
|
+
# @param path [String] the path to the new file
|
128
|
+
# @param options [Hash] the options hash
|
129
|
+
# @option options [Boolean] :create_dir (true) whether to create the parent directories of the path
|
130
|
+
# @return [Integer] the length of the newly created file
|
131
|
+
def write(path, options: { create_dir: true })
|
132
|
+
FileUtils.mkdir_p(File.dirname(path)) if options[:create_dir]
|
133
|
+
File.write(path, to_s)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Checks whether the current object is the root of the Markdown object
|
137
|
+
# @return [Boolean] whether the current object is the root
|
138
|
+
def root?
|
139
|
+
@parent.nil?
|
140
|
+
end
|
141
|
+
|
142
|
+
# Calculates the current nesting level of the Markdown object
|
143
|
+
# @return [Integer] the nesting level of the object (0 for the root, +1 for each additional level)
|
144
|
+
def level
|
145
|
+
return 0 if root?
|
146
|
+
|
147
|
+
@parent.level + 1
|
148
|
+
end
|
149
|
+
|
150
|
+
# For a block { |k, v| ... }
|
151
|
+
# @yield [k, v] Gives the key and value of the object
|
152
|
+
def each
|
153
|
+
return enum_for(__method__) unless block_given?
|
154
|
+
|
155
|
+
@children.each do |child|
|
156
|
+
yield child.title, child
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Compares Markdown objects with other objects through string conversion
|
161
|
+
# @param other [Object] object to compare
|
162
|
+
# @return [Integer] the result of the <=> operator on the string values of both objects
|
163
|
+
def <=>(other)
|
164
|
+
to_s <=> other.to_s
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
# Validates that all children have valid precedence levels and raises an exception if they are not
|
170
|
+
# @param child [MdTransformer::Markdown] child object ot validate
|
171
|
+
# @return [MdTransformer::Markdown] the validated child object
|
172
|
+
def validate_levels(child)
|
173
|
+
if child.level >= LOWEST_PRECEDENCE
|
174
|
+
raise HeaderTooDeep, "#{child.title} header level (h#{child.level}) is beyond h#{LOWEST_PRECEDENCE}"
|
175
|
+
end
|
176
|
+
|
177
|
+
child.children.each { |c| validate_levels(c) }
|
178
|
+
child
|
179
|
+
end
|
180
|
+
|
181
|
+
# Parses the provided markdown string content into a the current object's content and children
|
182
|
+
# @param content [String] the string Markdown content to parse
|
183
|
+
def parse!(content)
|
184
|
+
@children = []
|
185
|
+
@content = ''
|
186
|
+
|
187
|
+
# Parse all direct children and create new markdown objects
|
188
|
+
sections = Section.generate_sections(content)
|
189
|
+
|
190
|
+
# No children!
|
191
|
+
if sections.empty?
|
192
|
+
@content = content
|
193
|
+
return
|
194
|
+
end
|
195
|
+
|
196
|
+
# Populate content prior to found headers
|
197
|
+
@content = content[0..sections.first.header_location.begin - 1] if sections.first.header_location.begin > 0
|
198
|
+
|
199
|
+
parse_children!(sections)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Parses all available sections into direct children of the current object
|
203
|
+
# @param sections [Array] the array of Markdown::Section objects to parse
|
204
|
+
def parse_children!(sections)
|
205
|
+
# Go through the headers sequentially to find all direct children (base on header level vs. current level)
|
206
|
+
last_child_level = LOWEST_PRECEDENCE + 1
|
207
|
+
|
208
|
+
sections.each do |s|
|
209
|
+
# Finish parsing if we encounter a sibling (same level) or aunt/uncle (higher level)
|
210
|
+
break if s.level <= level
|
211
|
+
|
212
|
+
if s.level <= last_child_level
|
213
|
+
@children.push(Markdown.new(s.content, title: s.title, parent: self))
|
214
|
+
last_child_level = s.level
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'md_transformer/version'
|
2
|
+
require 'md_transformer/markdown'
|
3
|
+
|
4
|
+
# Module containing classes relating to parsing and using Markdown content similarly to a hash
|
5
|
+
module MdTransformer
|
6
|
+
# Base error class for MdTransformer
|
7
|
+
class Error < ::StandardError; end
|
8
|
+
|
9
|
+
# Error indicating that a passed markdown path cannot be found or read
|
10
|
+
class InvalidMarkdownPath < MdTransformer::Error; end
|
11
|
+
|
12
|
+
# Error indicating that a parsed header will exceed the maximum level (over h6)
|
13
|
+
class HeaderTooDeep < MdTransformer::Error; end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
# Creates a new Markdown object from given content
|
17
|
+
# @param content [String] the markdown content
|
18
|
+
# @return [MdTransformer::Markdown] the new Markdown object
|
19
|
+
def markdown(content)
|
20
|
+
Markdown.new(content)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Creates a new Markdown object from a file
|
24
|
+
# @param path [String] path to the markdown file to open
|
25
|
+
# @return [MdTransformer::Markdown] the new Markdown object
|
26
|
+
def markdown_file(path)
|
27
|
+
Markdown.new(path, file: true)
|
28
|
+
end
|
29
|
+
|
30
|
+
alias md markdown
|
31
|
+
alias md_file markdown_file
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'md_transformer/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'md_transformer'
|
7
|
+
spec.version = MdTransformer::VERSION
|
8
|
+
spec.authors = ['wheatevo']
|
9
|
+
spec.email = ['matthewtnewell@gmail.com']
|
10
|
+
|
11
|
+
spec.summary = 'Gem for transforming markdown files into a nested hash for easy editing and output.'
|
12
|
+
spec.description = 'Gem for transforming markdown files into a nested hash for easy editing and output.'
|
13
|
+
spec.homepage = 'https://github.com/wheatevo/md_transformer'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
17
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
18
|
+
if spec.respond_to?(:metadata)
|
19
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
20
|
+
spec.metadata['source_code_uri'] = 'https://github.com/wheatevo/md_transformer'
|
21
|
+
spec.metadata['changelog_uri'] = 'https://github.com/wheatevo/md_transformer/blob/master/CHANGELOG.md'
|
22
|
+
else
|
23
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
24
|
+
'public gem pushes.'
|
25
|
+
end
|
26
|
+
|
27
|
+
# Specify which files should be added to the gem when it is released.
|
28
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
29
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
30
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
31
|
+
end
|
32
|
+
spec.bindir = 'exe'
|
33
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
34
|
+
spec.require_paths = ['lib']
|
35
|
+
|
36
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
37
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
38
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
39
|
+
spec.add_development_dependency 'rubocop', '~> 0.67'
|
40
|
+
spec.add_development_dependency 'yard', '~> 0.9'
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: md_transformer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- wheatevo
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-04-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.67'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.67'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: yard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.9'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.9'
|
83
|
+
description: Gem for transforming markdown files into a nested hash for easy editing
|
84
|
+
and output.
|
85
|
+
email:
|
86
|
+
- matthewtnewell@gmail.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- ".rspec"
|
93
|
+
- ".rubocop.yml"
|
94
|
+
- ".travis.yml"
|
95
|
+
- CHANGELOG.md
|
96
|
+
- Gemfile
|
97
|
+
- Gemfile.lock
|
98
|
+
- LICENSE.txt
|
99
|
+
- README.md
|
100
|
+
- Rakefile
|
101
|
+
- bin/console
|
102
|
+
- bin/setup
|
103
|
+
- lib/md_transformer.rb
|
104
|
+
- lib/md_transformer/markdown.rb
|
105
|
+
- lib/md_transformer/markdown/section.rb
|
106
|
+
- lib/md_transformer/version.rb
|
107
|
+
- md_transformer.gemspec
|
108
|
+
homepage: https://github.com/wheatevo/md_transformer
|
109
|
+
licenses:
|
110
|
+
- MIT
|
111
|
+
metadata:
|
112
|
+
homepage_uri: https://github.com/wheatevo/md_transformer
|
113
|
+
source_code_uri: https://github.com/wheatevo/md_transformer
|
114
|
+
changelog_uri: https://github.com/wheatevo/md_transformer/blob/master/CHANGELOG.md
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options: []
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
requirements: []
|
130
|
+
rubyforge_project:
|
131
|
+
rubygems_version: 2.7.6
|
132
|
+
signing_key:
|
133
|
+
specification_version: 4
|
134
|
+
summary: Gem for transforming markdown files into a nested hash for easy editing and
|
135
|
+
output.
|
136
|
+
test_files: []
|