mdx-tex 0.1.13 → 0.2.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 +4 -4
- data/lib/mdx_tex/core_ext/string.rb +4 -0
- data/lib/mdx_tex/to_markdown/bold.rb +35 -0
- data/lib/mdx_tex/to_markdown/header.rb +35 -0
- data/lib/mdx_tex/to_markdown/ordered_list.rb +41 -0
- data/lib/mdx_tex/to_markdown/unordered_list.rb +41 -0
- data/lib/mdx_tex/to_markdown.rb +85 -0
- data/lib/mdx_tex/version.rb +1 -1
- data/lib/mdx_tex.rb +5 -0
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c30f6b611b9feb5094eb58bff7920f23bdac921d2eb8d4bf82a5700516672d43
|
|
4
|
+
data.tar.gz: c571088c6cae7523ce3ece74dc648f10b08ba74c9b53fafa262c78c37e981a01
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 45806c1388c49103d659f12ad50f144081477f479db973a9ad2900dbfb7eacb1e569632fbdf8adf7958dd1de331ba40ed2bc71ffdc2abca5f5f759eebace4584
|
|
7
|
+
data.tar.gz: 737d4a0f4c3a07ded53396994ed78d38ee7acdfb40ea4900fb182c6628e5bff957892e8d259d040b9eb28b74e231d2204a42326490e28ddf9c1ee8fec57b816a
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MdxTex
|
|
4
|
+
class ToMarkdown
|
|
5
|
+
# Converts Textile bold syntax to Markdown bold syntax.
|
|
6
|
+
# Textile bold is *text* with non-whitespace immediately inside both asterisks;
|
|
7
|
+
# whitespace-padded asterisks are not bold (and are typically list markers).
|
|
8
|
+
#
|
|
9
|
+
# | Input (Textile) | Output (Markdown) |
|
|
10
|
+
# |-------------------|-------------------|
|
|
11
|
+
# | *hello* | **hello** |
|
|
12
|
+
# | *a* and *b* | **a** and **b** |
|
|
13
|
+
# | * hello * | * hello * |
|
|
14
|
+
module Bold
|
|
15
|
+
# Matches a Textile bold span: *...*
|
|
16
|
+
# \* opening literal *
|
|
17
|
+
# ( capture the content
|
|
18
|
+
# [^\s*] first char must be non-whitespace and non-*
|
|
19
|
+
# (Textile rule: no padding inside the asterisks;
|
|
20
|
+
# also keeps us from matching list markers like `* item`)
|
|
21
|
+
# (?: optional trailing run, present only for 2+ char content
|
|
22
|
+
# [^*]*? any chars except *, non-greedy so we stop at the
|
|
23
|
+
# nearest closing * rather than spanning into another span
|
|
24
|
+
# [^\s*] last char must also be non-whitespace and non-*
|
|
25
|
+
# )? optional, so single-char bold like *a* still matches
|
|
26
|
+
# )
|
|
27
|
+
# \* closing literal *
|
|
28
|
+
PATTERN = /\*([^\s*](?:[^*]*?[^\s*])?)\*/.freeze
|
|
29
|
+
|
|
30
|
+
def self.execute(line)
|
|
31
|
+
line.gsub(PATTERN, '**\1**')
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MdxTex
|
|
4
|
+
class ToMarkdown
|
|
5
|
+
# Converts a Textile heading to a Markdown heading.
|
|
6
|
+
# The Textile tag's level determines the number of leading #s.
|
|
7
|
+
# A space must follow the period for the line to be recognised as a heading.
|
|
8
|
+
# Lines that do not match are returned unchanged.
|
|
9
|
+
#
|
|
10
|
+
# | Input (Textile) | Output (Markdown) |
|
|
11
|
+
# |--------------------|--------------------|
|
|
12
|
+
# | h1. Title | # Title |
|
|
13
|
+
# | h3. Note | ### Note |
|
|
14
|
+
# | h6. Tiny | ###### Tiny |
|
|
15
|
+
# | h3.NoSpace | h3.NoSpace |
|
|
16
|
+
# | h7. TooDeep | h7. TooDeep |
|
|
17
|
+
module Header
|
|
18
|
+
# Matches a Textile heading line: hN. content
|
|
19
|
+
# \A anchor to start of line (no leading whitespace allowed)
|
|
20
|
+
# h literal 'h'
|
|
21
|
+
# ([1-6]) capture the heading level digit, restricted to 1-6
|
|
22
|
+
# (Textile/HTML only have h1..h6)
|
|
23
|
+
# \. literal period
|
|
24
|
+
# \s+ one or more whitespace chars
|
|
25
|
+
# (required: `h3.NoSpace` is not a heading)
|
|
26
|
+
# (.+) capture the heading content (at least one char)
|
|
27
|
+
# \z anchor to end of line
|
|
28
|
+
PATTERN = /\Ah([1-6])\.\s+(.+)\z/.freeze
|
|
29
|
+
|
|
30
|
+
def self.execute(line)
|
|
31
|
+
line.sub(PATTERN) { "#{'#' * ::Regexp.last_match(1).to_i} #{::Regexp.last_match(2)}" }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MdxTex
|
|
4
|
+
class ToMarkdown
|
|
5
|
+
# Converts a Textile ordered list item to a Markdown ordered list item.
|
|
6
|
+
# Depth is derived from the leading-hash count minus +base_list_depth+
|
|
7
|
+
# (the smallest hash count seen anywhere in the document, detected by the
|
|
8
|
+
# coordinator). Markdown indents 2 spaces per depth level. The +number+
|
|
9
|
+
# is supplied by the coordinator, which maintains per-depth counters.
|
|
10
|
+
#
|
|
11
|
+
# | Input (Textile) | base_list_depth | number | Output (Markdown) |
|
|
12
|
+
# |-----------------|-----------------|--------|-------------------|
|
|
13
|
+
# | # item | 1 | 1 | 1. item |
|
|
14
|
+
# | # item | 1 | 5 | 5. item |
|
|
15
|
+
# | ## nested | 1 | 1 | 1. nested |
|
|
16
|
+
# | ## item | 2 | 1 | 1. item |
|
|
17
|
+
module OrderedList
|
|
18
|
+
INDENT_SIZE = 2
|
|
19
|
+
|
|
20
|
+
# Matches a Textile ordered list line: optional leading whitespace, a run
|
|
21
|
+
# of hashes, a mandatory space, and at least one content character.
|
|
22
|
+
# \A start of line
|
|
23
|
+
# \s* tolerate leading whitespace (not preserved in the output)
|
|
24
|
+
# (#+) capture the run of hashes (depth indicator)
|
|
25
|
+
# \s+ one or more whitespace chars
|
|
26
|
+
# (required: distinguishes a list marker from things like
|
|
27
|
+
# `#foo` which is not a Textile ordered list item)
|
|
28
|
+
# (.+) capture the item content
|
|
29
|
+
# \z end of line
|
|
30
|
+
PATTERN = /\A\s*(#+)\s+(.+)\z/.freeze
|
|
31
|
+
|
|
32
|
+
def self.execute(line, base_list_depth:, number:)
|
|
33
|
+
line.sub(PATTERN) do
|
|
34
|
+
depth = ::Regexp.last_match(1).length - base_list_depth + 1
|
|
35
|
+
indent = ' ' * ((depth - 1) * INDENT_SIZE)
|
|
36
|
+
"#{indent}#{number}. #{::Regexp.last_match(2)}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MdxTex
|
|
4
|
+
class ToMarkdown
|
|
5
|
+
# Converts a Textile unordered list item to a Markdown unordered list item.
|
|
6
|
+
# Depth is derived from the leading-asterisk count minus +base_list_depth+
|
|
7
|
+
# (the smallest asterisk count seen anywhere in the document, detected by
|
|
8
|
+
# the coordinator). Markdown indents 2 spaces per depth level.
|
|
9
|
+
#
|
|
10
|
+
# | Input (Textile) | base_list_depth | Output (Markdown) |
|
|
11
|
+
# |-----------------|-----------------|-------------------|
|
|
12
|
+
# | *** item | 3 | - item |
|
|
13
|
+
# | **** nested | 3 | - nested |
|
|
14
|
+
# | ***** deep | 3 | - deep |
|
|
15
|
+
# | * item | 1 | - item |
|
|
16
|
+
# | ** nested | 1 | - nested |
|
|
17
|
+
module UnorderedList
|
|
18
|
+
INDENT_SIZE = 2
|
|
19
|
+
|
|
20
|
+
# Matches a Textile unordered list line: optional leading whitespace, a run
|
|
21
|
+
# of asterisks, a mandatory space, and at least one content character.
|
|
22
|
+
# \A start of line
|
|
23
|
+
# \s* tolerate leading whitespace (not preserved in the output)
|
|
24
|
+
# (\*+) capture the run of asterisks (depth indicator)
|
|
25
|
+
# \s+ one or more whitespace chars
|
|
26
|
+
# (required: this is what distinguishes a list marker from
|
|
27
|
+
# inline bold like `*foo*` or a bare `*`)
|
|
28
|
+
# (.+) capture the item content
|
|
29
|
+
# \z end of line
|
|
30
|
+
PATTERN = /\A\s*(\*+)\s+(.+)\z/.freeze
|
|
31
|
+
|
|
32
|
+
def self.execute(line, base_list_depth:)
|
|
33
|
+
line.sub(PATTERN) do
|
|
34
|
+
depth = ::Regexp.last_match(1).length - base_list_depth + 1
|
|
35
|
+
indent = ' ' * ((depth - 1) * INDENT_SIZE)
|
|
36
|
+
"#{indent}- #{::Regexp.last_match(2)}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mdx_tex/to_markdown/bold'
|
|
4
|
+
require 'mdx_tex/to_markdown/header'
|
|
5
|
+
require 'mdx_tex/to_markdown/unordered_list'
|
|
6
|
+
require 'mdx_tex/to_markdown/ordered_list'
|
|
7
|
+
|
|
8
|
+
module MdxTex
|
|
9
|
+
# Converts Textile to Markdown.
|
|
10
|
+
#
|
|
11
|
+
# The base list depth is auto-detected per document (smallest asterisks/hashes
|
|
12
|
+
# count across all list lines) so that input with inconsistent base
|
|
13
|
+
# indentation still produces a clean depth-1 Markdown list. Ordered list
|
|
14
|
+
# items are numbered with incrementing per-depth counters that reset on
|
|
15
|
+
# blank lines or any non-ordered-list line.
|
|
16
|
+
class ToMarkdown
|
|
17
|
+
def execute(input)
|
|
18
|
+
return nil if input.nil?
|
|
19
|
+
|
|
20
|
+
lines = input.to_s.split("\n", -1)
|
|
21
|
+
@unordered_base, @ordered_base = detect_bases(lines)
|
|
22
|
+
@counters = {}
|
|
23
|
+
|
|
24
|
+
lines.map { |line| convert_line(line) }.join("\n")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
# Single pass over the document:
|
|
30
|
+
# - every line is matched against the list patterns once,
|
|
31
|
+
# and the smallest marker run seen for each kind becomes the depth-1 base.
|
|
32
|
+
# - Falls back to 1 when no list of that kind is present
|
|
33
|
+
# (the value is irrelevant in that case — no line will match for conversion).
|
|
34
|
+
def detect_bases(lines)
|
|
35
|
+
unordered_counts = []
|
|
36
|
+
ordered_counts = []
|
|
37
|
+
lines.each do |line|
|
|
38
|
+
if (m = line.match(UnorderedList::PATTERN))
|
|
39
|
+
unordered_counts << m[1].length
|
|
40
|
+
elsif (m = line.match(OrderedList::PATTERN))
|
|
41
|
+
ordered_counts << m[1].length
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
[unordered_counts.min || 1, ordered_counts.min || 1]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def convert_line(line)
|
|
48
|
+
# List conversion must run before Bold (and before Header for symmetry):
|
|
49
|
+
# - Textile uses `*` for both unordered list markers and inline bold.
|
|
50
|
+
# A line like `*** *foo*` is a depth-3 list item containing the bold word "foo".
|
|
51
|
+
# - If Bold ran first, the leading `*` characters would be eaten by its
|
|
52
|
+
# regex and the list structure would be lost.
|
|
53
|
+
# Converting lists first rewrites the leading markers to Markdown `-`/`1.`,
|
|
54
|
+
# leaving only the inline `*foo*` for Bold to handle on the next pass.
|
|
55
|
+
line = convert_ordered_and_unordered_list(line)
|
|
56
|
+
line = Header.execute(line)
|
|
57
|
+
Bold.execute(line)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def convert_ordered_and_unordered_list(line)
|
|
61
|
+
match = line.match(OrderedList::PATTERN)
|
|
62
|
+
if match
|
|
63
|
+
# Ordered-list line: bump the counter at this depth
|
|
64
|
+
# (which also drops any deeper counters so they restart fresh next time we descend).
|
|
65
|
+
depth = match[1].length - @ordered_base + 1
|
|
66
|
+
number = bump_counter(depth)
|
|
67
|
+
OrderedList.execute(line, base_list_depth: @ordered_base, number: number)
|
|
68
|
+
else
|
|
69
|
+
# Anything else (blank line, unordered item, header, paragraph)
|
|
70
|
+
# ends the current run of ordered items, so all ordered counters reset.
|
|
71
|
+
# The next `# foo` encountered will start over at 1.
|
|
72
|
+
@counters.clear
|
|
73
|
+
UnorderedList.execute(line, base_list_depth: @unordered_base)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Increment the counter at +depth+ and drop any deeper-depth counters.
|
|
78
|
+
# So the next time we descend to those depths they start fresh at 1.
|
|
79
|
+
def bump_counter(depth)
|
|
80
|
+
@counters[depth] = (@counters[depth] || 0) + 1
|
|
81
|
+
@counters.delete_if { |d, _| d > depth }
|
|
82
|
+
@counters[depth]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
data/lib/mdx_tex/version.rb
CHANGED
data/lib/mdx_tex.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'mdx_tex/version'
|
|
4
4
|
require 'mdx_tex/configuration'
|
|
5
5
|
require 'mdx_tex/to_textile'
|
|
6
|
+
require 'mdx_tex/to_markdown'
|
|
6
7
|
|
|
7
8
|
module MdxTex
|
|
8
9
|
class << self
|
|
@@ -21,6 +22,10 @@ module MdxTex
|
|
|
21
22
|
MdxTex::ToTextile.new(**merged).execute(markdown)
|
|
22
23
|
end
|
|
23
24
|
|
|
25
|
+
def to_markdown(textile:)
|
|
26
|
+
MdxTex::ToMarkdown.new.execute(textile)
|
|
27
|
+
end
|
|
28
|
+
|
|
24
29
|
def load_string_extension!
|
|
25
30
|
require 'mdx_tex/core_ext/string'
|
|
26
31
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mdx-tex
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gloria Budiman
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-05-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Converts Markdown syntax to Textile syntax, with configurable header
|
|
14
14
|
levels and list depth.
|
|
@@ -24,6 +24,11 @@ files:
|
|
|
24
24
|
- lib/mdx_tex/configuration.rb
|
|
25
25
|
- lib/mdx_tex/core_ext/string.rb
|
|
26
26
|
- lib/mdx_tex/railtie.rb
|
|
27
|
+
- lib/mdx_tex/to_markdown.rb
|
|
28
|
+
- lib/mdx_tex/to_markdown/bold.rb
|
|
29
|
+
- lib/mdx_tex/to_markdown/header.rb
|
|
30
|
+
- lib/mdx_tex/to_markdown/ordered_list.rb
|
|
31
|
+
- lib/mdx_tex/to_markdown/unordered_list.rb
|
|
27
32
|
- lib/mdx_tex/to_textile.rb
|
|
28
33
|
- lib/mdx_tex/to_textile/bold.rb
|
|
29
34
|
- lib/mdx_tex/to_textile/errors.rb
|