Skip to content

Instantly share code, notes, and snippets.

@joeyespo
Created February 6, 2015 06:28
Show Gist options
  • Save joeyespo/519070535829e81b513e to your computer and use it in GitHub Desktop.
Save joeyespo/519070535829e81b513e to your computer and use it in GitHub Desktop.
Demonstrates how to use grip's API to export files without inlining GitHub's CSS, and an example of HTML output rendered from grip's README.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>README.md - Grip</title>
<link rel="icon" href="/grip-static/favicon.ico" /><link rel="stylesheet" href="/grip-cache/github-8f609688881a4249463fd5c6862df2e8a30b3f83dd00f6d08bbb07aeaa2b1851.css" />
<link rel="stylesheet" href="/grip-cache/github2-680b12df069daafad44834a7cfa6ee19c1239d3208aa6bcbe43f9cdaf55ae4e5.css" />
<style>
/* Page tweaks */
.preview-page {
margin-top: 64px;
}
/* Discussion tweaks */
.discussion-timeline.wide {
width: 920px;
}
.timeline-comment-wrapper > .timeline-comment:after,
.timeline-comment-wrapper > .timeline-comment:before {
content: none;
}
</style>
</head>
<body>
<div class="page">
<div class="preview-page">
<div class="container">
<div class="repository-with-sidebar repo-container with-full-navigation">
<div class="repository-content context-loader-container">
<div id="readme" class="boxed-group flush clearfix announce instapaper_body md">
<h3>
<span class="octicon octicon-book"></span>
README.md
</h3>
<div class="markdown-body entry-content">
<h1>
<a id="user-content-grip----github-readme-instant-preview" class="anchor" href="#grip----github-readme-instant-preview" aria-hidden="true"><span class="octicon octicon-link"></span></a>Grip -- GitHub Readme Instant Preview</h1>
<p><a href="http://pypi.python.org/pypi/grip/"><img src="https://camo.githubusercontent.com/2455b24d28eee2b2f93ccb0b9e0cfc725247b73b/687474703a2f2f696d672e736869656c64732e696f2f707970692f646d2f677269702e737667" alt="Downloads/month on PyPI" data-canonical-src="http://img.shields.io/pypi/dm/grip.svg" style="max-width:100%;"></a>
<a href="http://pypi.python.org/pypi/grip/"><img src="https://camo.githubusercontent.com/3fc0b2f4f2be32e31b7b4c2bfb0176ad5b9cc7e2/687474703a2f2f696d672e736869656c64732e696f2f707970692f762f677269702e737667" alt="Current version on PyPI" data-canonical-src="http://img.shields.io/pypi/v/grip.svg" style="max-width:100%;"></a></p>
<p>Render local readme files before sending off to GitHub.</p>
<p>Grip is a command-line server application written in Python that uses the
<a href="http://developer.github.com/v3/markdown">GitHub markdown API</a> to render a local readme file. The styles also
come directly from GitHub, so you'll know exactly how it will appear.</p>
<h2>
<a id="user-content-motivation" class="anchor" href="#motivation" aria-hidden="true"><span class="octicon octicon-link"></span></a>Motivation</h2>
<p>Sometimes you just want to see the exact readme
result before committing and pushing to GitHub.</p>
<p>Especially when doing <a href="http://tom.preston-werner.com/2010/08/23/readme-driven-development.html">Readme-driven development</a>.</p>
<h2>
<a id="user-content-installation" class="anchor" href="#installation" aria-hidden="true"><span class="octicon octicon-link"></span></a>Installation</h2>
<p>To install grip, simply:</p>
<div class="highlight highlight-bash"><pre>$ pip install grip</pre></div>
<h2>
<a id="user-content-usage" class="anchor" href="#usage" aria-hidden="true"><span class="octicon octicon-link"></span></a>Usage</h2>
<p>To render the readme of a repository:</p>
<div class="highlight highlight-bash"><pre>$ <span class="pl-s3">cd</span> myrepo
$ grip
<span class="pl-k">*</span> Running on http://localhost:5000/</pre></div>
<p>Now open a browser and visit <a href="http://localhost:5000/">http://localhost:5000</a>.</p>
<p>You can also specify a port:</p>
<div class="highlight highlight-bash"><pre>$ grip 80
<span class="pl-k">*</span> Running on http://localhost:80/</pre></div>
<p>Or an explicit file:</p>
<div class="highlight highlight-bash"><pre>$ grip AUTHORS.md
<span class="pl-k">*</span> Running on http://localhost:5000/</pre></div>
<p>Alternatively, you could just run <code>grip</code> and visit <a href="AUTHORS.md">localhost:5000/AUTHORS.md</a>
since grip supports relative URLs.</p>
<p>You can combine the previous examples. Or specify a hostname instead of a port. Or provide both.</p>
<div class="highlight highlight-bash"><pre>$ grip AUTHORS.md 80
<span class="pl-k">*</span> Running on http://localhost:80/</pre></div>
<div class="highlight highlight-bash"><pre>$ grip CHANGES.md 0.0.0.0
<span class="pl-k">*</span> Running on http://0.0.0.0:5000/</pre></div>
<div class="highlight highlight-bash"><pre>$ grip <span class="pl-s3">.</span> 0.0.0.0:80
<span class="pl-k">*</span> Running on http://0.0.0.0:80/</pre></div>
<p>You can even bypass the server and <strong>export</strong> to a single HTML file, with all the styles and assets inlined:</p>
<div class="highlight highlight-bash"><pre>$ grip --export
Exporting to README.html</pre></div>
<p>Control the output name with the second argument:</p>
<div class="highlight highlight-bash"><pre>$ grip README.md --export readme.html
Exporting to readme.html</pre></div>
<p>Reading and writing from <strong>stdin</strong> and <strong>stdout</strong> is also supported, allowing you to use Grip with other programs:</p>
<div class="highlight highlight-bash"><pre>$ cat README.md <span class="pl-k">|</span> grip -
<span class="pl-k">*</span> Running on http://localhost:5000/</pre></div>
<div class="highlight highlight-bash"><pre>$ grip AUTHORS.md --export - <span class="pl-k">|</span> bcat</pre></div>
<div class="highlight highlight-bash"><pre>$ cat README.md <span class="pl-k">|</span> grip --export - <span class="pl-k">|</span> less</pre></div>
<p>This allows you to quickly test how things look by entering Markdown directly in your terminal:</p>
<div class="highlight highlight-bash"><pre>$ grip -
Hello <span class="pl-k">**</span>world<span class="pl-k">**</span><span class="pl-k">!</span>
^Z
<span class="pl-k">*</span> Running on http://localhost:5000/</pre></div>
<p><strong>Comment / issue-style GFM</strong> is also supported, with an optional repository context for linking to issues:</p>
<div class="highlight highlight-bash"><pre>$ grip --gfm --context=joeyespo/grip
<span class="pl-k">*</span> Running on http://localhost:5000/</pre></div>
<p>For more details and additional options, see the help:</p>
<div class="highlight highlight-bash"><pre>$ grip -h</pre></div>
<h2>
<a id="user-content-access" class="anchor" href="#access" aria-hidden="true"><span class="octicon octicon-link"></span></a>Access</h2>
<p>Grip strives to be as close to GitHub as possible. To accomplish this, grip
uses <a href="http://developer.github.com/v3/markdown">GitHub's Markdown API</a> 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.</p>
<div class="highlight highlight-bash"><pre>$ grip --user <span class="pl-k">&lt;</span>your-username<span class="pl-k">&gt;</span> --pass <span class="pl-k">&lt;</span>your-password<span class="pl-k">&gt;</span></pre></div>
<p>Or use a <a href="https://github.com/settings/tokens/new?scopes=">personal access token</a> with an empty scope (note that a token is
<em>required</em> if your GitHub account is set up with two-factor authentication):</p>
<div class="highlight highlight-bash"><pre>$ grip --pass <span class="pl-k">&lt;</span>token<span class="pl-k">&gt;</span></pre></div>
<p>You can persist these options <a href="#configuration">in your local configuration</a>.
For security purposes, it's highly recommended that you <strong>use an access token
over a password</strong>. (You could also keep your password safe by configuring
Grip to <a href="https://gist.github.com/klmr/3840aa3c12f947e4064c">grab your password from a password manager</a>.)</p>
<p>There's also a <a href="http://github.com/joeyespo/grip/tree/fix-render-offline">work-in-progress branch</a> to provide
<strong>offline rendering</strong>. 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.</p>
<p>Grip always accesses GitHub over HTTPS,
so your README and credentials are protected.</p>
<h2>
<a id="user-content-known-issues" class="anchor" href="#known-issues" aria-hidden="true"><span class="octicon octicon-link"></span></a>Known issues</h2>
<ul>
<li>[ ] GitHub introduced read-only task lists to all Markdown documents in
repositories and wikis <a href="https://github.com/blog/1825-task-lists-in-all-markdown-documents">back in April</a>, but
<a href="http://developer.github.com/v3/markdown">the API</a> doesn't respect this yet.</li>
</ul>
<h2>
<a id="user-content-configuration" class="anchor" href="#configuration" aria-hidden="true"><span class="octicon octicon-link"></span></a>Configuration</h2>
<p>To customize Grip, create <code>~/.grip/settings.py</code>, then add one or more of the following variables:</p>
<ul>
<li>
<code>HOST</code>: The host to use when not provided as a CLI argument, <code>localhost</code> by default</li>
<li>
<code>PORT</code>: The port to use when not provided as a CLI argument, <code>5000</code> by default</li>
<li>
<code>DEBUG</code>: Whether to use Flask's debugger when an error happens, <code>True</code> by default</li>
<li>
<code>DEBUG_GRIP</code>: Prints extended information when an error happens, <code>False</code> by default</li>
<li>
<code>USERNAME</code>: The username to use when not provided as a CLI argument, <code>None</code> by default</li>
<li>
<code>PASSWORD</code>: The password or <a href="https://github.com/settings/tokens/new?scopes=">personal access token</a> to use when not provided as a CLI argument (<em>Please don't save your passwords here.</em> Instead, use an access token or drop in this code <a href="https://gist.github.com/klmr/3840aa3c12f947e4064c">grab your password from a password manager</a>), <code>None</code> by default</li>
<li>
<code>CACHE_DIRECTORY</code>: The directory, relative to <code>~/.grip</code>, to place cached assets (this gets run through the following filter: <code>CACHE_DIRECTORY.format(version=__version__)</code>), <code>'cache-{version}'</code> by default</li>
<li>
<code>CACHE_URL</code>: The URL to serve cached styles and assets from, in case there's a URL conflict, <code>'/grip-cache'</code> by default</li>
<li>
<code>STATIC_URL_PATH</code>: The URL to serve static assets from, in case there's a URL conflict, <code>'/grip-static'</code> by default</li>
<li>
<code>STYLE_URLS</code>: Additional URLs that will be added to the rendered page, <code>[]</code> by default</li>
<li>
<code>STYLE_URLS_SOURCE</code>: The URL to use to locate and download the styles from, <code>https://github.com/joeyespo/grip</code> by default</li>
<li>
<code>STYLE_URLS_RE</code>: The regular expression to use to parse the styles from the source</li>
<li>
<code>STYLE_ASSET_URLS_RE</code>: The regular expression to use to parse the assets from the styles</li>
<li>
<code>STYLE_ASSET_URLS_SUB</code>: Replaces the above regular expression with a local URL, as saved in the cache</li>
<li>
<code>STYLE_ASSET_URLS_INLINE</code>: The regular expression to use when inlining assets into the downloaded style
Note that this must include both the original and post-<code>STYLE_ASSET_URLS_SUB</code> patterns.</li>
</ul>
<h4>
<a id="user-content-advanced" class="anchor" href="#advanced" aria-hidden="true"><span class="octicon octicon-link"></span></a>Advanced</h4>
<p>This file is a normal Python script, so you can add more advanced configuration.</p>
<p>For example, to read a setting from the environment and provide a default value
when it's not set:</p>
<div class="highlight highlight-python"><pre>PORT <span class="pl-k">=</span> os.environ.get(<span class="pl-s1"><span class="pl-pds">'</span>GRIP_PORT<span class="pl-pds">'</span></span>, <span class="pl-c1">8080</span>)</pre></div>
<h2>
<a id="user-content-api" class="anchor" href="#api" aria-hidden="true"><span class="octicon octicon-link"></span></a>API</h2>
<p>You can access the API directly with Python, using it in your own projects:</p>
<div class="highlight highlight-python"><pre><span class="pl-k">from</span> grip <span class="pl-k">import</span> serve
serve(<span class="pl-vpf">port</span><span class="pl-k">=</span><span class="pl-c1">8080</span>)
<span class="pl-k">*</span> Running on http:<span class="pl-k">//</span>localhost:<span class="pl-c1">8080</span><span class="pl-k">/</span></pre></div>
<p>Or access the underlying Flask application for even more flexibility:</p>
<div class="highlight highlight-python"><pre><span class="pl-k">from</span> grip <span class="pl-k">import</span> create_app
grip_app <span class="pl-k">=</span> create_app(<span class="pl-vpf">gfm</span><span class="pl-k">=</span><span class="pl-c1">True</span>)
<span class="pl-c"># Use in your own app</span></pre></div>
<h3>
<a id="user-content-documentation" class="anchor" href="#documentation" aria-hidden="true"><span class="octicon octicon-link"></span></a>Documentation</h3>
<h4>
<a id="user-content-serve" class="anchor" href="#serve" aria-hidden="true"><span class="octicon octicon-link"></span></a>serve</h4>
<p>Runs a local server and renders the Readme file located
at <code>path</code> when visited in the browser.</p>
<div class="highlight highlight-python"><pre>serve(<span class="pl-vpf">path</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">host</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">port</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">gfm</span><span class="pl-k">=</span><span class="pl-c1">False</span>, <span class="pl-vpf">context</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">username</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">password</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">render_offline</span><span class="pl-k">=</span><span class="pl-c1">False</span>, <span class="pl-vpf">render_wide</span><span class="pl-k">=</span><span class="pl-c1">False</span>, <span class="pl-vpf">render_inline</span><span class="pl-k">=</span><span class="pl-c1">False</span>)</pre></div>
<ul>
<li>
<code>path</code>: The filename to render, or the directory containing your Readme file, defaulting to the current working directory</li>
<li>
<code>host</code>: The host to listen on, defaulting to the HOST configuration variable</li>
<li>
<code>port</code>: The port to listen on, defaulting to the PORT configuration variable</li>
<li>
<code>gfm</code>: Whether to render using <a href="http://github.github.com/github-flavored-markdown">GitHub Flavored Markdown</a>
</li>
<li>
<code>context</code>: The project context to use when <code>gfm</code> is true, which
takes the form of <code>username/project</code>
</li>
<li>
<code>username</code>: The user to authenticate with GitHub to extend the API limit</li>
<li>
<code>password</code>: The password to authenticate with GitHub to extend the API limit</li>
<li>
<code>render_offline</code>: Whether to render locally using <a href="http://github.com/waylan/Python-Markdown">Python-Markdown</a> (Note: this is a work in progress)</li>
<li>
<code>render_wide</code>: Whether to render a wide page, <code>False</code> by default (this has no effect when used with <code>gfm</code>)</li>
<li>
<code>render_inline</code>: Whether to inline the styles within the HTML file</li>
</ul>
<h4>
<a id="user-content-export" class="anchor" href="#export" aria-hidden="true"><span class="octicon octicon-link"></span></a>export</h4>
<p>Writes the specified Readme file to an HTML file with styles and assets inlined.</p>
<div class="highlight highlight-python"><pre>export(<span class="pl-vpf">path</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">gfm</span><span class="pl-k">=</span><span class="pl-c1">False</span>, <span class="pl-vpf">context</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">username</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">password</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">render_offline</span><span class="pl-k">=</span><span class="pl-c1">False</span>, <span class="pl-vpf">render_wide</span><span class="pl-k">=</span><span class="pl-c1">False</span>, <span class="pl-vpf">render_inline</span><span class="pl-k">=</span><span class="pl-c1">True</span>, <span class="pl-vpf">out_filename</span><span class="pl-k">=</span><span class="pl-c1">None</span>)</pre></div>
<ul>
<li>
<code>path</code>: The filename to render, or the directory containing your Readme file, defaulting to the current working directory</li>
<li>
<code>gfm</code>: Whether to render using <a href="http://github.github.com/github-flavored-markdown">GitHub Flavored Markdown</a>
</li>
<li>
<code>context</code>: The project context to use when <code>gfm</code> is true, which
takes the form of <code>username/project</code>
</li>
<li>
<code>username</code>: The user to authenticate with GitHub to extend the API limit</li>
<li>
<code>password</code>: The password to authenticate with GitHub to extend the API limit</li>
<li>
<code>render_offline</code>: Whether to render locally using <a href="http://github.com/waylan/Python-Markdown">Python-Markdown</a> (Note: this is a work in progress)</li>
<li>
<code>render_wide</code>: Whether to render a wide page, <code>False</code> by default (this has no effect when used with <code>gfm</code>)</li>
<li>
<code>render_inline</code>: Whether to inline the styles within the HTML file (Note: unlike the other API functions, this defaults to <code>True</code>)</li>
<li>
<code>out_filename</code>: The filename to write to, <code>&lt;in_filename&gt;.html</code> by default</li>
</ul>
<h4>
<a id="user-content-create_app" class="anchor" href="#create_app" aria-hidden="true"><span class="octicon octicon-link"></span></a>create_app</h4>
<p>Creates a Flask application you can use to render and serve the Readme files.
This is the same app used by <code>serve</code> and <code>export</code> and initializes the cache,
using the cached styles when available.</p>
<div class="highlight highlight-python"><pre>create_app(<span class="pl-vpf">path</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">gfm</span><span class="pl-k">=</span><span class="pl-c1">False</span>, <span class="pl-vpf">context</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">username</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">password</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">render_offline</span><span class="pl-k">=</span><span class="pl-c1">False</span>, <span class="pl-vpf">render_wide</span><span class="pl-k">=</span><span class="pl-c1">False</span>, <span class="pl-vpf">render_inline</span><span class="pl-k">=</span><span class="pl-c1">False</span>, <span class="pl-vpf">text</span><span class="pl-k">=</span><span class="pl-c1">None</span>)</pre></div>
<ul>
<li>
<code>path</code>: The filename to render, or the directory containing your Readme file, defaulting to the current working directory</li>
<li>
<code>gfm</code>: Whether to render using <a href="http://github.github.com/github-flavored-markdown">GitHub Flavored Markdown</a>
</li>
<li>
<code>context</code>: The project context to use when <code>gfm</code> is true, which
takes the form of <code>username/project</code>
</li>
<li>
<code>username</code>: The user to authenticate with GitHub to extend the API limit</li>
<li>
<code>password</code>: The password to authenticate with GitHub to extend the API limit</li>
<li>
<code>render_offline</code>: Whether to render locally using <a href="http://github.com/waylan/Python-Markdown">Python-Markdown</a> (Note: this is a work in progress)</li>
<li>
<code>render_wide</code>: Whether to render a wide page, <code>False</code> by default (this has no effect when used with <code>gfm</code>)</li>
<li>
<code>render_inline</code>: Whether to inline the styles within the HTML file</li>
<li>
<code>text</code>: A string or stream of Markdown text to render instead of being loaded from <code>path</code> (Note: <code>path</code> can be used to set the page title)</li>
</ul>
<h4>
<a id="user-content-render_app" class="anchor" href="#render_app" aria-hidden="true"><span class="octicon octicon-link"></span></a>render_app</h4>
<p>Renders the application created by <code>create_app</code> and returns the HTML that would
normally appear when visiting that route.</p>
<div class="highlight highlight-python"><pre>render_app(app, <span class="pl-vpf">route</span><span class="pl-k">=</span><span class="pl-s1"><span class="pl-pds">'</span>/<span class="pl-pds">'</span></span>)</pre></div>
<ul>
<li>
<code>app</code>: The Flask application to render</li>
<li>
<code>route</code>: The route to render, '/' by default</li>
</ul>
<h4>
<a id="user-content-render_content" class="anchor" href="#render_content" aria-hidden="true"><span class="octicon octicon-link"></span></a>render_content</h4>
<p>Renders the specified markdown text without caching.</p>
<div class="highlight highlight-python"><pre>render_content(text, <span class="pl-vpf">gfm</span><span class="pl-k">=</span><span class="pl-c1">False</span>, <span class="pl-vpf">context</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">username</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">password</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">render_offline</span><span class="pl-k">=</span><span class="pl-c1">False</span>)</pre></div>
<ul>
<li>
<code>text</code>: The Markdown text to render</li>
<li>
<code>gfm</code>: Whether to render using <a href="http://github.github.com/github-flavored-markdown">GitHub Flavored Markdown</a>
</li>
<li>
<code>context</code>: The project context to use when <code>gfm</code> is true, which
takes the form of <code>username/project</code>
</li>
<li>
<code>username</code>: The user to authenticate with GitHub to extend the API limit</li>
<li>
<code>password</code>: The password to authenticate with GitHub to extend the API limit</li>
<li>
<code>render_offline</code>: Whether to render locally using <a href="http://github.com/waylan/Python-Markdown">Python-Markdown</a> (Note: this is a work in progress)</li>
</ul>
<h4>
<a id="user-content-render_page" class="anchor" href="#render_page" aria-hidden="true"><span class="octicon octicon-link"></span></a>render_page</h4>
<p>Renders the markdown from the specified path or text, without caching,
and returns an HTML page that resembles the GitHub Readme view.</p>
<div class="highlight highlight-python"><pre>render_page(<span class="pl-vpf">page</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">gfm</span><span class="pl-k">=</span><span class="pl-c1">False</span>, <span class="pl-vpf">context</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">username</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">password</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">render_offline</span><span class="pl-k">=</span><span class="pl-c1">False</span>, <span class="pl-vpf">render_wide</span><span class="pl-k">=</span><span class="pl-c1">False</span>, <span class="pl-vpf">render_inline</span><span class="pl-k">=</span><span class="pl-c1">False</span>, <span class="pl-vpf">text</span><span class="pl-k">=</span><span class="pl-c1">None</span>)</pre></div>
<ul>
<li>
<code>path</code>: The path to use for the page title, rendering <code>'README.md'</code> if None</li>
<li>
<code>gfm</code>: Whether to render using <a href="http://github.github.com/github-flavored-markdown">GitHub Flavored Markdown</a>
</li>
<li>
<code>context</code>: The project context to use when <code>gfm</code> is true, which
takes the form of <code>username/project</code>
</li>
<li>
<code>username</code>: The user to authenticate with GitHub to extend the API limit</li>
<li>
<code>password</code>: The password to authenticate with GitHub to extend the API limit</li>
<li>
<code>render_offline</code>: Whether to render offline using <a href="http://github.com/waylan/Python-Markdown">Python-Markdown</a> (Note: this is a work in progress)</li>
<li>
<code>render_wide</code>: Whether to render a wide page, <code>False</code> by default (this has no effect when used with <code>gfm</code>)</li>
<li>
<code>render_inline</code>: Whether to inline the styles within the HTML file</li>
<li>
<code>text</code>: A string or stream of Markdown text to render instead of being loaded from <code>path</code> (Note: <code>path</code> can be used to set the page title)</li>
</ul>
<h4>
<a id="user-content-resolve_readme" class="anchor" href="#resolve_readme" aria-hidden="true"><span class="octicon octicon-link"></span></a>resolve_readme</h4>
<p>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.</p>
<div class="highlight highlight-python"><pre>resolve_readme(<span class="pl-vpf">path</span><span class="pl-k">=</span><span class="pl-c1">None</span>, <span class="pl-vpf">force</span><span class="pl-k">=</span><span class="pl-c1">False</span>)</pre></div>
<ul>
<li>
<code>path</code>: The filename to render, or the directory containing your Readme file, defaulting to the current working directory</li>
<li>
<code>force</code>: Whether to force a result, even when a readme file is not found</li>
</ul>
<h4>
<a id="user-content-clear_cache" class="anchor" href="#clear_cache" aria-hidden="true"><span class="octicon octicon-link"></span></a>clear_cache</h4>
<p>Clears the cached styles and assets.</p>
<div class="highlight highlight-python"><pre>clear_cache()</pre></div>
<h4>
<a id="user-content-supported_extensions" class="anchor" href="#supported_extensions" aria-hidden="true"><span class="octicon octicon-link"></span></a>supported_extensions</h4>
<p>The supported extensions, as defined by <a href="http://developer.github.com/v3/markdown">GitHub</a>.</p>
<div class="highlight highlight-python"><pre>supported_extensions <span class="pl-k">=</span> [<span class="pl-s1"><span class="pl-pds">'</span>.md<span class="pl-pds">'</span></span>, <span class="pl-s1"><span class="pl-pds">'</span>.markdown<span class="pl-pds">'</span></span>]</pre></div>
<h4>
<a id="user-content-default_filenames" class="anchor" href="#default_filenames" aria-hidden="true"><span class="octicon octicon-link"></span></a>default_filenames</h4>
<p>This constant contains the names Grip looks for when no file is provided.</p>
<div class="highlight highlight-python"><pre>default_filenames <span class="pl-k">=</span> <span class="pl-s3">map</span>(<span class="pl-st">lambda</span> <span class="pl-vpf">ext</span>: <span class="pl-s1"><span class="pl-pds">'</span>README<span class="pl-pds">'</span></span> <span class="pl-k">+</span> ext, supported_extensions)</pre></div>
<h2>
<a id="user-content-contributing" class="anchor" href="#contributing" aria-hidden="true"><span class="octicon octicon-link"></span></a>Contributing</h2>
<ol>
<li>Check the open issues or open a new issue to start a discussion around
your feature idea or the bug you found</li>
<li>Fork the repository, make your changes, and add yourself to <a href="AUTHORS.md">Authors.md</a>
</li>
<li>Send a pull request</li>
</ol>
<p>If your PR has been waiting a while, feel free to <a href="http://twitter.com/joeyespo">ping me on Twitter</a>.</p>
<p>Use this software often? Please consider supporting me on
<a href="http://gittip.com/joeyespo" title="Thank you!">
<img align="center" src="https://camo.githubusercontent.com/b6807903265d9d2fb16ee09659489b70dc2c9104/687474703a2f2f6a6f65796573706f2e636f6d2f696d616765732f6769747469702d627574746f6e2e706e67" alt="Gittip" data-canonical-src="http://joeyespo.com/images/gittip-button.png" style="max-width:100%;">
</a></p>
</div>
</div>
</div>
</div>
</div>
</div>
<div>&nbsp;</div>
</div><script>
function showCanonicalImages() {
var images = document.getElementsByTagName('img');
if (!images) {
return;
}
for (var index = 0; index < images.length; index++) {
var image = images[index];
if (image.getAttribute('data-canonical-src')) {
image.src = image.getAttribute('data-canonical-src');
}
}
}
function scrollToHash() {
if (location.hash && !document.querySelector(":target")) {
var elements = document.getElementsByName('user-content-' + location.hash.slice(1));
if (elements.length > 0) {
elements[elements.length - 1].scrollIntoView();
}
}
}
window.onhashchange = function() {
scrollToHash();
}
window.onload = function() {
scrollToHash();
}
showCanonicalImages();
</script>
</body>
</html>

Grip -- GitHub Readme Instant Preview

Downloads/month on PyPI Current version on PyPI

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.

Motivation

Sometimes you just want to see the exact readme result before committing and pushing to GitHub.

Especially when doing Readme-driven development.

Installation

To install grip, simply:

$ pip install grip

Usage

To render the readme of a repository:

$ cd myrepo
$ grip
 * Running on http://localhost:5000/

Now open a browser and visit http://localhost:5000.

You can also specify a port:

$ grip 80
 * Running on http://localhost:80/

Or an explicit file:

$ grip AUTHORS.md
 * Running on http://localhost:5000/

Alternatively, you could just run grip and visit localhost:5000/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:5000/
$ 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:5000/
$ 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**!
^Z
 * Running on http://localhost:5000/

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:5000/

For more details and additional options, see the help:

$ grip -h

Access

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.

Known issues

  • 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.

Configuration

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 default
  • PORT: The port to use when not provided as a CLI argument, 5000 by default
  • DEBUG: Whether to use Flask's debugger when an error happens, True by default
  • DEBUG_GRIP: Prints extended information when an error happens, False by default
  • USERNAME: The username to use when not provided as a CLI argument, None by default
  • PASSWORD: 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 default
  • 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 default
  • CACHE_URL: The URL to serve cached styles and assets from, in case there's a URL conflict, '/grip-cache' by default
  • STATIC_URL_PATH: The URL to serve static assets from, in case there's a URL conflict, '/grip-static' by default
  • STYLE_URLS: Additional URLs that will be added to the rendered page, [] by default
  • STYLE_URLS_SOURCE: The URL to use to locate and download the styles from, https://github.com/joeyespo/grip by default
  • STYLE_URLS_RE: The regular expression to use to parse the styles from the source
  • STYLE_ASSET_URLS_RE: The regular expression to use to parse the assets from the styles
  • STYLE_ASSET_URLS_SUB: Replaces the above regular expression with a local URL, as saved in the cache
  • STYLE_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.

Advanced

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)

API

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

Documentation

serve

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)
  • path: The filename to render, or the directory containing your Readme file, defaulting to the current working directory
  • host: The host to listen on, defaulting to the HOST configuration variable
  • port: The port to listen on, defaulting to the PORT configuration variable
  • gfm: Whether to render using GitHub Flavored Markdown
  • context: The project context to use when gfm is true, which takes the form of username/project
  • username: The user to authenticate with GitHub to extend the API limit
  • password: The password to authenticate with GitHub to extend the API limit
  • render_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 with gfm)
  • render_inline: Whether to inline the styles within the HTML file

export

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)
  • path: The filename to render, or the directory containing your Readme file, defaulting to the current working directory
  • gfm: Whether to render using GitHub Flavored Markdown
  • context: The project context to use when gfm is true, which takes the form of username/project
  • username: The user to authenticate with GitHub to extend the API limit
  • password: The password to authenticate with GitHub to extend the API limit
  • render_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 with gfm)
  • render_inline: Whether to inline the styles within the HTML file (Note: unlike the other API functions, this defaults to True)
  • out_filename: The filename to write to, <in_filename>.html by default

create_app

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, text=None)
  • path: The filename to render, or the directory containing your Readme file, defaulting to the current working directory
  • gfm: Whether to render using GitHub Flavored Markdown
  • context: The project context to use when gfm is true, which takes the form of username/project
  • username: The user to authenticate with GitHub to extend the API limit
  • password: The password to authenticate with GitHub to extend the API limit
  • render_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 with gfm)
  • render_inline: Whether to inline the styles within the HTML file
  • text: A string or stream of Markdown text to render instead of being loaded from path (Note: path can be used to set the page title)

render_app

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 render
  • route: The route to render, '/' by default

render_content

Renders the specified markdown text without caching.

render_content(text, gfm=False, context=None, username=None, password=None, render_offline=False)
  • text: The Markdown text to render
  • gfm: Whether to render using GitHub Flavored Markdown
  • context: The project context to use when gfm is true, which takes the form of username/project
  • username: The user to authenticate with GitHub to extend the API limit
  • password: The password to authenticate with GitHub to extend the API limit
  • render_offline: Whether to render locally using Python-Markdown (Note: this is a work in progress)

render_page

Renders the markdown from the specified path or text, without caching, and returns an HTML page that resembles the GitHub Readme view.

render_page(page=None, gfm=False, context=None, username=None, password=None, render_offline=False, render_wide=False, render_inline=False, text=None)
  • path: The path to use for the page title, rendering 'README.md' if None
  • gfm: Whether to render using GitHub Flavored Markdown
  • context: The project context to use when gfm is true, which takes the form of username/project
  • username: The user to authenticate with GitHub to extend the API limit
  • password: The password to authenticate with GitHub to extend the API limit
  • render_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 with gfm)
  • render_inline: Whether to inline the styles within the HTML file
  • text: A string or stream of Markdown text to render instead of being loaded from path (Note: path can be used to set the page title)

resolve_readme

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 directory
  • force: Whether to force a result, even when a readme file is not found

clear_cache

Clears the cached styles and assets.

clear_cache()

supported_extensions

The supported extensions, as defined by GitHub.

supported_extensions = ['.md', '.markdown']

default_filenames

This constant contains the names Grip looks for when no file is provided.

default_filenames = map(lambda ext: 'README' + ext, supported_extensions)

Contributing

  1. Check the open issues or open a new issue to start a discussion around your feature idea or the bug you found
  2. Fork the repository, make your changes, and add yourself to Authors.md
  3. Send a pull request

If your PR has been waiting a while, feel free to ping me on Twitter.

Use this software often? Please consider supporting me on Gittip

import grip
grip.export(render_inline=False)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment