diff --git a/.gitignore b/.gitignore
index 0df0a146..dd1868e0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@ tox.ini
/.vscode
.eggs
*.code-workspace
+dragonfly.log*
diff --git a/README.md b/README.md
index 6af2bd95..b54fcffe 100644
--- a/README.md
+++ b/README.md
@@ -10,16 +10,24 @@
Dragonfly is collection of libraries to model and analyze urban climate, energy use, and daylight.
It extends the capabilites of [honeybee-core](https://github.com/ladybug-tools/honeybee-core) for the urban scale.
+This repository is the core repository that provides dragonfly's common functionalities.
+To extend these functionalities you should install available Dragonfly extensions or write
+your own.
+
+Here are a number of frequently used extensions for Dragonfly:
+- [dragonfly-energy](https://github.com/ladybug-tools/dragonfly-energy): Adds Energy simulation to Dragonfly.
+
+
## Installation
-```console
-pip install dragonfly-core
-```
-## QuickStart
-```python
-import dragonfly
+`pip install -U dragonfly-core`
-```
+If you want to also include the command line interface try:
+
+`pip install -U dragonfly-core[cli]`
+
+To check if Dragonfly command line is installed correctly try `dragonfly viz` and you
+should get a `viiiiiiiiiiiiizzzzzzzzz!` back in response! :bee:
## [API Documentation](https://www.ladybug.tools/dragonfly-core/docs/)
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 5a5fd4e2..8b63345f 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -2,6 +2,7 @@ coverage==4.5.2
coveralls==1.5.1
pytest==4.1.0
pytest-cov==2.6.1
+click>=5.1
Sphinx==1.8.5
sphinx-bootstrap-theme==0.6.5
sphinxcontrib-fulltoc==1.2.0
diff --git a/docs/cli.rst b/docs/cli.rst
new file mode 100644
index 00000000..09f100e0
--- /dev/null
+++ b/docs/cli.rst
@@ -0,0 +1,6 @@
+dragonfly command line interface
+===============================
+
+.. click:: dragonfly.cli:main
+ :prog: dragonfly
+ :show-nested:
diff --git a/docs/conf.py b/docs/conf.py
index cc04e76b..78b76fa1 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -16,7 +16,7 @@
import sys
import datetime
now = datetime.datetime.now()
-sys.path.insert(0, os.path.abspath('../'))
+sys.path.insert(0, os.path.abspath('..'))
# -- Project information -----------------------------------------------------
@@ -49,7 +49,8 @@
'sphinx.ext.viewcode',
'sphinx.ext.githubpages',
'sphinxcontrib.fulltoc',
- 'sphinx.ext.napoleon'
+ 'sphinx.ext.napoleon',
+ 'sphinx_click.ext'
]
# Add any paths that contain templates here, relative to this directory.
diff --git a/docs/index.rst b/docs/index.rst
index a9822be4..9f1abb6e 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,11 +1,43 @@
-Welcome to dragonfly-core's documentation!
-===================================
+Welcome to Dragonfly's documentation!
+=========================================
+
+.. image:: http://www.ladybug.tools/assets/img/dragonfly.png
+
+Dragonfly is collection of libraries to model and analyze urban climate, energy
+use, and daylight. It extends the capabilites of
+`honeybee-core `_ for the urban scale.
+
+
+Installation
+============
+
+To install the core library try ``pip install -U dragonfly-core``.
+
+If you want to also include the command line interface try ``pip install -U dragonfly-core[cli]``.
+
+To check if the Dragonfly command line is installed correctly try ``dragonfly viz`` and you
+should get a ``viiiiiiiiiiiiizzzzzzzzz!`` back in response!
+
+
+Documentation
+=============
+
+This document includes `Dragonfly API documentation <#dragonfly>`_ and
+`Dragonfly Command Line Interface <#id1>`_ documentation for ``dragonfly core`` and does
+not include the documentation for dragonfly extensions. For each extension refer to
+extension's documentation page.
+
+Here are a number of popular Dragonfly extensions:
+
+- `dragonfly-energy `_
+
.. toctree::
:maxdepth: 2
:caption: Contents:
.. include:: modules.rst
+.. include:: cli.rst
Indices and tables
diff --git a/dragonfly/__main__.py b/dragonfly/__main__.py
new file mode 100644
index 00000000..b7574377
--- /dev/null
+++ b/dragonfly/__main__.py
@@ -0,0 +1,4 @@
+from dragonfly.cli import main
+
+if __name__ == '__main__':
+ main()
diff --git a/dragonfly/cli/__init__.py b/dragonfly/cli/__init__.py
new file mode 100644
index 00000000..6dc56280
--- /dev/null
+++ b/dragonfly/cli/__init__.py
@@ -0,0 +1,82 @@
+"""
+Command Line Interface (CLI) entry point for dragonfly and dragonfly extensions.
+
+Use this file only to add commands related to dragonfly-core. For adding extra commands
+from each extention see below.
+
+Dragonfly is using click (https://click.palletsprojects.com/en/7.x/) for creating the CLI.
+You can extend the command line interface from inside each extention by following these
+steps:
+
+1. Create a ``cli`` folder in your extension.
+2. Import the ``main`` function from this ``dragonfly.cli``.
+3. Add your commands and command groups to main using add_command method.
+4. Add ``import [your-extention].cli`` to ``__init__.py`` file to the commands are added
+ to the cli when the module is loaded.
+
+Good practice is to group all your extention commands in a command group named after
+the extension. This will make the commands organized under extension namespace. For
+instance commands for `dragonfly-energy` will be called like ``dragonfly energy [energy-command]``.
+
+
+.. code-block:: python
+
+ import click
+ from dragonfly.cli import main
+
+ @click.group()
+ def energy():
+ pass
+
+ # add commands to energy group
+ @energy.command('to-idf')
+ # ...
+ def to_idf():
+ pass
+
+ # finally add the newly created commands to dragonfly cli
+ main.add_command(energy)
+
+ # do not forget to import this module in __init__.py otherwise it will not be added
+ # to dragonfly commands.
+
+Note:
+ For extension with several commands you can use a folder structure instead of a single
+ file.
+"""
+
+try:
+ import click
+except ImportError:
+ raise ImportError(
+ 'click module is not installed. Try `pip install dragonfly-core[cli]` command.'
+ )
+
+from dragonfly.cli.validate import validate
+
+import sys
+import os
+import logging
+import json
+
+
+_logger = logging.getLogger(__name__)
+
+
+@click.group()
+@click.version_option()
+def main():
+ pass
+
+
+@main.command('viz')
+def viz():
+ """Check if dragonfly is flying!"""
+ click.echo('viiiiiiiiiiiiizzzzzzzzz!')
+
+
+main.add_command(validate)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/dragonfly/cli/validate.py b/dragonfly/cli/validate.py
new file mode 100644
index 00000000..530caccc
--- /dev/null
+++ b/dragonfly/cli/validate.py
@@ -0,0 +1,56 @@
+"""dragonfly validation commands."""
+
+try:
+ import click
+except ImportError:
+ raise ImportError(
+ 'click is not installed. Try `pip install . [cli]` command.'
+ )
+
+from dragonfly.model import Model
+
+import sys
+import os
+import logging
+import json
+
+_logger = logging.getLogger(__name__)
+
+try:
+ import dragonfly_schema.model as schema_model
+except ImportError:
+ _logger.exception(
+ 'dragonfly_schema is not installed. Try `pip install . [cli]` command.'
+ )
+
+
+@click.group(help='Commands for validating Dragonfly JSON files.')
+def validate():
+ pass
+
+@validate.command('model')
+@click.argument('model-json')
+def validate_model(model_json):
+ """Validate a Model JSON file against the Dragonfly schema.
+ \b
+ Args:
+ model_json: Full path to a Model JSON file.
+ """
+ try:
+ assert os.path.isfile(model_json), 'No JSON file found at {}.'.format(model_json)
+
+ # validate the Model JSON
+ click.echo('Validating Model JSON ...')
+ schema_model.Model.parse_file(model_json)
+ click.echo('Pydantic validation passed.')
+ with open(model_json) as json_file:
+ data = json.load(json_file)
+ parsed_model = Model.from_dict(data)
+ parsed_model.check_missing_adjacencies(raise_exception=True)
+ click.echo('Python re-serialization passed.')
+ click.echo('Congratulations! Yout Model JSON is valid!')
+ except Exception as e:
+ _logger.exception('Model validation failed.\n{}'.format(e))
+ sys.exit(1)
+ else:
+ sys.exit(0)
diff --git a/setup.py b/setup.py
index 8a4ede19..7d7f92af 100644
--- a/setup.py
+++ b/setup.py
@@ -10,7 +10,7 @@
setuptools.setup(
name="dragonfly-core",
- use_scm_version = True,
+ use_scm_version=True,
setup_requires=['setuptools_scm'],
author="Ladybug Tools",
author_email="info@ladybug.tools",
@@ -20,6 +20,12 @@
url="https://github.com/ladybug-tools/dragonfly-core",
packages=setuptools.find_packages(exclude=["tests"]),
install_requires=requirements,
+ extra_requires={
+ 'cli': ['click>=5.1', 'dragonfly-schema>=1.2.0']
+ },
+ entry_points={
+ "console_scripts": ["dragonfly = dragonfly.cli:main"]
+ },
classifiers=[
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.6",
diff --git a/tests/cli_test.py b/tests/cli_test.py
new file mode 100644
index 00000000..7febfeae
--- /dev/null
+++ b/tests/cli_test.py
@@ -0,0 +1,12 @@
+"""Test cli."""
+
+from click.testing import CliRunner
+from dragonfly.cli import viz
+
+
+def test_viz():
+ runner = CliRunner()
+ result = runner.invoke(viz)
+ assert result.exit_code == 0
+ assert result.output.startswith('vi')
+ assert result.output.endswith('z!\n')