diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8968d1e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+__pycache__
+lazy_table.egg-info
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f3c4cc5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2021 Parsiad Azimzadeh
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..cfa4a66
--- /dev/null
+++ b/README.md
@@ -0,0 +1,68 @@
+
+
+
+
+A [python-tabulate](https://github.com/astanin/python-tabulate) wrapper for producing tables from generators.
+
+## Motivation
+
+lazy_table is useful when (*i*) each row of your table is generated by a possibly expensive computation and (*ii*) you want to print rows to the screen as soon as they are available.
+
+For example, the rows in the table below correspond to [numerically solving an ordinary differential equation](https://en.wikipedia.org/wiki/Numerical_methods_for_ordinary_differential_equations) with progressively more steps. See [examples/ode.py](https://raw.githubusercontent.com/parsiad/lazy-table/master/examples/ode.py) for the code to generate this table.
+
+
+
+## Installation and usage
+
+Install lazy_table via pip:
+
+```console
+$ pip install lazy_table
+```
+
+In your Python code, add
+
+```python
+import lazy_table as lt
+```
+
+Now, generating your own lazy table is as simple as calling `lt.stream` on a generator that yields one or more lists, where each list is a row of your table (see the example below).
+
+## Example
+
+As a toy example, consider printing the first 10 [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number) in a table.
+Since this is a relatively cheap operation, we will pretend it is expensive by adding an artificial `sleep(1)` call beteween Fibonacci numbers.
+
+```python
+import time
+import lazy_table as lt
+
+def fib_table(n):
+ x0, x1 = 0, 1
+ yield [0, x0]
+ yield [1, x1]
+ for i in range(2, n + 1):
+ x0, x1 = x1, x0 + x1
+ yield [i, x1]
+ time.sleep(1) # Simulate work
+
+lt.stream(fib_table(10), headers=['N', 'F_N'])
+```
+
+Your final table should look like this:
+
+```
+ N F_N
+--- -----
+ 0 0
+ 1 1
+ 2 1
+ 3 2
+ 4 3
+ 5 5
+ 6 8
+ 7 13
+ 8 21
+ 9 34
+ 10 55
+```
diff --git a/examples/ode.gif b/examples/ode.gif
new file mode 100755
index 0000000..754bda2
Binary files /dev/null and b/examples/ode.gif differ
diff --git a/examples/ode.py b/examples/ode.py
new file mode 100755
index 0000000..5c8775f
--- /dev/null
+++ b/examples/ode.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+
+import lazy_table as lt
+import numpy as np
+
+
+def f(y):
+ return np.array([y[1], (1. - y[0]**2) * y[1] - y[0]])
+
+
+def solve(n_steps, t1=16., y0=(2., 0.)):
+ h = t1 / n_steps
+ y = np.array(y0)
+ for _ in range(n_steps):
+ y = y + h * f(y)
+ return y
+
+
+def convergence_table(init_n_steps=200):
+ prev_value = np.nan
+ prev_delta = np.nan
+ for n_steps in init_n_steps * 2**np.arange(0, 12):
+ y = solve(n_steps)
+ value = y[0]
+ delta = value - prev_value
+ ratio = prev_delta / delta
+ order = np.log2(ratio)
+ prev_value = value
+ prev_delta = delta
+ yield [n_steps, value, delta, order]
+
+
+lt.stream(
+ convergence_table(),
+ tablefmt='simple',
+ headers=['Steps', 'Value', 'Delta', 'Order'],
+)
diff --git a/logo.png b/logo.png
new file mode 100644
index 0000000..885d22b
Binary files /dev/null and b/logo.png differ
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..374b58c
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,6 @@
+[build-system]
+requires = [
+ "setuptools>=42",
+ "wheel"
+]
+build-backend = "setuptools.build_meta"
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..ce94745
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,26 @@
+[metadata]
+name = lazy-table
+version = 0.1.0
+author = Parsiad Azimzadeh
+author_email = parsiad.azimzadeh@gmail.com
+description = A python-tabulate wrapper for producing tables from generators
+long_description = file: README.md
+long_description_content_type = text/markdown
+url = https://github.com/parsiad/lazy-table
+project_urls =
+ Bug Tracker = https://github.com/parsiad/lazy-table/issues
+classifiers =
+ Programming Language :: Python :: 3
+ License :: OSI Approved :: MIT License
+ Operating System :: OS Independent
+
+[options]
+install_requires =
+ tabulate
+package_dir =
+ = src
+packages = find:
+python_requires = >= 3.6
+
+[options.packages.find]
+where = src
diff --git a/src/lazy_table/__init__.py b/src/lazy_table/__init__.py
new file mode 100644
index 0000000..dbe5b05
--- /dev/null
+++ b/src/lazy_table/__init__.py
@@ -0,0 +1,5 @@
+"""A python-tabulate wrapper for producing tables from generators."""
+
+__all__ = ['ConsoleArtist', 'stream']
+
+from ._lazy_table import ConsoleArtist, stream
diff --git a/src/lazy_table/_lazy_table.py b/src/lazy_table/_lazy_table.py
new file mode 100644
index 0000000..19fc3d5
--- /dev/null
+++ b/src/lazy_table/_lazy_table.py
@@ -0,0 +1,67 @@
+import sys
+
+from tabulate import tabulate
+
+
+class ConsoleArtist:
+ """Renders a table to the console.
+
+ Parameters
+ ----------
+ clear : bool
+ Clears the screen every time the table is updated.
+ out : TextIOBase
+ Text stream to write to. If unspecified, ``sys.stdout`` is used.
+ """
+ def __init__(self, clear=False, out=sys.stdout):
+ self._clear = clear
+ self._n_lines = 0
+ self._out = out
+
+ def __call__(self, result):
+ if self._clear:
+ self._out.write('\x1b[2J')
+ else:
+ self._out.write('\033[F\033[K' * self._n_lines)
+ self._n_lines = result.count('\n') + 1
+ self._out.write(result)
+ self._out.write('\n')
+ self._out.flush()
+
+
+def stream(table, artist=None, **kwargs):
+ """Streams a table.
+
+ kwargs are forwarded to tabulate.
+
+ Examples
+ --------
+ >>> import time
+ >>> import lazy_table as lt
+ >>>
+ >>> def fib_table(n):
+ ... x0, x1 = 0, 1
+ ... yield [0, x0]
+ ... yield [1, x1]
+ ... for i in range(2, n + 1):
+ ... x0, x1 = x1, x0 + x1
+ ... yield [i, x1]
+ ... time.sleep(1) # Simulate work
+ >>>
+ >>> lt.stream(fib_table(10), headers=['N', 'F_N'])
+
+ Parameters
+ ----------
+ table : Generator[List[T], None, None]
+ A generator which yields rows of the table.
+ artist : callable, optional
+ A callable of the form ``draw(string)`` which determines how to render the table. If unspecified,
+ ``lazy_table.Console`` is used.
+ """
+ if artist is None:
+ artist = ConsoleArtist()
+ rows = []
+ for row in table:
+ rows.append(row)
+ t = tabulate(rows, **kwargs)
+ artist(t)