markdown_to_mrkdwn 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/CHANGELOG.md +38 -0
- data/LICENSE +22 -0
- data/README.md +100 -0
- data/bin/console +11 -0
- data/bin/markdown_to_mrkdwn +76 -0
- data/bin/setup +30 -0
- data/lib/markdown_to_mrkdwn/converter.rb +113 -0
- data/lib/markdown_to_mrkdwn/version.rb +5 -0
- data/lib/markdown_to_mrkdwn.rb +10 -0
- metadata +58 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 93c4327078bb8105c71a63757fa465e5b2015d71a80fd38cc1ea96f7182a353b
|
4
|
+
data.tar.gz: 58f95cd43167a151be124472dd127abcdd49ea4f6fdae8ef2e9920020c61d4a1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1af92c26cb7090bcefa93c89effb2bb78ea1cb22f5e61c4d0b0aa7791ecdd502d13d3f34191945855acf203fbc7da17fba30110ffceada014fb77c3cb67a5a7a
|
7
|
+
data.tar.gz: 78b86e0059b240908028cc52c679739f254333582628276e225503db4959600a270426a469aa42327d15086b55f408b2c92b0b62391be052288063d24b7fb2cc
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [Unreleased]
|
9
|
+
|
10
|
+
### Added
|
11
|
+
|
12
|
+
- Initial implementation
|
13
|
+
- CLI tool for converting Markdown to Slack mrkdwn
|
14
|
+
- Ruby library API for programmatic conversion
|
15
|
+
- Support for headings, bold, italic, strikethrough, code blocks, links, and images
|
16
|
+
- Command-line options for customizing conversion behavior
|
17
|
+
|
18
|
+
### Changed
|
19
|
+
|
20
|
+
### Deprecated
|
21
|
+
|
22
|
+
### Removed
|
23
|
+
|
24
|
+
### Fixed
|
25
|
+
|
26
|
+
### Security
|
27
|
+
|
28
|
+
## [0.1.0] - 2024-09-14
|
29
|
+
|
30
|
+
### Added
|
31
|
+
|
32
|
+
- Initial release of markdown_to_mrkdwn
|
33
|
+
- Basic Markdown to Slack mrkdwn conversion functionality
|
34
|
+
- Command-line interface
|
35
|
+
- Ruby library interface
|
36
|
+
|
37
|
+
[Unreleased]: https://github.com/emp823/markdown_to_mrkdwn/compare/v0.1.0...HEAD
|
38
|
+
[0.1.0]: https://github.com/emp823/markdown_to_mrkdwn/releases/tag/v0.1.0
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Erik Pearson
|
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.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# markdown_to_mrkdwn
|
2
|
+
|
3
|
+
[](https://github.com/emp823/markdown_to_mrkdwn/actions/workflows/ci.yml)
|
4
|
+
[](https://codecov.io/gh/emp823/markdown_to_mrkdwn)
|
5
|
+
[](https://www.ruby-lang.org/)
|
6
|
+
|
7
|
+
Convert Markdown to Slack mrkdwn (lightweight, no dependencies).
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Install the gem:
|
12
|
+
|
13
|
+
```bash
|
14
|
+
gem install markdown_to_mrkdwn
|
15
|
+
```
|
16
|
+
|
17
|
+
Or add to your Gemfile:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem "markdown_to_mrkdwn"
|
21
|
+
```
|
22
|
+
|
23
|
+
For development, clone and setup:
|
24
|
+
|
25
|
+
```bash
|
26
|
+
git clone https://github.com/emp823/markdown_to_mrkdwn.git
|
27
|
+
cd markdown_to_mrkdwn
|
28
|
+
bin/setup
|
29
|
+
```
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
### Ruby API
|
34
|
+
|
35
|
+
````ruby
|
36
|
+
require "markdown_to_mrkdwn"
|
37
|
+
|
38
|
+
mrkdwn = MarkdownToMrkdwn.convert(<<~MARKDOWN)
|
39
|
+
# Heading
|
40
|
+
|
41
|
+
**Bold** and *italic* and ~~strike~~ and `code`.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
puts "hello"
|
45
|
+
````
|
46
|
+
|
47
|
+

|
48
|
+
MARKDOWN
|
49
|
+
|
50
|
+
puts mrkdwn
|
51
|
+
|
52
|
+
````
|
53
|
+
|
54
|
+
### Command Line
|
55
|
+
|
56
|
+
```bash
|
57
|
+
# Convert from stdin
|
58
|
+
echo "# Title\n**bold** and _italic_." | markdown_to_mrkdwn
|
59
|
+
|
60
|
+
# Convert a file
|
61
|
+
markdown_to_mrkdwn README.md
|
62
|
+
|
63
|
+
# Use options
|
64
|
+
markdown_to_mrkdwn --plain-headings README.md
|
65
|
+
````
|
66
|
+
|
67
|
+
## What’s converted
|
68
|
+
|
69
|
+
- Headings `#`..`######` → bold line (or plain with `--plain-headings`).
|
70
|
+
- Bold `**text**` or `__text__` → `*text*`.
|
71
|
+
- Italic `*text*` or `_text_` → `_text_`.
|
72
|
+
- Strikethrough `~~text~~` → `~text~`.
|
73
|
+
- Inline code `` `code` `` → `` `code` `` (unchanged).
|
74
|
+
- Fenced code blocks `lang ... ` → `...` (language hint removed).
|
75
|
+
- Links `[label](url)` → `<url|label>`.
|
76
|
+
- Images `` → `<url|alt>`.
|
77
|
+
- Horizontal rules → em dash line (or keep with `--keep-hr`).
|
78
|
+
|
79
|
+
Notes:
|
80
|
+
|
81
|
+
- Lists, blockquotes, and paragraphs are mostly compatible with Slack already and are left as-is.
|
82
|
+
- Code (inline and blocks) is protected from other conversions.
|
83
|
+
- This is a pragmatic converter; edge cases of Markdown are not fully supported.
|
84
|
+
|
85
|
+
## Contributing
|
86
|
+
|
87
|
+
1. Fork the repository
|
88
|
+
2. Create a feature branch (`git checkout -b feature/my-new-feature`)
|
89
|
+
3. Make your changes and add tests
|
90
|
+
4. Run the test suite (`bundle exec rake test`)
|
91
|
+
5. Ensure code style passes (`bundle exec rubocop`)
|
92
|
+
6. Commit your changes (`git commit -am 'Add some feature'`)
|
93
|
+
7. Push to the branch (`git push origin feature/my-new-feature`)
|
94
|
+
8. Create a Pull Request
|
95
|
+
|
96
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.
|
97
|
+
|
98
|
+
## License
|
99
|
+
|
100
|
+
MIT - See [LICENSE](LICENSE) file for details.
|
data/bin/console
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "markdown_to_mrkdwn"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
require "irb"
|
11
|
+
IRB.start(__FILE__)
|
@@ -0,0 +1,76 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "optparse"
|
5
|
+
|
6
|
+
begin
|
7
|
+
require_relative "../lib/markdown_to_mrkdwn"
|
8
|
+
rescue LoadError
|
9
|
+
warn "Could not load markdown_to_mrkdwn library. Ensure you're running from the gem root or installed the gem."
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
|
13
|
+
options = {
|
14
|
+
heading: :bold,
|
15
|
+
keep_hr: false
|
16
|
+
}
|
17
|
+
|
18
|
+
parser = OptionParser.new do |opts|
|
19
|
+
opts.banner = "Usage: markdown_to_mrkdwn [options] [FILE]"
|
20
|
+
opts.separator ""
|
21
|
+
opts.separator "Convert Markdown to Slack mrkdwn format"
|
22
|
+
opts.separator ""
|
23
|
+
opts.separator "Options:"
|
24
|
+
|
25
|
+
opts.on("--plain-headings", "Do not bold headings") { options[:heading] = :plain }
|
26
|
+
opts.on("--keep-hr", "Keep horizontal rules as --- or ***") { options[:keep_hr] = true }
|
27
|
+
opts.on("-v", "--version", "Show version") do
|
28
|
+
puts "markdown_to_mrkdwn #{MarkdownToMrkdwn::VERSION}"
|
29
|
+
exit 0
|
30
|
+
end
|
31
|
+
opts.on("-h", "--help", "Show this help") do
|
32
|
+
puts opts
|
33
|
+
exit 0
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.separator ""
|
37
|
+
opts.separator "Examples:"
|
38
|
+
opts.separator " echo '# Title' | markdown_to_mrkdwn"
|
39
|
+
opts.separator " markdown_to_mrkdwn README.md"
|
40
|
+
opts.separator " markdown_to_mrkdwn --plain-headings file.md"
|
41
|
+
end
|
42
|
+
|
43
|
+
begin
|
44
|
+
parser.parse!
|
45
|
+
rescue OptionParser::InvalidOption => e
|
46
|
+
warn "Error: #{e.message}"
|
47
|
+
warn "Use --help for usage information"
|
48
|
+
exit 1
|
49
|
+
end
|
50
|
+
|
51
|
+
input = begin
|
52
|
+
if ARGV[0]
|
53
|
+
unless File.exist?(ARGV[0])
|
54
|
+
warn "Error: File '#{ARGV[0]}' not found"
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
unless File.readable?(ARGV[0])
|
58
|
+
warn "Error: File '#{ARGV[0]}' is not readable"
|
59
|
+
exit 1
|
60
|
+
end
|
61
|
+
File.read(ARGV[0])
|
62
|
+
else
|
63
|
+
$stdin.read
|
64
|
+
end
|
65
|
+
rescue StandardError => e
|
66
|
+
warn "Error reading input: #{e.message}"
|
67
|
+
exit 1
|
68
|
+
end
|
69
|
+
|
70
|
+
begin
|
71
|
+
result = MarkdownToMrkdwn.convert(input, **options)
|
72
|
+
puts result
|
73
|
+
rescue StandardError => e
|
74
|
+
warn "Error converting markdown: #{e.message}"
|
75
|
+
exit 1
|
76
|
+
end
|
data/bin/setup
ADDED
@@ -0,0 +1,30 @@
|
|
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("\n== Command #{args} failed ==")
|
11
|
+
end
|
12
|
+
|
13
|
+
FileUtils.chdir APP_ROOT do
|
14
|
+
# This script is a way to set up or update your development environment automatically.
|
15
|
+
# This script is idempotent, so that you can run it at anytime and get an expectable outcome.
|
16
|
+
# Add necessary setup steps to this file.
|
17
|
+
|
18
|
+
puts "== Installing dependencies =="
|
19
|
+
system! "gem install bundler --conservative"
|
20
|
+
system("bundle check") || system!("bundle install")
|
21
|
+
|
22
|
+
puts "\n== Removing old logs and tempfiles =="
|
23
|
+
FileUtils.rm_f Dir.glob("*.gem")
|
24
|
+
FileUtils.rm_rf "pkg/"
|
25
|
+
|
26
|
+
puts "\n== Running tests =="
|
27
|
+
system! "bundle exec rake test"
|
28
|
+
|
29
|
+
puts "\n== All set! =="
|
30
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MarkdownToMrkdwn
|
4
|
+
class Converter
|
5
|
+
SENTINEL = "§"
|
6
|
+
# Options:
|
7
|
+
# - heading: :bold (default) or :plain
|
8
|
+
# - keep_hr: true to keep --- as is (default false -> replace with an em dash line)
|
9
|
+
def initialize(heading: :bold, keep_hr: false)
|
10
|
+
@heading_style = heading
|
11
|
+
@keep_hr = keep_hr
|
12
|
+
end
|
13
|
+
|
14
|
+
def convert(markdown)
|
15
|
+
return "" if markdown.nil? || markdown.empty?
|
16
|
+
|
17
|
+
text = markdown.dup
|
18
|
+
|
19
|
+
# 1) Extract fenced code blocks and inline code to protect from further processing
|
20
|
+
blocks = {}
|
21
|
+
text = extract_fenced_code_blocks(text, blocks)
|
22
|
+
text = extract_inline_code(text, blocks)
|
23
|
+
|
24
|
+
# 2) Images:  -> <url|alt>
|
25
|
+
text.gsub!(/!\[(.*?)\]\((\S+?)(?:\s+".*?")?\)/) do
|
26
|
+
alt = Regexp.last_match(1)
|
27
|
+
url = Regexp.last_match(2)
|
28
|
+
"<#{url}|#{alt}>"
|
29
|
+
end
|
30
|
+
|
31
|
+
# 3) Links: [text](url) -> <url|text>
|
32
|
+
text.gsub!(/\[(.*?)\]\((\S+?)(?:\s+".*?")?\)/) do
|
33
|
+
label = Regexp.last_match(1)
|
34
|
+
url = Regexp.last_match(2)
|
35
|
+
"<#{url}|#{label}>"
|
36
|
+
end
|
37
|
+
|
38
|
+
# 4) Strikethrough: ~~text~~ -> ~text~
|
39
|
+
text.gsub!(/~~(.*?)~~/, '~\1~')
|
40
|
+
|
41
|
+
# 5) Italic: *text* or _text_ -> _text_
|
42
|
+
# Run italics BEFORE bold so that bold remains asterisks in Slack.
|
43
|
+
# Only match single markers, not double.
|
44
|
+
text.gsub!(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/, '_\1_')
|
45
|
+
text.gsub!(/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/, '_\1_')
|
46
|
+
|
47
|
+
# 6) Bold: **text** or __text__ -> *text*
|
48
|
+
text.gsub!(/\*\*(.+?)\*\*/, '*\1*')
|
49
|
+
text.gsub!(/__(.+?)__/, '*\1*')
|
50
|
+
|
51
|
+
# 7) Headings: # H -> *H*
|
52
|
+
text = convert_headings(text)
|
53
|
+
|
54
|
+
# 8) Horizontal rules
|
55
|
+
text.gsub!(/^\s*([-*_])\s*\1\s*\1\s*$/, @keep_hr ? '\0' : "—\n")
|
56
|
+
|
57
|
+
# 9) Restore protected code segments
|
58
|
+
restore_placeholders(text, blocks)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def extract_fenced_code_blocks(text, store)
|
64
|
+
idx = 0
|
65
|
+
text.gsub(/```([a-zA-Z0-9_+-]*)\n([\s\S]*?)\n```/) do
|
66
|
+
Regexp.last_match(1)
|
67
|
+
body = Regexp.last_match(2)
|
68
|
+
key = "#{SENTINEL}MRKDWNBLOCK_#{idx += 1}#{SENTINEL}"
|
69
|
+
# Slack ignores the language hint; keep pure triple backticks
|
70
|
+
store[key] = "```\n#{body}\n```"
|
71
|
+
key
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def extract_inline_code(text, store)
|
76
|
+
idx = store.size
|
77
|
+
text.gsub(/`([^`\n]+)`/) do
|
78
|
+
body = Regexp.last_match(1)
|
79
|
+
key = "#{SENTINEL}MRKDWNBLOCK_#{idx += 1}#{SENTINEL}"
|
80
|
+
store[key] = "`#{body}`"
|
81
|
+
key
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def restore_placeholders(text, store)
|
86
|
+
return text if store.empty?
|
87
|
+
|
88
|
+
loop do
|
89
|
+
before = text.dup
|
90
|
+
store.each { |k, v| text = text.gsub(k, v) }
|
91
|
+
break if text == before
|
92
|
+
end
|
93
|
+
text
|
94
|
+
end
|
95
|
+
|
96
|
+
def convert_headings(text)
|
97
|
+
text.lines.map do |line|
|
98
|
+
if (m = line.match(/^(\s{0,3})(#{Regexp.escape("#")}{1,6})\s+(.*)$/))
|
99
|
+
indent = m[1]
|
100
|
+
content = m[3].strip
|
101
|
+
case @heading_style
|
102
|
+
when :bold
|
103
|
+
"#{indent}*#{content}*\n"
|
104
|
+
else
|
105
|
+
"#{indent}#{content}\n"
|
106
|
+
end
|
107
|
+
else
|
108
|
+
line
|
109
|
+
end
|
110
|
+
end.join
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: markdown_to_mrkdwn
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Erik Pearson
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies: []
|
12
|
+
description: Lightweight converter for common Markdown to Slack mrkdwn syntax with
|
13
|
+
a simple CLI. No dependencies, fast conversion.
|
14
|
+
email:
|
15
|
+
- emp823@icloud.com
|
16
|
+
executables:
|
17
|
+
- markdown_to_mrkdwn
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- CHANGELOG.md
|
22
|
+
- LICENSE
|
23
|
+
- README.md
|
24
|
+
- bin/console
|
25
|
+
- bin/markdown_to_mrkdwn
|
26
|
+
- bin/setup
|
27
|
+
- lib/markdown_to_mrkdwn.rb
|
28
|
+
- lib/markdown_to_mrkdwn/converter.rb
|
29
|
+
- lib/markdown_to_mrkdwn/version.rb
|
30
|
+
homepage: https://github.com/emp823/markdown_to_mrkdwn
|
31
|
+
licenses:
|
32
|
+
- MIT
|
33
|
+
metadata:
|
34
|
+
bug_tracker_uri: https://github.com/emp823/markdown_to_mrkdwn/issues
|
35
|
+
changelog_uri: https://github.com/emp823/markdown_to_mrkdwn/blob/main/CHANGELOG.md
|
36
|
+
documentation_uri: https://rubydoc.info/gems/markdown_to_mrkdwn
|
37
|
+
homepage_uri: https://github.com/emp823/markdown_to_mrkdwn
|
38
|
+
source_code_uri: https://github.com/emp823/markdown_to_mrkdwn
|
39
|
+
wiki_uri: https://github.com/emp823/markdown_to_mrkdwn/wiki
|
40
|
+
rubygems_mfa_required: 'true'
|
41
|
+
rdoc_options: []
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: 3.1.0
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
requirements: []
|
55
|
+
rubygems_version: 3.6.7
|
56
|
+
specification_version: 4
|
57
|
+
summary: Convert Markdown to Slack mrkdwn
|
58
|
+
test_files: []
|