Skip to content

Commit 1251708

Browse files
committed
👓readme
1 parent 2c6be41 commit 1251708

File tree

3 files changed

+136
-5
lines changed

3 files changed

+136
-5
lines changed

README.md

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,128 @@
11
# snippet
2-
A snippet extractor
2+
3+
## purpose
4+
Extract text snippets from files.
5+
6+
This works on plain old text files of any kind.
7+
- Works on any language, reading from source
8+
- Customisable syntax
9+
- Write to templated output (e.g. `.md` triple quotes)
10+
- Hide sections from output
11+
- Validation to help avoid snippets breaking as code changes
12+
13+
## motivation
14+
Investigating approaches for having language-native code
15+
tested and displayed in documentation.
16+
- The docs need a written example demonstrating use of some code
17+
- But documentation can become outdated, or contain its own errors
18+
- Treat documentation examples as you would any other code in your project
19+
- So write some tests for them!
20+
- But it can be hard to instrument examples in a testable way... typically tests need:
21+
- Setup
22+
- Assertions
23+
- Hooks, grey-box tweaking and other checks
24+
- Teardown
25+
26+
One approach, using `snippet`, is to extract the useful (customer readable!) part of the example from
27+
within the test, ready for rendering in the docs.
28+
29+
## example
30+
Say you have an API that you would like to document and test:
31+
```
32+
class MyTest():
33+
def setUp():
34+
self.load_fixtures()
35+
36+
def test():
37+
import my_api
38+
api = my_api()
39+
items = api.list_items()
40+
for item in items:
41+
print(item)
42+
assert len(items) > 1
43+
```
44+
45+
`snippet` can be used to extract the useful documentation that's buried in this code:
46+
```python
47+
# listing api items
48+
items = api.list_items()
49+
for item in items:
50+
print(item)
51+
```
52+
53+
It does this using a customisable markup syntax:
54+
```
55+
class MyTest():
56+
def setUp():
57+
self.load_fixtures()
58+
59+
def test():
60+
import my_api
61+
api = my_api()
62+
# an example: listing api items
63+
items = api.list_items()
64+
for item in items:
65+
print(item)
66+
# end of example
67+
assert len(items) > 1
68+
```
69+
70+
## cloaking
71+
Maybe you can't escape a janky test hook in your code, or you don't fancy
72+
having a separate file for your doc tests:
73+
```
74+
import my_api
75+
api = my_api()
76+
items = api.list_items()
77+
for item in items:
78+
assert 'id' in item
79+
assert item.size > 42
80+
print(item)
81+
assert len(items) > 1
82+
```
83+
`snippet` can exclude lines from the output:
84+
```
85+
import my_api
86+
api = my_api()
87+
# an example: listing api items
88+
items = api.list_items()
89+
for item in items:
90+
# cloak
91+
assert 'id' in item
92+
assert item.size > 42
93+
# uncloak
94+
print(item)
95+
# end of example
96+
assert len(items) > 1
97+
```
98+
Once again this gives the thoroughly readable:
99+
```python
100+
# listing api items
101+
items = api.list_items()
102+
for item in items:
103+
print(item)
104+
```
105+
106+
## validation
107+
Because `snippet` is language-agnostic it is also unlikely that an
108+
IDE would detect invalid snippets, or changes to code that may break them.
109+
110+
`snippet` can be configured to validate snippets to avoid the syntax from
111+
introducing bizarre broken documentation to your users. That said, it can't
112+
do everything - and it's still wise to QA your docs from time to time.
113+
Some of the checks include:
114+
- Tag open / close matches (can't nest examples or cloaks)
115+
- Examples left unterminated
116+
- Indents reducing beyond the start level for the current example
117+
118+
# future work
119+
This is best tracked with proper issues, but areas to work on may include:
120+
121+
- [x] Pep8 compliance
122+
- [x] Test coverage
123+
- [x] Refactor
124+
- [x] Cloaking (disabling capture, without making a new snippet)
125+
- [ ] Test config load
126+
- [ ] Template workflow
127+
- [ ] Parsing with regex
128+
- [ ] Parsing performance

src/snippet/file_wrangler.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import pystache
1+
import os
22
import glob
33

4+
import pystache
5+
46
from snippet.config import Config
57

68

@@ -11,7 +13,10 @@ def write_example(config: Config, path, example_name, example_block):
1113
name=example_name,
1214
code=example_block
1315
)
14-
with open(path, 'a' if config.output_append else 'w') as fh:
16+
# TODO: this, properly...
17+
clean_name = example_name.strip().replace(' ', '')
18+
output_file = os.path.join(config.output_dir, example_name)
19+
with open(output_file, 'a' if config.output_append else 'w') as fh:
1520
fh.write(output)
1621

1722

tests/test_parser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66

77
start = f'# this is {Config.start_flag}: '
88
newline = '\n'
9-
A = 'items = my_api().do_something()\n'
9+
A = 'items = my_api().list_items()\n'
1010
B = 'for item in items:\n'
1111
C = ' print(item.name)\n'
1212
stop = f'# {Config.end_flag}\n'
1313
cloak = f'# {Config.cloak_flag}\n'
1414
uncloak = f'# {Config.uncloak_flag}\n'
1515

1616
# however the output is combined, it should match this
17-
sample_output = """items = my_api().do_something()\nfor item in items:\n print(item.name)"""
17+
sample_output = """items = my_api().list_items()\nfor item in items:\n print(item.name)"""
1818

1919

2020
class Test(unittest.TestCase):

0 commit comments

Comments
 (0)