Skip to content

Commit 93b9ee8

Browse files
authored
Support fixing adjacent inline references (pre-sort) (hellt#10)
* Add support for separating adjacent footnote references with a space * Simplify 'adjacent' argument definition and improve conditional logic * Fix unexpected pattern replace when the same inline ref appears twice * Modify test data for adjacent inline refs * Modify adjacent spacing function name and move function call to main * Add command line argument documentation to README.md * Rename for accuracy (space_adjacent_footnotes to space_adjacent_references) * Remove commented test function as it isn't needed * Update CONTRIBUTING.md - replace emoji with text version Replace unicode emoji with the text version so the markdown is plain text. `:tada:` * Update CONTRIBUTING.md - missed the bulb emoji Missed the bulb emoji in the last commit. Plaintext for the win. * Remove extra argument from sort_footnotes in main * Remove named argument (as there's a single arg) * Add adjacent inline refs to test data
1 parent 44a6b09 commit 93b9ee8

File tree

7 files changed

+206
-5
lines changed

7 files changed

+206
-5
lines changed

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Contributing
22

3-
🎉 Thank you for your interest in contributing to this project! 🎉
3+
:tada: Thank you for your interest in contributing to this project! :tada:
44

55
A couple of quick items:
66

@@ -55,4 +55,4 @@ Choose one of the below testing commands methods that suits your needs.
5555
1. Discover unit tests in specific directory (in this case, `tests`) with verbosity
5656
* `python -m unittest discover -s tests -v`
5757

58-
💡 For additional `unittest` command line options, refer to the [official Python unittest documentation](https://docs.python.org/3/library/unittest.html#command-line-interface).
58+
:bulb: For additional `unittest` command line options, refer to the [official Python unittest documentation](https://docs.python.org/3/library/unittest.html#command-line-interface).

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,16 @@ Or download the script and put it in your `$PATH`:
2727

2828
```bash
2929
curl -sL https://raw.githubusercontent.com/hellt/markdown-footnote-sorter/main/fnsort.py
30+
31+
fnsort.py path/to/doc.md
3032
```
3133

34+
## Command Line Arguments
35+
### --adjacent
36+
Adjacent inline references that are not separated by other characters become problematic (by default).
37+
This option adds spacing between those inline references so they are properly identified during sorting.
38+
39+
`fnsort.py path/to/doc.md --adjacent`
3240

3341
## Contributing
3442
For information about contributing to this project, see the [contributing guidelines](CONTRIBUTING.md).

fnsort.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ def parse_arguments():
2828
default="-",
2929
help="Input file to process (use - for stdin)",
3030
)
31+
32+
parser.add_argument(
33+
"--adjacent",
34+
action="store_true",
35+
help="Fix adjacent footnotes by adding a space between them",
36+
)
37+
3138
return parser.parse_args()
3239

3340

@@ -48,6 +55,32 @@ def replace_reference(m, order):
4855
return f"{m.group(0)[:1]}[^{order.index(m.group(2)) + 1}]"
4956

5057

58+
def space_adjacent_references(text):
59+
# add space between two inline footnotes as long as a space isn't present
60+
# ex: [^1][^2] becomes [^1] [^2]
61+
62+
# inline refs that do NOT begin with white space
63+
inline_note = re.compile(r"([^\s]\[\^\w+\])")
64+
65+
notes = inline_note.findall(text)
66+
67+
for note in notes:
68+
# matches cannot be at the beginning of a line
69+
note_re = r"(?<!^)" + re.escape(note)
70+
71+
# slice regex to remove the negative look behind and
72+
# pattern replace backslash escape chars
73+
repl = note_re[6:].replace("\\", "")
74+
75+
# print(f"\nFindall: {re.findall(note_re, text, flags=re.MULTILINE)}\n")
76+
77+
# slice the string to separate preceding character from inline reference
78+
# ex: s[^4]
79+
text = re.sub(note_re, f"{repl[0]} {repl[1:]}", text, flags=re.MULTILINE)
80+
81+
return text
82+
83+
5184
def sort_footnotes(text):
5285
# removes the last newline from EOF so there is no EOL on the last line
5386
text = text.rstrip()
@@ -84,6 +117,10 @@ def main():
84117
args = parse_arguments()
85118
with open(args.file, "r+") as file:
86119
text = file.read()
120+
121+
if args.adjacent:
122+
text = space_adjacent_references(text)
123+
87124
processed_text = sort_footnotes(text)
88125
file.seek(0)
89126
file.write(processed_text)

tests/adjacent/adjacent.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Wrens
2+
3+
## Overview
4+
Wrens [^4][^7] are a family of brown birds of which there are 88 species.
5+
6+
## Size
7+
Wrens are medium-small to very small birds. The Eurasian wren is among the smallest birds in its range, while the smaller species from the Americas are among the smallest passerines in that part of the world.[^1]
8+
9+
## Coloration
10+
The dominating colors of their plumage are generally drab, composed of gray, brown, black, and white, and most species show some barring, especially on the tail or wings[^1].
11+
12+
## Diet
13+
Wrens are primarily insectivorous, eating insects, spiders and other small invertebrates, but many species also eat vegetable matter and some eat small frogs and lizards[^4][^7].
14+
15+
## Species
16+
### Eurasian Wren
17+
![Eurasian wren](https://upload.wikimedia.org/wikipedia/commons/thumb/6/6c/Ein_neugieriger_Zaunk%C3%B6nig.jpg/240px-Ein_neugieriger_Zaunk%C3%B6nig.jpg)[^3]
18+
19+
The Eurasian wren is the only species of wren found outside the Americas, as restricted to Europe, Asia, and northern Africa.[^2]
20+
21+
The Eurasian wren has been recorded wading into shallow water to catch small fish and tadpoles[^6] .
22+
23+
The most common call of the Eurasian is a sharp repeated "tic-tic-tic"[^5].
24+
25+
[^4]: https://en.wikipedia.org/wiki/Wren
26+
[^7]: https://www.allaboutbirds.org/guide/browse/shape/Wrens
27+
[^1]: https://en.wikipedia.org/wiki/Wren#Description
28+
[^5]: https://en.wikipedia.org/wiki/Eurasian_wren#Vocalizations
29+
[^2]: https://en.wikipedia.org/wiki/Wren#Distribution_and_habitat
30+
[^6]: https://en.wikipedia.org/wiki/Wren#Behavior_and_ecology
31+
[^3]: https://en.wikipedia.org/wiki/File:Ein_neugieriger_Zaunk%C3%B6nig.jpg
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Wrens
2+
3+
## Overview
4+
Wrens [^1] [^2] are a family of brown birds of which there are 88 species.
5+
6+
## Size
7+
Wrens are medium-small to very small birds. The Eurasian wren is among the smallest birds in its range, while the smaller species from the Americas are among the smallest passerines in that part of the world. [^3]
8+
9+
## Coloration
10+
The dominating colors of their plumage are generally drab, composed of gray, brown, black, and white, and most species show some barring, especially on the tail or wings [^3].
11+
12+
## Diet
13+
Wrens are primarily insectivorous, eating insects, spiders and other small invertebrates, but many species also eat vegetable matter and some eat small frogs and lizards [^1] [^2].
14+
15+
## Species
16+
### Eurasian Wren
17+
![Eurasian wren](https://upload.wikimedia.org/wikipedia/commons/thumb/6/6c/Ein_neugieriger_Zaunk%C3%B6nig.jpg/240px-Ein_neugieriger_Zaunk%C3%B6nig.jpg) [^4]
18+
19+
The Eurasian wren is the only species of wren found outside the Americas, as restricted to Europe, Asia, and northern Africa. [^5]
20+
21+
The Eurasian wren has been recorded wading into shallow water to catch small fish and tadpoles [^6] .
22+
23+
The most common call of the Eurasian is a sharp repeated "tic-tic-tic" [^7].
24+
25+
[^1]: https://en.wikipedia.org/wiki/Wren
26+
[^2]: https://www.allaboutbirds.org/guide/browse/shape/Wrens
27+
[^3]: https://en.wikipedia.org/wiki/Wren#Description
28+
[^4]: https://en.wikipedia.org/wiki/File:Ein_neugieriger_Zaunk%C3%B6nig.jpg
29+
[^5]: https://en.wikipedia.org/wiki/Wren#Distribution_and_habitat
30+
[^6]: https://en.wikipedia.org/wiki/Wren#Behavior_and_ecology
31+
[^7]: https://en.wikipedia.org/wiki/Eurasian_wren#Vocalizations

tests/adjacent/adjacent_spacing.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Wrens
2+
3+
## Overview
4+
Wrens [^4] [^7] are a family of brown birds of which there are 88 species.
5+
6+
## Size
7+
Wrens are medium-small to very small birds. The Eurasian wren is among the smallest birds in its range, while the smaller species from the Americas are among the smallest passerines in that part of the world. [^1]
8+
9+
## Coloration
10+
The dominating colors of their plumage are generally drab, composed of gray, brown, black, and white, and most species show some barring, especially on the tail or wings [^1].
11+
12+
## Diet
13+
Wrens are primarily insectivorous, eating insects, spiders and other small invertebrates, but many species also eat vegetable matter and some eat small frogs and lizards [^4] [^7].
14+
15+
## Species
16+
### Eurasian Wren
17+
![Eurasian wren](https://upload.wikimedia.org/wikipedia/commons/thumb/6/6c/Ein_neugieriger_Zaunk%C3%B6nig.jpg/240px-Ein_neugieriger_Zaunk%C3%B6nig.jpg) [^3]
18+
19+
The Eurasian wren is the only species of wren found outside the Americas, as restricted to Europe, Asia, and northern Africa. [^2]
20+
21+
The Eurasian wren has been recorded wading into shallow water to catch small fish and tadpoles [^6] .
22+
23+
The most common call of the Eurasian is a sharp repeated "tic-tic-tic" [^5].
24+
25+
[^4]: https://en.wikipedia.org/wiki/Wren
26+
[^7]: https://www.allaboutbirds.org/guide/browse/shape/Wrens
27+
[^1]: https://en.wikipedia.org/wiki/Wren#Description
28+
[^5]: https://en.wikipedia.org/wiki/Eurasian_wren#Vocalizations
29+
[^2]: https://en.wikipedia.org/wiki/Wren#Distribution_and_habitat
30+
[^6]: https://en.wikipedia.org/wiki/Wren#Behavior_and_ecology
31+
[^3]: https://en.wikipedia.org/wiki/File:Ein_neugieriger_Zaunk%C3%B6nig.jpg

tests/test_footnote_sorting.py

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1+
import argparse
12
import unittest
23

34
import fnsort
45

6+
7+
def set_command_line_args(args):
8+
""" Set (what would otherwise be) command-line arguments """
9+
return argparse.Namespace(**args)
10+
11+
512
class TestDefaults(unittest.TestCase):
613
@classmethod
714
def setUpClass(self):
@@ -16,6 +23,12 @@ def setUpClass(self):
1623
with open(f"{path}/example_expected.md") as fh:
1724
self.expected_text = fh.read()
1825

26+
# technically there is also a "file" kwarg by default
27+
args = {"adjacent": False}
28+
self.args = set_command_line_args(args)
29+
if self.args.adjacent:
30+
self.text = fnsort.space_adjacent_references(self.text)
31+
1932
# allow for full diff output
2033
# self.maxDiff = None
2134

@@ -51,6 +64,12 @@ def setUpClass(self):
5164
with open(f"{path}/duplicates_expected.md") as fh:
5265
self.expected_text = fh.read()
5366

67+
# technically there is also a "file" kwarg by default
68+
args = {"adjacent": False}
69+
self.args = set_command_line_args(args)
70+
if self.args.adjacent:
71+
self.text = fnsort.space_adjacent_references(self.text)
72+
5473
# allow for full diff output
5574
# self.maxDiff = None
5675

@@ -90,6 +109,12 @@ def setUpClass(self):
90109
with open(f"{path}/must_be_last_expected.md") as fh:
91110
self.expected_text = fh.read()
92111

112+
# technically there is also a "file" kwarg by default
113+
args = {"adjacent": False}
114+
self.args = set_command_line_args(args)
115+
if self.args.adjacent:
116+
self.text = fnsort.space_adjacent_references(self.text)
117+
93118
# allow for full diff output
94119
# self.maxDiff = None
95120

@@ -104,12 +129,50 @@ def test_footnote_sort_trailing_text(self):
104129
footnote creating a choppy looking list
105130
106131
in short this is not expected to return the desired output
107-
108-
just so happened to luck out that the last footnote reference of the
109-
"duplicates" example was indeed the last footnote :shrug:
110132
"""
111133
self.assertNotEqual(fnsort.sort_footnotes(self.text), self.expected_text)
112134

113135

136+
class TestAdjacentFootnotes(unittest.TestCase):
137+
@classmethod
138+
def setUpClass(self):
139+
# example markdown files intentionally have had trailing EOL trimmed
140+
# from the end of the file (EOF)
141+
142+
path = "tests/adjacent"
143+
144+
with open(f"{path}/adjacent.md") as fh:
145+
self.text = fh.read()
146+
147+
with open(f"{path}/adjacent_expected.md") as fh:
148+
self.expected_text = fh.read()
149+
150+
# technically there is also a "file" kwarg by default
151+
args = {"adjacent": True}
152+
self.args = set_command_line_args(args)
153+
154+
# allow for full diff output
155+
# self.maxDiff = None
156+
157+
158+
def test_adjacent_inline_reference_spacing(self):
159+
""" Test spacing out adjacent inline references """
160+
with open(f"tests/adjacent/adjacent_spacing.md") as fh:
161+
spacing_text = fh.read()
162+
163+
self.assertEqual(
164+
fnsort.space_adjacent_references(self.text),
165+
spacing_text
166+
)
167+
168+
169+
def test_adjacent_footnote_sort(self):
170+
""" Entire footnote sort process with adjacent footnote references """
171+
if self.args.adjacent:
172+
self.text = fnsort.space_adjacent_references(self.text)
173+
174+
self.assertEqual(fnsort.sort_footnotes(self.text), self.expected_text)
175+
176+
114177
if __name__ == "__main__":
115178
unittest.main()

0 commit comments

Comments
 (0)