Render local readme files before sending off to GitHub.
Grip is a command-line server application written in Python that uses the GitHub markdown API to render a local readme file. The styles also come directly from GitHub, so you'll know exactly how it will appear.
Sometimes you just want to see the exact readme result before committing and pushing to GitHub.
Especially when doing Readme-driven development.
To install grip, simply:
$ pip install grip
To render the readme of a repository:
$ cd myrepo
$ grip
* Running on http://localhost:6419/
Now open a browser and visit http://localhost:6419.
You can also specify a port:
$ grip 80
* Running on http://localhost:80/
Or an explicit file:
$ grip AUTHORS.md
* Running on http://localhost:6419/
Alternatively, you could just run grip
and visit localhost:6419/AUTHORS.md
since grip supports relative URLs.
You can combine the previous examples. Or specify a hostname instead of a port. Or provide both.
$ grip AUTHORS.md 80
* Running on http://localhost:80/
$ grip CHANGES.md 0.0.0.0
* Running on http://0.0.0.0:6419/
$ grip . 0.0.0.0:80
* Running on http://0.0.0.0:80/
You can even bypass the server and export to a single HTML file, with all the styles and assets inlined:
$ grip --export
Exporting to README.html
Control the output name with the second argument:
$ grip README.md --export readme.html
Exporting to readme.html
Reading and writing from stdin and stdout is also supported, allowing you to use Grip with other programs:
$ cat README.md | grip -
* Running on http://localhost:6419/
$ grip AUTHORS.md --export - | bcat
$ cat README.md | grip --export - | less
This allows you to quickly test how things look by entering Markdown directly in your terminal:
$ grip -
Hello **world**!
^D
* Running on http://localhost:6419/
Note: ^D
means Ctrl+D
, which works on Linux and iOS. On Windows you'll have to use Ctrl+Z
.
Comment / issue-style GFM is also supported, with an optional repository context for linking to issues:
$ grip --gfm --context=joeyespo/grip
* Running on http://localhost:6419/
For more details and additional options, see the help:
$ grip -h
Grip strives to be as close to GitHub as possible. To accomplish this, grip uses GitHub's Markdown API so that changes to their rendering engine are reflected immediately without requiring you to upgrade grip. However, because of this you may hit the API's hourly rate limit. If this happens, grip offers a way to access the API using your credentials to unlock a much higher rate limit.
$ grip --user <your-username> --pass <your-password>
Or use a personal access token with an empty scope (note that a token is required if your GitHub account is set up with two-factor authentication):
$ grip --pass <token>
You can persist these options in your local configuration. For security purposes, it's highly recommended that you use an access token over a password. (You could also keep your password safe by configuring Grip to grab your password from a password manager.)
There's also a work-in-progress branch to provide offline rendering. Once this resembles GitHub more precisely, it'll be exposed in the CLI, and will ultimately be used as a seamless fallback engine for when the API can't be accessed.
Grip always accesses GitHub over HTTPS, so your README and credentials are protected.
- GitHub introduced read-only task lists to all Markdown documents in repositories and wikis back in April, but the API doesn't respect this yet.
To customize Grip, create ~/.grip/settings.py
, then add one or more of the following variables:
HOST
: The host to use when not provided as a CLI argument,localhost
by defaultPORT
: The port to use when not provided as a CLI argument,6419
by defaultDEBUG
: Whether to use Flask's debugger when an error happens,True
by defaultDEBUG_GRIP
: Prints extended information when an error happens,False
by defaultUSERNAME
: The username to use when not provided as a CLI argument,None
by defaultPASSWORD
: The password or personal access token to use when not provided as a CLI argument (Please don't save your passwords here. Instead, use an access token or drop in this code grab your password from a password manager),None
by defaultAPI_URL
: Base URL for the github API, for example that of a Github Enterprise instance. The default is the public API https://api.github.com.CACHE_DIRECTORY
: The directory, relative to~/.grip
, to place cached assets (this gets run through the following filter:CACHE_DIRECTORY.format(version=__version__)
),'cache-{version}'
by defaultCACHE_URL
: The URL to serve cached styles and assets from, in case there's a URL conflict,'/grip-cache'
by defaultSTATIC_URL_PATH
: The URL to serve static assets from, in case there's a URL conflict,'/grip-static'
by defaultSTYLE_URLS
: Additional URLs that will be added to the rendered page,[]
by defaultSTYLE_URLS_SOURCE
: The URL to use to locate and download the styles from,https://github.com/joeyespo/grip
by defaultSTYLE_URLS_RE
: The regular expression to use to parse the styles from the sourceSTYLE_ASSET_URLS_RE
: The regular expression to use to parse the assets from the stylesSTYLE_ASSET_URLS_SUB
: Replaces the above regular expression with a local URL, as saved in the cacheSTYLE_ASSET_URLS_INLINE
: The regular expression to use when inlining assets into the downloaded style Note that this must include both the original and post-STYLE_ASSET_URLS_SUB
patterns.
This file is a normal Python script, so you can add more advanced configuration.
For example, to read a setting from the environment and provide a default value when it's not set:
PORT = os.environ.get('GRIP_PORT', 8080)
You can access the API directly with Python, using it in your own projects:
from grip import serve
serve(port=8080)
* Running on http://localhost:8080/
Or access the underlying Flask application for even more flexibility:
from grip import create_app
grip_app = create_app(gfm=True)
# Use in your own app
Runs a local server and renders the Readme file located
at path
when visited in the browser.
serve(path=None, host=None, port=None, gfm=False, context=None, username=None, password=None, render_offline=False, render_wide=False, render_inline=False, api_url=None, title=None)
path
: The filename to render, or the directory containing your Readme file, defaulting to the current working directoryhost
: The host to listen on, defaulting to the HOST configuration variableport
: The port to listen on, defaulting to the PORT configuration variablegfm
: Whether to render using GitHub Flavored Markdowncontext
: The project context to use whengfm
is true, which takes the form ofusername/project
username
: The user to authenticate with GitHub to extend the API limitpassword
: The password to authenticate with GitHub to extend the API limitrender_offline
: Whether to render locally using Python-Markdown (Note: this is a work in progress)render_wide
: Whether to render a wide page,False
by default (this has no effect when used withgfm
)render_inline
: Whether to inline the styles within the HTML fileapi_url
: A different base URL for the github API, for example that of a Github Enterprise instance. The default is the public API https://api.github.com.title
: The page title, derived frompath
by default
Writes the specified Readme file to an HTML file with styles and assets inlined.
export(path=None, gfm=False, context=None, username=None, password=None, render_offline=False, render_wide=False, render_inline=True, out_filename=None, api_url=None, title=None)
path
: The filename to render, or the directory containing your Readme file, defaulting to the current working directorygfm
: Whether to render using GitHub Flavored Markdowncontext
: The project context to use whengfm
is true, which takes the form ofusername/project
username
: The user to authenticate with GitHub to extend the API limitpassword
: The password to authenticate with GitHub to extend the API limitrender_offline
: Whether to render locally using Python-Markdown (Note: this is a work in progress)render_wide
: Whether to render a wide page,False
by default (this has no effect when used withgfm
)render_inline
: Whether to inline the styles within the HTML file (Note: unlike the other API functions, this defaults toTrue
)out_filename
: The filename to write to,<in_filename>.html
by defaultapi_url
: A different base URL for the github API, for example that of a Github Enterprise instance. The default is the public API https://api.github.com.title
: The page title, derived frompath
by default
Creates a Flask application you can use to render and serve the Readme files.
This is the same app used by serve
and export
and initializes the cache,
using the cached styles when available.
create_app(path=None, gfm=False, context=None, username=None, password=None, render_offline=False, render_wide=False, render_inline=False, api_url=None, title=None, text=None)
path
: The filename to render, or the directory containing your Readme file, defaulting to the current working directorygfm
: Whether to render using GitHub Flavored Markdowncontext
: The project context to use whengfm
is true, which takes the form ofusername/project
username
: The user to authenticate with GitHub to extend the API limitpassword
: The password to authenticate with GitHub to extend the API limitrender_offline
: Whether to render locally using Python-Markdown (Note: this is a work in progress)render_wide
: Whether to render a wide page,False
by default (this has no effect when used withgfm
)render_inline
: Whether to inline the styles within the HTML fileapi_url
: A different base URL for the github API, for example that of a Github Enterprise instance. The default is the public API https://api.github.com.title
: The page title, derived frompath
by defaulttext
: A string or stream of Markdown text to render instead of being loaded frompath
(Note:path
can be used to set the page title)
Renders the application created by create_app
and returns the HTML that would
normally appear when visiting that route.
render_app(app, route='/')
app
: The Flask application to renderroute
: The route to render, '/' by default
Renders the specified markdown text without caching.
render_content(text, gfm=False, context=None, username=None, password=None, render_offline=False, api_url=None, title=None)
text
: The Markdown text to rendergfm
: Whether to render using GitHub Flavored Markdowncontext
: The project context to use whengfm
is true, which takes the form ofusername/project
username
: The user to authenticate with GitHub to extend the API limitpassword
: The password to authenticate with GitHub to extend the API limitrender_offline
: Whether to render locally using Python-Markdown (Note: this is a work in progress)api_url
: A different base URL for the github API, for example that of a Github Enterprise instance. This is required when not using the offline renderer.title
: The page title, derived frompath
by default
Renders the markdown from the specified path or text, without caching, and returns an HTML page that resembles the GitHub Readme view.
render_page(path=None, gfm=False, context=None, username=None, password=None, render_offline=False, render_wide=False, render_inline=False, api_url=None, title=None, text=None)
path
: The path to use for the page title, rendering'README.md'
if Nonegfm
: Whether to render using GitHub Flavored Markdowncontext
: The project context to use whengfm
is true, which takes the form ofusername/project
username
: The user to authenticate with GitHub to extend the API limitpassword
: The password to authenticate with GitHub to extend the API limitrender_offline
: Whether to render offline using Python-Markdown (Note: this is a work in progress)render_wide
: Whether to render a wide page,False
by default (this has no effect when used withgfm
)render_inline
: Whether to inline the styles within the HTML fileapi_url
: A different base URL for the github API, for example that of a Github Enterprise instance. The default is the public API https://api.github.com.title
: The page title, derived frompath
by defaulttext
: A string or stream of Markdown text to render instead of being loaded frompath
(Note:path
can be used to set the page title)
Returns the path if it's a file; otherwise, looks for a compatible README file in the directory specified by path. If path is None, the current working directory is used. If no compatible README can be found, ValueError is raised.
resolve_readme(path=None, force=False)
path
: The filename to render, or the directory containing your Readme file, defaulting to the current working directoryforce
: Whether to force a result, even when a readme file is not found
Clears the cached styles and assets.
clear_cache()
The supported extensions, as defined by GitHub.
supported_extensions = ['.md', '.markdown']
This constant contains the names Grip looks for when no file is provided.
default_filenames = map(lambda ext: 'README' + ext, supported_extensions)
- Check the open issues or open a new issue to start a discussion around your feature idea or the bug you found
- Fork the repository, make your changes, and add yourself to Authors.md
- Send a pull request
If your PR has been waiting a while, feel free to ping me on Twitter.