Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problems with doctorings #10

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 23 additions & 24 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ and embed documentation into your code. I know, purist literate programmers will

## What is all this (il)literate programming about?

If you've never heard about literate programming before, then I suggest you to read at least the
If you've never heard about literate programming before, then I suggest you to read at least the
[Wikipedia entry](https://en.wikipedia.org/wiki/Literate_programming)
and then we can continue discussing.
and then we can continue discussing.
It is a fascinating topic, and there are [many resources](http://www.literateprogramming.com) out there.

Back already? Ok, so here is illiterate's take on this matter.
Expand All @@ -31,12 +31,12 @@ Hence, even though it is awesome, literate programming has some major practical
make it impossible for many people to apply it widely, including:

- Poor support from editors and lack of tooling, which is not just a matter of syntax highlighting. The very feature that makes
literate programming extra powerful, i.e., macros, makes it almost impossible for any semantic analysis to work, so forget
about intellisense, smart completion, or interactive linting.
literate programming extra powerful, i.e., macros, makes it almost impossible for any semantic analysis to work, so forget
about intellisense, smart completion, or interactive linting.
- A hard entry curve, since unfortunately people in the 21st century still learns to code the "old" way, that is,
code-first. Introducing someone into literate programming is hard because it takes some time to grok it and understand the benefits.
code-first. Introducing someone into literate programming is hard because it takes some time to grok it and understand the benefits.
- It's hard to incrementally switch to it. If you already have a somewhat large program written in the "traditional" way,
it's very hard to port it to the literate programming paradigm incrementally.
it's very hard to port it to the literate programming paradigm incrementally.

All these reasons make it, at least for me, almost impossible to apply pure literate programming in anything more
than toy projects. However, I do love the paradigm, and I do think it makes you a better programmer, and makes your code
Expand All @@ -51,29 +51,29 @@ paradigm, and also because it is supposed to help us illiterates to write more l
## So what does illiterate proposes?

Glad you asked. The idea is to encourage a more literate codebase while introducing as few changes as possible.
Specifically, you should not need to use new tools, editor extensions, or preprocessors. Code written using the
Specifically, you should not need to use new tools, editor extensions, or preprocessors. Code written using the
illiterate style looks exactly like regular code, but hopefully, a bit better.

Everything stems from these key principles:

- Documentation for a codebase should be written as prose, and it should be enjoyable to read it top to bottom.
It should not be simply a list of modules and methods with few-line descriptions; rather, it should be a cohesive
piece of literature that clearly explains the authors' intents for any small details and how everything fits into the bigger
picture.
- Documentation should be as close as possible to real code, ideally right next to it, instead of in external
markdown files that can easily get out of sync. Furthermore, there should be some automated way
to ensure that documentation is up to date, ideally with embedded unit tests that fail if documentation is wrong.
- Documentation for a codebase should be written as prose, and it should be enjoyable to read it top to bottom.
It should not be simply a list of modules and methods with few-line descriptions; rather, it should be a cohesive
piece of literature that clearly explains the authors' intents for any small details and how everything fits into the bigger
picture.
- Documentation should be as close as possible to real code, ideally right next to it, instead of in external
markdown files that can easily get out of sync. Furthermore, there should be some automated way
to ensure that documentation is up to date, ideally with embedded unit tests that fail if documentation is wrong.
- Documentation should be written both for users of your code and future developers, and it should be
easy for anyone to dive as deep as they want. This means that users only interested in calling your high-level
API can easily understand how to use it, while collaborators or anyone wanting to understand how everything works
should also be able to follow all the details.
easy for anyone to dive as deep as they want. This means that users only interested in calling your high-level
API can easily understand how to use it, while collaborators or anyone wanting to understand how everything works
should also be able to follow all the details.

To achieve these objectives, illiterate proposes really only one major paradigm shift:

> **Your code is the documentation.**

That's it. You will simply write all the documentation for your code right inside your code, as comments and as Python docstrings,
according that what is more convenient for each case. But keep this in mind, *all your code will be published as-is for documentation purposes*.
according that what is more convenient for each case. But keep this in mind, _all your code will be published as-is for documentation purposes_.

Now you are forced to think about your code in terms of: "well, this will be read by users at some point, so I better make it as publishable as possible".
This means that you can no longer simply put some throw-away code in some forsaken `_tmp.py` file. That file will appear in the documentation, so it better be publishable.
Expand All @@ -85,14 +85,14 @@ It will parse all your code, and output nicely formatted Markdown versions of ea

To use it, you simply run:

python -m illiterate build [src] [output]
python -m illiterate build --src [src]:[output]

Where `[src]` is the folder that is the root of your project's code (i.e., the top-level folder with an `__init__.py` inside), and `[output]` is where you want the markdown files. You can add `--copy from:to` to copy verbatim some files into the output folder.
I do this for copying the `Readme.md` into an `index.md` which becomes the homepage.

For example, in this project, standing on the root folder (where this Readme is located), you would run the following (🤓 yeah, it is kind if Inception-ish):

python -m illiterate build illiterate docs --copy Readme.md:index.md
python -m illiterate build --src illiterate:docs --copy Readme.md:index.md

This will take all the code in `illiterate`, convert it to Markdown, and drop it inside the `docs` folder.
It will also copy the `Readme.md` file into `docs/index.md`.
Expand All @@ -105,17 +105,16 @@ The same procedure can be obtained using configuration files. The configuration
inline: true # true if we want to process the comments within code blocks
output: docs # The address of the output folder
sources: # List of files to be processed or copied
sample_module.file: sample_module/file.py # An input is made up of the address of the output file without suffix and using periods as a separator and the address of the input file.
sample_module.file: sample_module/file.py # An input is made up of the address of the output file without suffix and using periods as a separator and the address of the input file.
```

The configuration is usually in a file called **illiterate.yml** somehow mimicking mkdocs.

Although these configuration files can be done completely by hand, we recommend using the command `python -m illiterate preset init` to create it. This command uses the same parameters as the `python -m illiterate build` command but instead of parsing the code directly it creates the appropriate configuration file.
As long as we have a configuration file to start with, we can modify it and include other details to suit our purpose as our code progresses. Then we can make illiterate work according to that setting using the command `python -m illiterate preset build` to build the Markdowns.


What you do with those Markdowns is up to you. In this project, I use [mkdocs](https://mkdocs.org) for documentation.
If you have `mkdocs`, then make sure to have your `mkdocs.yml` correctly configured so that it renders those freshly created markdowns.
What you do with those Markdowns is up to you. In this project, I use [mkdocs](https://mkdocs.org) for documentation.
If you have `mkdocs`, then make sure to have your `mkdocs.yml` correctly configured so that it renders those freshly created markdowns.
You can also see the `mkdocs.yml` in this repository to get an idea of how that configuration looks, but beware, I'm using some custom
themes and other stuff you might or might not want.

Expand Down
28 changes: 27 additions & 1 deletion illiterate/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,20 +331,45 @@ def parse(self):

for line in self.input_src:
if self.state == State.Docstring:
if line.strip().startswith('"""'):
# If we are in the Docstring node and
# in the line there is the symbols to close it
# We must parse the current content and
# to change our state
if '"""' in line: # It should matter if the closed symbol is down the text or at the end of the last line
current = self.store(current)
self.state = State.Markdown

elif self.state == State.Python:

if line.startswith("#") or (
self.config.inline and line.strip().startswith("#")
):
current = self.store(current)
self.state = State.Markdown

elif line.strip().startswith('"""'):
current = self.store(current)
self.state = State.Docstring

elif len(re.findall(r'"""', line)) == 1:
self.state = State.PythonWithDocstring

elif self.state == State.PythonWithDocstring:
# A docstring is only a string block that they don't matter
# for the logic of program. But we can also use the syntax """...."""
# to define some important strings into our program.
# So, to detect when is a docstring or no we need a new state

if line.startswith("#") or (
self.config.inline and line.strip().startswith("#")
):
self.state = State.Python
current = self.store(current)
self.state = State.Markdown

elif len(re.findall(r'"""', line)) == 1:
self.state = State.Python

elif self.state == State.Markdown:
if line.strip().startswith('"""'):
current = self.store(current)
Expand Down Expand Up @@ -393,6 +418,7 @@ class State(enum.Enum):
Markdown = 1
Docstring = 2
Python = 3
PythonWithDocstring = 4


# And this is it 🖖.