diff --git a/README.md b/README.md
index c2007dbc..b365109a 100644
--- a/README.md
+++ b/README.md
@@ -1,72 +1,111 @@
-# Spiff Workflow
+## SpiffWorkflow
+Spiff Workflow is a workflow engine implemented in pure Python. It is based on
+the excellent work of the Workflow Patterns initiative. In 2020 and 2021,
+extensive support was added for BPMN / DMN processing.
+## Motivation
+We created SpiffWorkflow to support the development of low-code business
+applications in Python. Using BPMN will allow non-developers to describe
+complex workflow processes in a visual diagram, coupled with a powerful python
+script engine that works seamlessly within the diagrams. SpiffWorkflow can parse
+these diagrams and execute them. The ability for businesses to create
+clear, coherent diagrams that drive an application has far reaching potential.
+While multiple tools exist for doing this in Java, we believe that wide
+adoption of the Python Language, and it's ease of use, create a winning
+strategy for building Low-Code applications.
+
+
+## Build status
[](https://travis-ci.org/sartography/SpiffWorkflow)
-[](https://coveralls.io/github/sartography/SpiffWorkflow?branch=master)
[](https://sonarcloud.io/dashboard?id=sartography_SpiffWorkflow)
+[](https://sonarcloud.io/dashboard?id=sartography_SpiffWorkflow)
+[](https://sonarcloud.io/dashboard?id=sartography_SpiffWorkflow)
[](http://spiffworkflow.readthedocs.io/en/latest/?badge=latest)
+[](https://github.com/sartography/SpiffWorkflow/issues)
+[](https://github.com/sartography/SpiffWorkflow/pulls)
-## Summary
+## Code style
-Spiff Workflow is a workflow engine implemented in pure Python. It is based
-on the excellent work of the
-[Workflow Patterns initiative](http://www.workflowpatterns.com/).
-In 2020 and 2021, extensive support was added for BPMN / DMN processing.
+[](https://www.python.org/dev/peps/pep-0008/)
-## Do you need commercial support?
+## Screenshots
-Spiff Workflow is now supported by [Sartography](https://sartography.com).
-We are working to build out a collection of open source projects, including
-a REST based API and frontend Javascript libraries that make building applications
-with SpiffWorkflow easier than ever.
-## Main design goals
-- Spiff Workflow aims to directly support as many of the patterns of
- workflowpatterns.com as possible.
-- Spiff Workflow uses unit testing as much as possible.
-- Spiff Workflow provides a **clean Python API**.
-- Spiff Workflow allows for mapping patterns into workflow elements that
- are **easy to understand for non-technical users** in a workflow GUI editor.
-- Spiff Workflow implements the best possible **path prediction** for
- workflows.
+## Dependencies
+We've worked to minimize external dependencies. We rely on lxml for parsing
+XML Documents, and there is some legacy support for Celery, but it is not
+core to the implementation, it is just a way to interconnect these systems.
+Built with
+- [lxml](https://lxml.de/)
+- [celery](https://docs.celeryproject.org/en/stable/)
-Spiff Workflow also provides a parser and workflow emulation
-layer that can be used to create executable Spiff Workflow specifications
-from Business Process Model and Notation (BPMN) documents.
+## Features
+* __BPMN__ - support for parsing BPMN diagrams, including the more complex
+components, like pools and lanes, multi-instance tasks, sub-workflows, timer
+events, signals, messages, boudary events and looping.
+* __DMN__ - We have a baseline implementation of DMN that is well integrated
+with our Python Execution Engine.
+* __Forms__ - forms, including text fields, selection lists, and most every other
+thing you can be extracted from the Camunda xml extension, and returned as
+json data that can be used to generate forms on the command line, or in web
+applications (we've used Formly to good success)
+* __Python Workflows__ - We've retained support for building workflows directly
+in code, or running workflows based on a internal json data structure.
-## Quick Intro
+_A complete list of the latest features is available with our [release notes](https://github.com/sartography/SpiffWorkflow/releases/tag/1.0) for
+version 1.0._
-The process of using Spiff Workflow involves the following steps:
+## Code Examples and Documentation
+Detailed documentation is available on [ReadTheDocs](https://spiffworkflow.readthedocs.io/en/latest/)
+Also, checkout our [example application](https://github.com/sartography/SpiffExample), which we
+reference extensively from the Documentation.
-1. Write a workflow specification. A specification may be written using
- XML ([example](https://github.com/knipknap/SpiffWorkflow/blob/master/tests/SpiffWorkflow/data/spiff/workflow1.xml)),
- JSON, or
- Python ([example](https://github.com/knipknap/SpiffWorkflow/blob/master/tests/SpiffWorkflow/data/spiff/workflow1.py)).
-2. Run the workflow using the Python API. Example code for running the workflow:
-
-```python
-from SpiffWorkflow.specs import WorkflowSpec
-from SpiffWorkflow.serializer.prettyxml import XmlSerializer
-from SpiffWorkflow import Workflow
-
-# Load the workflow specification:
-with open('my_workflow.xml') as fp:
- serializer = XmlSerializer()
- spec = WorkflowSpec.deserialize(serializer, fp.read())
-
-# Create an instance of the workflow, according to the specification.
-wf = Workflow(spec)
-
-# Complete tasks as desired. It is the job of the workflow engine to
-# guarantee a consistent state of the workflow.
-wf.complete_task_from_id(...)
-
-# Of course, you can also persist the workflow instance:
-xml = wf.serialize(XmlSerializer, 'workflow_state.xml')
+## Installation
+```
+pip install spiffworkflow
```
-## Documentation
+## Tests
+```
+cd tests
+./run_suite.sh
+```
-Full documentation is here:
+## Contribute
+Pull Requests are and always will be welcome!
- http://spiffworkflow.readthedocs.io/en/latest/
+Please check your formatting, assure that all tests are passing, and include
+any additional tests that can demonstrate the new code you created is working
+as expected. If applicable, please reference the issue number in your pull
+request.
+
+## Credits and Thanks
+Special Thanks:
+Sartography (Sartography.com) undertook these efforts to support the development
+of a custom web application for the University of Virginia. We would like to
+thank UVA for their support and trust in allowing us to take on the mammoth
+task of building a general-purpose workflows system, and contributing something
+back to the open source community.
+
+Bruce Silver, the author of BPMN Quick and Easy Using Method and Style, whose
+work we referenced extensively as we made implementation decisions and
+educated ourselves on the BPMN and DMN standards.
+
+Samuel Abels (@knipknap) for keeping SpiffWorkflow alive for the past few years,
+and offering us commit access to make these contributions, and Matthew Hampton
+for kicking this project off and giving us sound footing on which to build.
+
+The BPMN.js library, without which we would not have the tools to effectively
+build out our models, embed an editor in our application, and pull this mad
+mess together.
+
+Kelly McDonald (@w4kpm) who dove deeper into the core of SpiffWorkflow than
+anyone else, and was instrumental in helping us get some of these major
+enhancements working correctly.
+
+We would like to thank Denny Weinberg for his early contributions to DMN
+support, which we used as a baseline and then extended.
+
+## License
+GNU LESSER GENERAL PUBLIC LICENSE
diff --git a/doc/basics.rst b/doc/basics.rst
index b096a4ce..6f566345 100644
--- a/doc/basics.rst
+++ b/doc/basics.rst
@@ -1,23 +1,22 @@
-Basics
-======
+The Basics
+==========
Introduction
------------
The process of using Spiff Workflow involves the following steps:
-#. Write a workflow specification. A specification may be written using XML
- (`example `_),
+#. Create a workflow specification. A specification may be written using BPMN,
JSON, or Python
(`example `_).
#. Run the workflow using the Python API. Example code for running the workflow::
from SpiffWorkflow.specs import *
from SpiffWorkflow import Workflow
-
+
spec = WorkflowSpec()
- # (Add tasks to the spec here.)
-
+ # (Add tasks to the spec here, or create one directly from an existing file)
+
wf = Workflow(spec)
wf.complete_task_from_id(...)
@@ -95,9 +94,9 @@ Running a workflow
To run the workflow, create an instance of the *Workflow* class as follows::
from SpiffWorkflow import Workflow
-
+
spec = ... # see above
-
+
wf = Workflow(spec)
...
@@ -133,7 +132,7 @@ The order of these state transitions is violated only in one case: A *Trigger* t
- **READY** means "the preconditions for marking this task as complete are met".
- **COMPLETED** means that the task is done.
-
+
- **CANCELLED** means that the task was explicitly cancelled, for example by a CancelTask operation.
Associating data with a workflow
diff --git a/doc/bpmn.rst b/doc/bpmn.rst
deleted file mode 100644
index 7fa5560b..00000000
--- a/doc/bpmn.rst
+++ /dev/null
@@ -1,140 +0,0 @@
-.. _bpmn_page:
-
-Business Process Model and Notation (BPMN)
-==========================================
-
-Business Process Model and Notation (BPMN) is a standard for business process modeling that
-provides a graphical notation for specifying business processes, based on a flowcharting technique.
-The objective of BPMN is to support business process management, for both technical users and business users,
-by providing a notation that is intuitive to business users, yet able to represent complex
-process semantics. The BPMN specification also provides a standard XML serialization format, which
-is what Spiff Workflow parses.
-
-A reasonable subset of the BPMN notation is supported, including the following elements:
-
- 1. Call Activity
- 2. Start Event
- 3. End Event (including interrupting)
- 4. User and Manual Tasks
- 5. Script Task
- 6. Exclusive Gateway
- 7. Inclusive Gateway (converging only)
- 8. Parallel Gateway
- 9. MultiInstance & Variants
- 10. Intermediate Catch Events (Timer and Message)
- 11. Boundary Events (Timer and Message, interrupting and non-interrupting)
-
-.. figure:: figures/action-management.png
- :alt: Example BPMN Workflow
-
- Example BPMN Workflow
-
-Please refer to http://www.bpmn.org/ for details on BPMN and to the API documentation for instructions on the
-use of the BPMN implementation.
-
-MultiInstance Notes
--------------------
-
-A subset of MultiInstance and Looping Tasks are supported. Notably,
-the completion condition is not currently supported.
-
-The following definitions should prove helpful
-
-**loopCardinality** - This variable can be a text representation of a
-number - for example '2' or it can be the name of a variable in
-task.data that resolves to a text representation of a number.
-It can also be a collection such as a list or a dictionary. In the
-case that it is a list, the loop cardinality is equal to the length of
-the list and in the case of a dictionary, it is equal to the list of
-the keys of the dictionary.
-
-If loopCardinality is left blank and the Collection is defined, or if
-loopCardinality and Collection are the same collection, then the
-Multiinstnace will loop over the collection and update each element of
-that collection with the new information. In this case, it is assumed
-that the incoming collection is a dictionary, currently behavior for
-working with a list in this manner is not defined and will raise an error.
-
-**Collection** This is the name of the collection that is created from
-the data generated when the task is run. Examples of this would be
-form data that is generated from a UserTask or data that is generated
-from a script that is run. Currently the collection is built up to be
-a dictionary with a numeric key that corresponds to the place in the
-loopCardinality. For example, if we set the loopCardinality to be a
-list such as ['a','b','c] the resulting collection would be {1:'result
-from a',2:'result from b',3:'result from c'} - and this would be true
-even if it is a parallel MultiInstance where it was filled out in a
-different order.
-
-**Element Variable** This is the variable name for the current
-iteration of the MultiInstance. In the case of the loopCardinality
-being just a number, this would be 1,2,3, . . . If the
-loopCardinality variable is mapped to a collection it would be either
-the list value from that position, or it would be the value from the
-dictionary where the keys are in sorted order. It is the content of the
-element variable that should be updated in the task.data. This content
-will then be added to the collection each time the task is completed.
-
-Example:
- In a sequential MultiInstance, loop cardinality is ['a','b','c'] and elementVariable is 'myvar'
- then in the case of a sequential multiinstance the first call would
- have 'myvar':'a' in the first run of the task and 'myvar':'b' in the
- second.
-
-Example:
- In a Parallel MultiInstance, Loop cardinality is a variable that contains
- {'a':'A','b':'B','c':'C'} and elementVariable is 'myvar' - when the multiinstance is ready, there
- will be 3 tasks. If we choose the second task, the task.data will
- contain 'myvar':'B'.
-
-Updating Data
-------------
-
-While there may be some MultiInstances that will not result in any
-data, most of the time there will be some kind of data generated that
-will be collected from the MultiInstance. A good example of this is a
-UserTask that has an associated form or a script that will do a lookup
-on a variable.
-
-Each time the MultiInstance task generates data, the method
-task.update_data(data) should be called where data is the data
-generated. The 'data' variable that is passed in is assumed to be a
-dictionary that contains the element variable. Calling task.update_data(...)
-will ensure that the MultiInstance gets the correct data to include in the
-collection. The task.data is also updated with the dictionary passed to
-this method.
-
-Example:
- In a Parallel MultiInstance, Loop cardinality is a variable that contains
- {'a':'A','b':'B','c':'C'} and elementVariable is 'myvar'.
- If we choose the second task, the task.data will contain 'myvar':{'b':'B'}.
- If we wish to update the data, we would call task.update_data('myvar':{'b':'B2'})
- When the task is completed, the task.data will now contain:
- {'a':'A','b':'B2','c':'C'}
-
-Looping Tasks
--------------
-
-A looping task sets the cardinality to 25 which is assumed to be a
-sane maximum value. The looping task will add to the collection each
-time it is processed assuming data is updated as outlined in the
-previous paragraph.
-
-To halt the looping the task.terminate_loop()
-
-Each time task.complete() is called (or
-workflow.complete_task_by_id(task.id) ), the task will again present
-as READY until either the cardinality is exausted, or
-task.terminate_loop() is called.
-
-**Caveats**
------------
-
-At the current time a sequential MultiInstance behaves more like a
-Looping Task than a MultiInstance - A true MultiInstace would actually
-create multiple copies of the task in the task tree - currently only
-one task is created and it is repeated the number of the
-loopCardinality
-
-
-
diff --git a/doc/bpmn/Makefile b/doc/bpmn/Makefile
new file mode 100644
index 00000000..391c73bf
--- /dev/null
+++ b/doc/bpmn/Makefile
@@ -0,0 +1,25 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = .
+BUILDDIR = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+.PHONY: apidoc
+apidoc:
+ sphinx-apidoc -d5 -Mefo . ../venv/lib/python3.7/site-packages/SpiffWorkflow
+
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/doc/bpmn/ScriptingEnhancements.rst b/doc/bpmn/ScriptingEnhancements.rst
new file mode 100644
index 00000000..0084441a
--- /dev/null
+++ b/doc/bpmn/ScriptingEnhancements.rst
@@ -0,0 +1,63 @@
+Scripting Enhancements
+===================================
+
+SpiffWorkflow is really flexible, and allows us to update and alter a workflow to adjust to the changing business
+needs of your application. While this is great and leads to faster time to market,
+SpiffWorkflow can only go so far. When you are implementing a 'real' project, it is likely you will need to be able
+to use your favorite library, use your own python functions or inject variables into a scripting environment.
+
+Luckily, SpiffWorkflow is able to do this with minor modifications.
+
+Lets take a look at a very simple workflow that needs to use a custom function.
+
+.. figure:: images/CustomScript.png
+ :align: center
+
+
+ custom script task
+
+Here we can see that we have created a workflow that is calling a function 'my_custom_function' in a ScriptTask
+
+Lets take a look at the code in ExampleCode-custom-script.py, and how it differs from our original code ExampleCode.py
+
+We have added the following:
+
+.. code:: python
+ :number-lines: 2
+
+ from SpiffWorkflow.bpmn.BpmnScriptEngine import BpmnScriptEngine
+
+this imports the default script engine so that we can make changes to it, and then we define our custom function.
+Note that the name is not my_custom_function, we control that when we update the scripting engine
+
+.. code:: python
+ :number-lines: 6
+
+ def testScript(input):
+ print('hello from testScript with input %s'%input)
+
+
+finally, we need to make some changes to how we load our workflow. Of course we need to alter our workflow file name
+and the id of the workflow, but we are also making an instance of the scripting engine with the additions we would
+like to have in our script, and passing this into the workflow as we create it.
+
+.. code:: python
+ :number-lines: 27
+
+ x = CamundaParser()
+ x.add_bpmn_file('custom_script.bpmn')
+ spec = x.get_spec('custom_script')
+
+ script_engine = BpmnScriptEngine(scriptingAdditions={'my_custom_function':testScript})
+ workflow = BpmnWorkflow(spec,script_engine=script_engine)
+
+When we run the workflow we get a very simple output
+
+.. code:: bash
+
+ my-prompt$ python ExampleCode-custom-script.py
+ hello from testScript with input test
+ {}
+
+This should open a whole world of possibilities and allow for as much customization as needed for your application.
+
diff --git a/doc/bpmn/advanced_features.rst b/doc/bpmn/advanced_features.rst
new file mode 100644
index 00000000..155e55f5
--- /dev/null
+++ b/doc/bpmn/advanced_features.rst
@@ -0,0 +1,90 @@
+Advanced Features
+===================================
+
+Our discussion of some of the more advanced features currently available in SpiffWorkflow will revolve around the
+following workflow
+
+.. image:: images/MessageBoundary.png
+
+which can be found in the MessageBoundary.bpmn file.
+
+In this workflow are several new concepts:
+
+* lanes
+* subprocesses
+* message events
+* time events
+* boundary events
+
+Lanes
+-------------
+
+Lanes are a method in BPMN to distinguish roles for the workflow and who is
+responsible for which actions. In some cases this will be different business
+units, and in some cases this will be different individuals - it really depends
+on the nature of the workflow. Within a BPMN editor, this is done by choosing the
+'Create pool/participant' option from the toolbar on the left hand side.
+
+Subprocesses
+-------------
+
+Subprocesses come in two different flavors, in this workflow we see an 'expanded' subprocess. The other option is an
+external subprocess. In general, subprocesses are a way of grouping work into smaller units. This, in theory, will
+help us to re-use sections of business logic, but it will also allow us to treat groups of work as a unit.
+
+Unfortunately, we can't collapse an expanded subprocess within BPMN.js, so really the only purpose of an expanded
+subprocess is to do the third thing, that is to treat a group of work as a unit. This is what we have done in our
+example workflow. We may want to interrupt the hard work that is going on from our worker and this is accomplished by
+a boundary event that will be covered later.
+
+In addition to expanded subprocess, we can have external subprocesses in what is known as a 'CallActivity'. This call
+activity is added as a normal task and then the type selected in the configuration menu. This allows us to 'call' a
+separate workflow in a different file by referencing the ID of the called workflow. This effectively allows us to
+simplify and re-use business logic.
+
+Messaging
+----------
+
+BPMN.js allows us to set up message events. These events are only usable within the workflow and may not cross
+workflows (even CallActivities). If any kind of cross-workflow coordination is needed, this will need to be handled
+through a script activity.
+
+.. sidebar:: TODO
+
+ This may technically not be true - BPMN.js allows you to define a catch event for a message name that you can type
+ into the message bar - so . . . we may be able to use it cross workflows - I have created a task for looking into
+ this.
+
+Messages allow us to control some workflow events. In the given example, if the boss says to interrupt work, a
+message is sent which is later 'caught' by the catch message event.
+
+There are a few ways to use a catch event, one is to use a start/catch event which starts a lane when a message is fired, another way is to have an in-line message catch event which stops the workflow until a message is caught, and the third is a boundary event (covered below).
+
+Time Event
+-------------
+
+A time event allows us to create a pause in the workflow, either for a duration or for a specified date/time. A time event can be used in a similar fashion to the message events, that is they can be used as a delayed start event, as an interrupt to the workflow, or as a boundary event
+
+Boundary Events
+----------------
+
+Boundary events are a way of stopping or interrupting a subprocess or a task. In the BPMN specification there are multiple events that are defined, but currently SpiffWorkflow only works with the Message and Timer events.
+
+Our example workflow has an example of where a message event gets thrown and then as a boundary event, it interrupts the process that it is assigned to. Timer events would interrupt the process at a specified time rather than when a 'Message Send' event gets thrown.
+
+Boundary events can also be non-interrupting.
+
+Process overview
+----------------
+
+With all of these individual concepts defined, we will now explain how they work in concert.
+
+In the MessageBoundary workflow, we have two lanes, one for the Boss and one for the Worker. Later on, we will see how we can request tasks for only the Boss or the Worker
+
+Once the workflow starts, the Boss is repeatedly asked if they would like to interrupt the work. Since the Boss has nothing better to do, they are happy to repeatedly answer this question!
+
+The worker alternates between doing work and taking a break - the break is only a few tenths of a second, and is represented by a 'timer event' showing that the workflow is stopped by the defined length of time. Because the worker is some kind of superhuman,they don't mind taking such a short break nor do they mind doing this forever, or until the boss says that they can stop. (Actually, the short break is because we actually use this workflow as a test for SpiffWorkflow, and we don't want to wait around on the tests forever)
+
+Once the boss says that 'yes' work should stop, there is a 'Message Throw event' that sends a message to the 'Catch Event'. Because the catch event is on the boundary of a subprocess, this neverending flow of work is interrupted and the entire workflow comes to an end. There are also 'non-interrupting' events that don't interrupt the subworkflow, but will allow for an exception, such as a reason why something was delayed.
+
+Because there are only ever tasks that are ready for the Boss, we can run this entire workflow using the exact same code as we have with our other examples. Next, we will discuss how we can get more information about where we are at in the workflow, and how to get a list of tasks that are ready for each participant in a pool.
diff --git a/doc/bpmn/call_activity.rst b/doc/bpmn/call_activity.rst
new file mode 100644
index 00000000..ea4e43ee
--- /dev/null
+++ b/doc/bpmn/call_activity.rst
@@ -0,0 +1,65 @@
+Call Activity
+===================================
+
+Our previous section hinted at a way of grouping tasks so that we can manage the complexity of workflows, and a
+mechanism for re-using workflows if we have a 'subroutine' that is somewhat complicated and we would like to be more
+efficient by making the workflow just once and then re-using it in many different locations.
+
+A CallActivity allows us to do this. Here are two different workflows - one for the top level and one for the bottom
+level.
+
+.. figure:: images/top_workflow.png
+ :scale: 50%
+ :align: center
+
+.. figure:: images/common_workflow.png
+ :scale: 50%
+ :align: center
+
+Essentially, we will add both bpmn documents in the code where we used to add just one. The first workflow will
+reference the second based upon its workflow ID.
+
+In the first figure, I have the CallActivity Task highlighted, and you can see in the configuration box on the right
+hand side that the call activity references the task ID from the subworkflow.
+
+.. figure:: images/topworkflowexample.png
+ :align: center
+
+And then in the second workflow, we can see that I've set the workflow id to be the same as I'm referencing in the
+first file. Again, to set the workflow id, we click on the background of the workflow and we can set it in the right
+hand panel. (please note, if we are using pools and lanes, this is slightly different, you will need to click on the
+pool and set it there)
+
+.. figure:: images/sublevelexample.png
+ :align: center
+
+This workflow, when run will simply perform the task activities in order, the script tasks all do what is printed in
+the task description, so we would expect it to
+
+ 1) print('task1')
+ 2) print('complicated common task')
+ 3) print('task2')
+
+I've put code for doing this in ExampleCode-Sub.py - and we can see that the only difference in the code is that we
+have changed the name of the files and the id of the starting activity.
+
+.. code:: python
+ :number-lines: 23
+
+ x = CamundaParser()
+ x.add_bpmn_file('top_workflow.bpmn')
+ x.add_bpmn_file('common_workflow.bpmn')
+
+ spec = x.get_spec('top_workflow')
+
+When we run this new code - we can see that it does exactly what we expect it to
+
+.. code::
+
+
+ python ExampleCode-Sub.py
+ task1
+ complicated common task
+ task2
+ {}
+
diff --git a/doc/bpmn/conf.py b/doc/bpmn/conf.py
new file mode 100755
index 00000000..996b2d8f
--- /dev/null
+++ b/doc/bpmn/conf.py
@@ -0,0 +1,60 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+numfig = True
+
+# -- Project information -----------------------------------------------------
+
+project = 'SpiffWorkflow-BPMN Documentation'
+copyright = '2020, Sartography'
+author = 'Sartography'
+
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+
+extensions = ['sphinx.ext.autodoc', # 'sphinx.ext.coverage',
+ 'sphinx.ext.viewcode',
+ 'sphinx.ext.autosummary',
+ #'sphinx.ext.intersphinx',
+ ]
+
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'default'
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+intersphinx_mapping = {'http://docs.python.org/': None}
diff --git a/doc/bpmn/dmn.rst b/doc/bpmn/dmn.rst
new file mode 100644
index 00000000..52713e34
--- /dev/null
+++ b/doc/bpmn/dmn.rst
@@ -0,0 +1,106 @@
+DMN and Decision Tables
+=======================
+
+What is DMN
+-----------
+
+Decision Model and Notation (DMN) is a standard for business decision modeling. DMN allows modelers to separate decision logic from process logic and maintain it in a table format. DMN is linked into BPMN with a *decision task*.
+
+
+BPMN Model With DMN
+--------------------------------
+
+With DMN, business analysts can model the rules that lead to a decision in an easy to read table. Those tables can be executed directly by SpiffWorkflow.
+
+This minimizes the risk of misunderstandings between business analysts and developers, and allows rapid changes in production.
+
+BPMN includes a decision task that refers to the decision table. The outcome of the decision lookup allows the next gateway or activity to route the flow.
+
+In the BPMN model below we build on our basic example. It includes a business rules task named *Make a decision*. This is where the decision table lookup is called on the BPMN side. The result of the table lookup is used by the *Where are we getting spam?* task.
+
+.. image:: images/decision_table.png
+
+
+Decision Table
+----------------------
+
+Here is the decision table for our *Make a decision* lookup.
+
+.. image:: images/dmn.png
+
+Based on their response to the *Location* question, we make a lookup in the table. We can then send them a message about where to get spam.
+
+If they go camping or stay in a cabin, we tell them to bring spam. If they stay in a hotel, we tell them they can buy it near the hotel.
+
+DMN tables also allow annotations that help explain entries in the table.
+
+
+Changes to ExampleCode.py
+-------------------------
+
+Up to this point, all of our BPMN models run using the code in ExampleCode.py. (We do need to updata the file and process names.)
+
+For DMN, we need to modify our code so that we can import a DMN table.
+
+This code is in Example-dmn.py.
+
+Below are the code changes that happened to make this happen
+
+We import a second parser.
+
+.. code:: python
+
+ from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser
+
+and create a class based on the new parser.
+
+.. code:: python
+
+ class MyCustomParser(BpmnDmnParser):
+ """
+ A BPMN and DMN parser that can also parse Camunda forms.
+ """
+ OVERRIDE_PARSER_CLASSES = BpmnDmnParser.OVERRIDE_PARSER_CLASSES
+ OVERRIDE_PARSER_CLASSES.update(CamundaParser.OVERRIDE_PARSER_CLASSES)
+
+This new class can parse both BPMN and DMN, along with the basic form elements
+supported in Camunda's free editor.
+
+To use the new class, we change
+
+.. code:: python
+
+ parser = MyCustomParser()
+ parser.add_bpmn_file('BasicExample.bpmn')
+ spec = parser.get_spec('BasicExample')
+
+to
+
+.. code:: python
+
+ parser = MyCustomParser()
+ parser.add_bpmn_file('decision_table.bpmn')
+ parser.add_dmn_file('spam_decision.dmn')
+ spec = parser.get_spec('step1')
+
+Note that we add both a bpmn file and a dmn file to the parser.
+
+.. sidebar:: TODO
+
+ This should really change - it seems really confusing to a person new to this as to why I should have to create a
+ custom class to do this.
+
+With this new code, we can run the DMN workflow.
+
+Here is some sample output from running Example-dmn.py.
+
+.. code:: bash
+
+ $ python ExampleCode-dmn.py
+ Where are you going? (Options: cabin, hotel, camping)? cabin
+ ['location']
+ Do you like spam? Yes
+ ['spam']
+ {'location': 'cabin', 'spam': 'Yes'}
+ Make sure to pack Spam!
+ {'location': 'cabin', 'spam': 'Yes', 'spampurchase': 'Make sure to pack Spam!'}
diff --git a/doc/bpmn/examples.rst b/doc/bpmn/examples.rst
new file mode 100644
index 00000000..36a35293
--- /dev/null
+++ b/doc/bpmn/examples.rst
@@ -0,0 +1,387 @@
+Basic Concepts, Example Code,and Diagrams
+=========================================
+
+The basic idea of SpiffWorkflow is that you can use it to write an interpreter
+in Python that creates business applications from BPMN models.
+
+In this section of the documentation, we introduce that process.
+
+.. sidebar:: Example Code
+
+ All the Python code and BPMN models used here are available in an example
+ project called `SpiffExample `_
+
+Basic Example
+--------------
+This model is found in BasicExample.bpmn.
+
+In this example, we examine one of the simplest BPMN workflows. There is a start event, an activity
+called Trip Info with a UserTask, and an end event.
+
+.. image:: images/basic_example.png
+ :scale: 25%
+ :align: center
+
+User tasks can include forms that ask the user questions. When you click on a
+user task in a BPMN modeler, the Properties Panel includes a form tab. Use this
+tab to build your questions.
+
+In this flow the user is answering two questions : where they are going, and whether they like spam.
+
+In the image below you can see information about the *location* question associated with the Trip Info user task. We are using an enumeration type with 3 possible answers; cabin, hotel, and camping. Note that there is also a *spam* form field for the second question.
+
+It is recommended that you add a default value in case the user does not input a variable
+that is recognized.
+
+.. image:: images/basic_example_form.png
+
+Example Code
+------------
+We can use the code in `ExampleCode.py `_ to run the workflow in our BPMN model.
+
+First, we have some imports.
+
+.. code:: python
+
+ from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
+ from SpiffWorkflow.camunda.parser.CamundaParser import CamundaParser
+ from SpiffWorkflow.camunda.specs.UserTask import EnumFormField, UserTask
+
+
+Next, we instantiate a parser, read the BPMN file, and grab the spec.
+
+.. code:: python
+
+ parser = CamundaParser()
+ parser.add_bpmn_file('BasicExample.bpmn')
+ spec = parser.get_spec('BasicExample')
+
+[Note that we hardcoded the name of the BPMN file and the spec. We will return to this below.]
+
+Next, we create a workflow instance from the spec using BPMNWorkflow. This is
+the engine that does the work for us. It manages the tasks and branches, and
+holds the data for a working workflow.
+
+.. code:: python
+
+ workflow = BpmnWorkflow(spec)
+
+We use do_engine_steps() to run all tasks the engine can complete on its own.
+I.e., without user input. Then, we gather the user tasks.
+
+.. code:: python
+
+ workflow.do_engine_steps()
+ ready_tasks = workflow.get_ready_user_tasks()
+
+The next section of code loops through the user tasks, displays their forms, and completes each task.
+
+.. code:: python
+
+ while len(ready_tasks) > 0:
+ for task in ready_tasks:
+ if isinstance(task.task_spec, UserTask):
+ show_form(task)
+ print(task.data)
+ else:
+ print("Complete Task ", task.task_spec.name)
+ workflow.complete_task_from_id(task.id)
+
+ExampleCode.py also defines the function *show_form* that builds an input prompt from the form, displays the prompt, and updates the workflow data with the user response.
+
+.. code:: python
+
+ def show_form(task):
+ model = {}
+ form = task.task_spec.form
+
+ if task.data is None:
+ task.data = {}
+
+ for field in form.fields:
+ prompt = field.label
+ if isinstance(field, EnumFormField):
+ prompt += "? (Options: " + ', '.join([str(option.id) for option in field.options]) + ")"
+ prompt += "? "
+ answer = input(prompt)
+ if field.type == "long":
+ answer = int(answer)
+ task.update_data_var(field.id,answer)
+
+Here is some sample output when running the code.
+
+.. code:: bash
+
+ $ python ExampleCode.py
+ Where are you going? (Options: cabin, hotel, camping)? camping
+ ['location']
+ Do you like spam? Yes
+ ['spam']
+ {'location': 'camping', 'spam': 'Yes'}
+ {'location': 'camping', 'spam': 'Yes'}
+
+
+Exclusive Gateway Example
+--------------------------
+This model is found in `ExclusiveGateway.bpmn `.
+
+In an exclusive gateway, exactly one alternative can be selected. The token runs along the sequence flow whose condition is met first. The response you get depends on which path you take.
+
+In this example, the path taken depends on the response to the “Do you like spam?” question in the previous user task . If you answered no, you will ONLY be asked for bad spam brands. If you answered yes, you will ONLY be asked good spam brands.
+
+.. image:: images/ExclusiveGateway.png
+ :scale: 25%
+ :align: center
+
+With a little modification, we can use the python in ExampleCode.py to run this model.
+
+Remember that we hardcoded the name of the BPMN file and the spec. To run the exclusive gateway model, we just need to edit the two lines to the new file and spec.
+
+Change
+
+.. code:: python
+
+ parser.add_bpmn_file('Basicexample.bpmn')
+ spec = parser.get_spec('Basicexample')
+
+to
+
+.. code:: python
+
+ parser.add_bpmn_file('ExclusiveGateway.bpmn')
+ spec = parser.get_spec('ExclusiveGateway')
+
+and run ExampleCode.py.
+
+Here is some sample output for ExclusiveGateway.bpmn
+
+.. code:: bash
+
+ $ python ExampleCode.py
+ Where are you going? (Options: cabin, hotel, camping)? hotel
+ ['location']
+ Do you like spam? yes
+ ['spam']
+ {'location': 'hotel', 'spam': 'yes'}
+ What is a good spam brand? SpamX
+ ['good brand']
+ {'location': 'hotel', 'spam': 'yes', 'good brand': 'SpamX'}
+ {'location': 'hotel', 'spam': 'yes', 'good brand': 'SpamX'}
+
+
+Parallel Gateway Example
+-------------------------
+This model is found in `ParallelGateway.bpmn `.
+
+A parallel or AND gateway creates parallel paths without checking any conditions. This means that each outgoing sequence flow becomes active upon the execution of a parallel gateway
+
+In this workflow, you will be prompted for both a good and bad example of spam.
+
+.. image:: images/ParallelGateway.png
+ :scale: 25%
+ :align: center
+
+To run this code, edit ExampleCode.py to use *ParallelGateway.bpmn* and *ParallelGateway*.
+
+.. code:: python
+
+ parser.add_bpmn_file('ParallelGateway.bpmn')
+ spec = parser.get_spec('ParallelGateway')
+
+
+Here is sample output.
+
+.. code:: bash
+
+ $ python ExampleCode.py
+ Where are you going? (Options: cabin, hotel, camping)? cabin
+ ['location']
+ Do you like spam? yes
+ ['spam']
+ {'location': 'cabin', 'spam': 'yes'}
+ What is a bad spam brand? Spambolina
+ ['bad brand']
+ {'location': 'cabin', 'spam': 'yes', 'bad brand': 'Spambolina'}
+ What is a good spam brand? SpamX
+ ['good brand']
+ {'location': 'cabin', 'spam': 'yes', 'good brand': 'SpamX'}
+ {'location': 'cabin', 'spam': 'yes', 'good brand': 'SpamX', 'bad brand': 'Spambolina'}
+
+
+Script Example
+-----------------
+This model is found in `ScriptExample.bpmn `.
+
+.. sidebar:: Setting up a script task
+
+ To create a script task in a BPMN modeler, you drag over a task from the object bar and then right click on the
+ task, use the wrench and select a script task from the options. Once you have a script task selected, use the
+ 'inline script' option in the options bar on the right and put in the code that you want to run. When using scripts,
+ you can interact with all of the data that has been put into the task.data object during the workflow.
+
+ .. image:: images/script_task.png
+ :align: center
+
+
+A Script Task is executed by a business process engine. In our example, it's do_engine_steps(). The modeler or implementer defines a script in a language that the engine can interpret. For us, this is python.
+
+When the Task is ready to start, the engine will execute the script. When the script is completed, the Task will also be completed. These are good to use when a task can be performed automatically.
+
+
+.. image:: images/Scriptsexample.png
+ :scale: 25%
+ :align: center
+
+In this example, the script prints something based on whether or not you like spam.
+
+To run this code, edit ExampleCode.py to use *ScriptExample.bpmn* and *ScriptExample*.
+
+.. code:: python
+
+ parser.add_bpmn_file('ScriptExample.bpmn')
+ spec = parser.get_spec('ScriptExample')
+
+
+Here is sample output.
+
+.. code:: bash
+
+ $ python ExampleCode.py
+ Where are you going? (Options: cabin, hotel, camping)? cabin
+ ['location']
+ Do you like spam? yes
+ ['spam']
+ {'location': 'cabin', 'spam': 'yes'}
+ Yeah Spam!!
+ {'location': 'cabin', 'spam': 'yes'}
+
+
+Multi-Instance Example
+-------------------------
+This model is found in `MultiInstance.bpmn `.
+
+Multi-instance activities are represented by three horizontal or vertical lines at the bottom-center of the activity and task symbol. The number of times that the activity completes is defined by the number of items that exist in the collection. This is different from other looping mechanisms that must check a condition every time the loop completes in order to determine if it should continue looping.
+
+Three vertical lines indicate that the multi-instance activity is parallel. This means that the
+activity can be completed for each item in the collection in no particular order.
+
+Three horizontal lines indicate that the multi-instance activity is sequential. This means that the activity must complete for each item in the order that they are received within the collection.
+
+.. image:: images/multi_instance_array.png
+
+In this example, the first activity is a UserTask where we ask for the family size. We then use that number to go through the multi-instance. The first multi-instance is parallel, which means that you can add the names in any order. The second multi-instance is sequential and will loop through the names from the previous task in the order they were received.
+
+.. code:: bash
+
+ $ python ExampleCode.py
+ Family Size? 2
+ ['Family', 'Size']
+ {'Family': {'Size': 2}}
+ First Name? John
+ ['FamilyMember', 'FirstName']
+ {'FamilyMember': {'FirstName': 'John'}, 'Family': {'Size': 2}}
+ First Name? Jane
+ ['FamilyMember', 'FirstName']
+ {'FamilyMember': {'FirstName': 'Jane'}, 'Family': {'Size': 2, 'Members': {1: {'FirstName': 'John'}}}}
+ Birthday? Johnday
+ ['CurrentFamilyMember', 'Birthdate']
+ {'CurrentFamilyMember': {'FirstName': 'John', 'Birthdate': 'Johnday'}, 'Family': {'Size': 2, 'Members': {1: {'FirstName': 'John'}, 2: {'FirstName': 'Jane'}}}, 'FamilyMember': {'FirstName': 'John'}}
+ Birthday? Janeday
+ ['CurrentFamilyMember', 'Birthdate']
+ {'CurrentFamilyMember': {'FirstName': 'Jane', 'Birthdate': 'Janeday'}, 'Family': {'Size': 2, 'Members': {1: {'FirstName': 'John', 'Birthdate': 'Johnday'}, 2: {'FirstName': 'Jane'}}}, 'FamilyMember': {'FirstName': 'John'}}
+ {'Family': {'Size': 2, 'Members': {1: {'FirstName': 'John', 'Birthdate': 'Johnday'}, 2: {'FirstName': 'Jane', 'Birthdate': 'Janeday'}}}, 'FamilyMember': {'FirstName': 'John'}}
+
+This is somewhat problematic, because the user must remember the order in which they entered the names. In the chapter on Jinja2, we cover multi-instance in more depth and use Templates to solve our problem.
+
+
+MultiInstance Notes
+-------------------
+
+A subset of MultiInstance and Looping Tasks are supported. Notably,
+the completion condition is not currently supported.
+
+The following definitions should prove helpful
+
+**loopCardinality** - This variable can be a text representation of a
+number - for example '2' or it can be the name of a variable in
+task.data that resolves to a text representation of a number.
+It can also be a collection such as a list or a dictionary. In the
+case that it is a list, the loop cardinality is equal to the length of
+the list and in the case of a dictionary, it is equal to the list of
+the keys of the dictionary.
+
+If loopCardinality is left blank and the Collection is defined, or if
+loopCardinality and Collection are the same collection, then the
+MultiInstance will loop over the collection and update each element of
+that collection with the new information. In this case, it is assumed
+that the incoming collection is a dictionary, currently behavior for
+working with a list in this manner is not defined and will raise an error.
+
+**Collection** This is the name of the collection that is created from
+the data generated when the task is run. Examples of this would be
+form data that is generated from a UserTask or data that is generated
+from a script that is run. Currently the collection is built up to be
+a dictionary with a numeric key that corresponds to the place in the
+loopCardinality. For example, if we set the loopCardinality to be a
+list such as ['a','b','c] the resulting collection would be {1:'result
+from a',2:'result from b',3:'result from c'} - and this would be true
+even if it is a parallel MultiInstance where it was filled out in a
+different order.
+
+**Element Variable** This is the variable name for the current
+iteration of the MultiInstance. In the case of the loopCardinality
+being just a number, this would be 1,2,3, . . . If the
+loopCardinality variable is mapped to a collection it would be either
+the list value from that position, or it would be the value from the
+dictionary where the keys are in sorted order. It is the content of the
+element variable that should be updated in the task.data. This content
+will then be added to the collection each time the task is completed.
+
+Example:
+ In a sequential MultiInstance, loop cardinality is ['a','b','c'] and elementVariable is 'myvar'
+ then in the case of a sequential multiinstance the first call would
+ have 'myvar':'a' in the first run of the task and 'myvar':'b' in the
+ second.
+
+Example:
+ In a Parallel MultiInstance, Loop cardinality is a variable that contains
+ {'a':'A','b':'B','c':'C'} and elementVariable is 'myvar' - when the multiinstance is ready, there
+ will be 3 tasks. If we choose the second task, the task.data will
+ contain 'myvar':'B'.
+
+Updating Data
+-------------
+
+While there may be some MultiInstances that will not result in any
+data, most of the time there will be some kind of data generated that
+will be collected from the MultiInstance. A good example of this is a
+UserTask that has an associated form or a script that will do a lookup
+on a variable.
+
+Each time the MultiInstance task generates data, the method
+task.update_data(data) should be called where data is the data
+generated. The 'data' variable that is passed in is assumed to be a
+dictionary that contains the element variable. Calling task.update_data(...)
+will ensure that the MultiInstance gets the correct data to include in the
+collection. The task.data is also updated with the dictionary passed to
+this method.
+
+Example:
+ In a Parallel MultiInstance, Loop cardinality is a variable that contains
+ {'a':'A','b':'B','c':'C'} and elementVariable is 'myvar'.
+ If we choose the second task, the task.data will contain 'myvar':{'b':'B'}.
+ If we wish to update the data, we would call task.update_data('myvar':{'b':'B2'})
+ When the task is completed, the task.data will now contain:
+ {'a':'A','b':'B2','c':'C'}
+
+Looping Tasks
+-------------
+
+A looping task sets the cardinality to 25 which is assumed to be a sane maximum value. The looping task will add to the collection each time it is processed if you are updating data.
+
+To halt the looping the task.terminate_loop()
+
+Each time task.complete() is called (or workflow.complete_task_by_id(task.id) ), the task will again present as READY until either the cardinality is exausted, or task.terminate_loop() is called.
+
+
+
diff --git a/doc/bpmn/images/Basicexample-Form.png b/doc/bpmn/images/Basicexample-Form.png
new file mode 100644
index 00000000..599bd95d
Binary files /dev/null and b/doc/bpmn/images/Basicexample-Form.png differ
diff --git a/doc/bpmn/images/Basicexample-output.png b/doc/bpmn/images/Basicexample-output.png
new file mode 100644
index 00000000..3aed5dcb
Binary files /dev/null and b/doc/bpmn/images/Basicexample-output.png differ
diff --git a/doc/bpmn/images/Basicexample.png b/doc/bpmn/images/Basicexample.png
new file mode 100644
index 00000000..5c52dcfc
Binary files /dev/null and b/doc/bpmn/images/Basicexample.png differ
diff --git a/doc/bpmn/images/CustomScript.png b/doc/bpmn/images/CustomScript.png
new file mode 100644
index 00000000..f257af78
Binary files /dev/null and b/doc/bpmn/images/CustomScript.png differ
diff --git a/doc/bpmn/images/ExclusiveGateway.png b/doc/bpmn/images/ExclusiveGateway.png
new file mode 100644
index 00000000..56ae2059
Binary files /dev/null and b/doc/bpmn/images/ExclusiveGateway.png differ
diff --git a/doc/bpmn/images/MessageBoundary.png b/doc/bpmn/images/MessageBoundary.png
new file mode 100644
index 00000000..dc63fb2a
Binary files /dev/null and b/doc/bpmn/images/MessageBoundary.png differ
diff --git a/doc/bpmn/images/ParallelGateway.png b/doc/bpmn/images/ParallelGateway.png
new file mode 100644
index 00000000..afa498aa
Binary files /dev/null and b/doc/bpmn/images/ParallelGateway.png differ
diff --git a/doc/bpmn/images/Scriptsexample-output.png b/doc/bpmn/images/Scriptsexample-output.png
new file mode 100644
index 00000000..40688061
Binary files /dev/null and b/doc/bpmn/images/Scriptsexample-output.png differ
diff --git a/doc/bpmn/images/Scriptsexample.png b/doc/bpmn/images/Scriptsexample.png
new file mode 100644
index 00000000..1c684754
Binary files /dev/null and b/doc/bpmn/images/Scriptsexample.png differ
diff --git a/doc/bpmn/images/basic_example.png b/doc/bpmn/images/basic_example.png
new file mode 100644
index 00000000..5c52dcfc
Binary files /dev/null and b/doc/bpmn/images/basic_example.png differ
diff --git a/doc/bpmn/images/basic_example_form.png b/doc/bpmn/images/basic_example_form.png
new file mode 100644
index 00000000..599bd95d
Binary files /dev/null and b/doc/bpmn/images/basic_example_form.png differ
diff --git a/doc/bpmn/images/basic_example_output.png b/doc/bpmn/images/basic_example_output.png
new file mode 100644
index 00000000..3aed5dcb
Binary files /dev/null and b/doc/bpmn/images/basic_example_output.png differ
diff --git a/doc/bpmn/images/bpmnbook.jpg b/doc/bpmn/images/bpmnbook.jpg
new file mode 100644
index 00000000..e5b0a393
Binary files /dev/null and b/doc/bpmn/images/bpmnbook.jpg differ
diff --git a/doc/bpmn/images/common_workflow.png b/doc/bpmn/images/common_workflow.png
new file mode 100644
index 00000000..89148941
Binary files /dev/null and b/doc/bpmn/images/common_workflow.png differ
diff --git a/doc/bpmn/images/decision_table.png b/doc/bpmn/images/decision_table.png
new file mode 100644
index 00000000..ee554e07
Binary files /dev/null and b/doc/bpmn/images/decision_table.png differ
diff --git a/doc/bpmn/images/dmn-output.png b/doc/bpmn/images/dmn-output.png
new file mode 100644
index 00000000..ca4a869f
Binary files /dev/null and b/doc/bpmn/images/dmn-output.png differ
diff --git a/doc/bpmn/images/dmn.png b/doc/bpmn/images/dmn.png
new file mode 100644
index 00000000..d1e04ed2
Binary files /dev/null and b/doc/bpmn/images/dmn.png differ
diff --git a/doc/bpmn/images/exgateway-output.png b/doc/bpmn/images/exgateway-output.png
new file mode 100644
index 00000000..973700df
Binary files /dev/null and b/doc/bpmn/images/exgateway-output.png differ
diff --git a/doc/bpmn/images/exgateway.png b/doc/bpmn/images/exgateway.png
new file mode 100644
index 00000000..14be94fe
Binary files /dev/null and b/doc/bpmn/images/exgateway.png differ
diff --git a/doc/bpmn/images/lanes.png b/doc/bpmn/images/lanes.png
new file mode 100644
index 00000000..8831b997
Binary files /dev/null and b/doc/bpmn/images/lanes.png differ
diff --git a/doc/bpmn/images/multi_instance_array-output.png b/doc/bpmn/images/multi_instance_array-output.png
new file mode 100644
index 00000000..c984aa12
Binary files /dev/null and b/doc/bpmn/images/multi_instance_array-output.png differ
diff --git a/doc/bpmn/images/multi_instance_array.png b/doc/bpmn/images/multi_instance_array.png
new file mode 100644
index 00000000..61551769
Binary files /dev/null and b/doc/bpmn/images/multi_instance_array.png differ
diff --git a/doc/bpmn/images/plgateway-output.png b/doc/bpmn/images/plgateway-output.png
new file mode 100644
index 00000000..a76dcdd3
Binary files /dev/null and b/doc/bpmn/images/plgateway-output.png differ
diff --git a/doc/bpmn/images/plgateway.png b/doc/bpmn/images/plgateway.png
new file mode 100644
index 00000000..6b242a82
Binary files /dev/null and b/doc/bpmn/images/plgateway.png differ
diff --git a/doc/bpmn/images/script_task.png b/doc/bpmn/images/script_task.png
new file mode 100644
index 00000000..6e35df61
Binary files /dev/null and b/doc/bpmn/images/script_task.png differ
diff --git a/doc/bpmn/images/simplestworkflow.png b/doc/bpmn/images/simplestworkflow.png
new file mode 100644
index 00000000..0b910ac3
Binary files /dev/null and b/doc/bpmn/images/simplestworkflow.png differ
diff --git a/doc/bpmn/images/sublevelexample.png b/doc/bpmn/images/sublevelexample.png
new file mode 100644
index 00000000..4d91b9ea
Binary files /dev/null and b/doc/bpmn/images/sublevelexample.png differ
diff --git a/doc/bpmn/images/top_workflow.png b/doc/bpmn/images/top_workflow.png
new file mode 100644
index 00000000..bd533da5
Binary files /dev/null and b/doc/bpmn/images/top_workflow.png differ
diff --git a/doc/bpmn/images/topworkflowexample.png b/doc/bpmn/images/topworkflowexample.png
new file mode 100644
index 00000000..59d9aaf6
Binary files /dev/null and b/doc/bpmn/images/topworkflowexample.png differ
diff --git a/doc/bpmn/index.rst b/doc/bpmn/index.rst
new file mode 100644
index 00000000..3a4b3e27
--- /dev/null
+++ b/doc/bpmn/index.rst
@@ -0,0 +1,32 @@
+Business Process Model and Notation (BPMN)
+==========================================
+
+Business Process Model and Notation (BPMN) is a diagramming language for
+business process flows. BPMN links the realms of business and IT, and creates
+a common process language that can be shared between the two.
+
+BPMN describes details of process behaviors efficiently in a diagram. The
+meaning is precise enough to describe the technical details that control
+process execution in an automation engine. SpiffWorkflow allows you to create
+code to get a BPMN up and running.
+
+When using SpiffWorkflow, a client can manipulate the BPMN diagram and still
+have their product work without a need for you to edit the Python code,
+improving response and turnaround time.
+
+Today, nearly every process modeling tool supports BPMN in some fashion making
+it a great tool to learn and use.
+
+.. toctree::
+ :maxdepth: 2
+
+ intro
+ examples
+ dmn
+ jinja
+ advanced_features
+ call_activity
+ nav_list
+ serialize
+ ScriptingEnhancements
+
diff --git a/doc/bpmn/intro.rst b/doc/bpmn/intro.rst
new file mode 100644
index 00000000..cffb721a
--- /dev/null
+++ b/doc/bpmn/intro.rst
@@ -0,0 +1,69 @@
+Overview of BPMN
+============================
+
+To use SpiffWorkflow, you need at least a basic understanding of BPMN.
+This page offers a brief overview. There are many resources for additional
+information about BPMN.
+
+In these examples and throughout the documentation we use the a Modeler for
+BPMN. `BPMN.js ` To support web forms
+we use Camunda's free BPMN editor.
+
+
+.. sidebar:: BPMN Modelers
+
+ There are a number of modelers in existence, and any BPMN compliant modeler should work.
+ SpiffWorkflow has some basic support for the free Camunda modeler, to use it's form building
+ capabilities, but we intend to encapsulate this support in an extension module and remove
+ it from the core library eventually. It does help for making some examples and demonstrating
+ how one might implement user tasks in an online environment.
+
+A Simple Workflow
+-----------------
+
+All BPMN models have a start event and at least one end event. The start event
+is represented with a single thin border circle. An end event is represented
+by a single thick border circle.
+
+The following example also has one task, represented by the rectangle with curved corners.
+
+
+.. figure:: images/simplestworkflow.png
+ :scale: 25%
+ :align: center
+
+ A simple workflow.
+
+
+The sequence flow is represented with a solid line connector. When the node at
+the tail of a sequence flow completes, the node at the arrowhead is enabled to start.
+
+
+More complicated Workflow
+-------------------------
+
+.. figure:: images/ExclusiveGateway.png
+ :scale: 25%
+ :align: center
+
+ A workflow with a gateway
+
+
+In this example, the diamond shape is called a gateway. It represents a branch
+point in our flow. This gateway is an exclusive data-based gateway (also
+called an XOR gateway). With an exclusive gateway, you must take one path or
+the other based on some data condition. BPMN has other gateway types.
+
+The important point is that we can use a gateway to add a branch in the
+workflow **without** creating an explicit branch in our Python code.
+
+
+.. sidebar:: BPMN Resources
+
+ This guide is a mere introduction to how to get started with BPMN allowing someone to get started with
+ SpiffWorkflow and start using it to write programs that perform a workflow and let SpiffWorkflow do the heavy lifting.
+ For a more serious modeling, we recommend looking for more comprehensive resources. We have used the books by Bruce
+ Silver as a guide for our BPMN modeling.
+
+ .. image:: images/bpmnbook.jpg
+ :align: center
diff --git a/doc/bpmn/jinja.rst b/doc/bpmn/jinja.rst
new file mode 100644
index 00000000..7f4d6da2
--- /dev/null
+++ b/doc/bpmn/jinja.rst
@@ -0,0 +1,80 @@
+Jinja Documentation Strings
+===================================
+
+When using forms, it is often nice to have the ability to display the context of an item, for example when we are
+using a multi-instance to enter the name of a bunch of people and then we need to enter the birthday for the list we
+just created. If I don't have context, how do I know if I am putting in the birthday for Bob or Sally?
+
+.. image:: images/multi_instance_array.png
+
+Luckily, we have a place to do that for BPMN.js modeler. When we click on the 'Enter Birthday for each family member
+in prev step' user task, there is an 'Element Documentation' in the left hand sidebar.
+
+If we put the following code in the documentation string, we will have a string that we can parse to provide
+interactive documentation. Spiff is set up in a way that you could use any templating library you want, but we have
+used Jinja for all of our examples.
+
+Jinja is a popular templating library (see https://jinja.palletsprojects.com/en/2.11.x/ )
+
+.. code::
+
+ Enter Birthday for {{ CurrentFamilyMember.FirstName }}
+
+Returning to the sample code, when we print out the form, we use the new code listed below (as seen in
+ExampleCode-multi.py)
+
+.. code:: python
+ :number-lines:
+
+ from jinja2 import Template # <-- Added
+
+.. code:: python
+ :number-lines: 7
+
+
+ def show_form(task):
+ model = {}
+ form = task.task_spec.form
+ docs = task.task_spec.documentation # <-- Added
+
+ template = Template(docs) # <-- Added
+ print(task.data)
+ print(template.render(task.data)) # <-- Added
+
+This code effectively gets the documentation string we entered above from the task specification, creates a jinja
+template and then uses that template paired with the current task data that we have created elsewhere.
+
+When we are filled out the information in 'Get Name for Each Family Member' we created a collection in Family.Members
+
+
+You will note that Family.Members is not typical python, it is a notation that we use to simplify entry - normal
+python would be Family['Members']
+
+In any event, we created a collection of family members, and in the task 'Enter Birthday for each family member ...'
+we are using a MultiInstance for each member of that collection. The collection variable for this task is
+'CurrentFamilyMember'. Because of how we filled out the form in the previous MultiInstance task, each Family member
+will have a FirstName in each collection item. For each item in the collection, it will fill out CurrentFamilyMember
+with the current collection item, and then we will add Birthdate to that collection item before we merge it back
+into the collection.
+
+
+As we run this code, when we get to the third task, the output from our example code will look something like this:
+
+.. code::
+
+ {'CurrentFamilyMember': {'FirstName': 'Bob'},
+ 'Family': {'Size': 2,
+ 'Members': {1: {'FirstName': 'Bob'},
+ 2: {'FirstName': 'Sally'}}},
+ 'FamilyMember': {'FirstName': 'Bob'}}
+ Enter Birthday for Bob
+ Birthday?
+
+we can see that we printed out the task.data dictionary (reformatted for clarity), the documentation string that has
+been transformed by jinja to use CurrentFamilyMember.FirstName (CurrentFamilyMember['FirstName'] would work as well,
+if you enjoy typing extra characters) and we can also see the label text that we entered for the form variable on the
+form for the last task.
+
+This feature is rather useful in giving end users feedback about exactly what they are filling out, especialy in the
+context of a MultiInstance task, since, by nature, the MultiInstance task will be allowing users to do the one thing
+multiple times, perhaps with different contexts.
diff --git a/doc/bpmn/make.bat b/doc/bpmn/make.bat
new file mode 100644
index 00000000..922152e9
--- /dev/null
+++ b/doc/bpmn/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=.
+set BUILDDIR=_build
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/doc/bpmn/nav_list.rst b/doc/bpmn/nav_list.rst
new file mode 100644
index 00000000..6f5cf999
--- /dev/null
+++ b/doc/bpmn/nav_list.rst
@@ -0,0 +1,152 @@
+Choosing Lane Specific Tasks
+===================================
+
+Lets take a look at a sample workflow with lanes:
+
+.. figure:: images/lanes.png
+
+and lets say that we would like to have two separate processes, one that shows the steps for only for the A role, and
+one that shows only tasks for the B role.
+
+Up until now, we have just asked the workflow processor for any ready tasks, but it is possible to ask the workflow
+processor for just those tasks that are for a specific lane. Please note that this feature will only work for those
+situations where you have set up a workflow with lanes
+
+.. tip:: Workflows with 'pools'
+
+ When you add lanes to a BPMN document, those lanes are contained within a 'pool' That pool has its own process
+ ID. To activate the workflow inside of a pool, you need to use the process id for the pool and not the workflow
+ document itself as we have in previous workflows. In the workflow above, the main workflow ID is lanes_outer, and
+ the processid on the pool is 'lanes' - so we would use 'lanes' in the 'get_spec' line of code to interact with this
+ workflow.
+
+To get all of the tasks that are ready for the 'A' workflow, we would use the same
+
+.. code:: python
+
+ ready_tasks = workflow.get_ready_user_tasks()
+
+but we would change it to do the following:
+
+.. code:: python
+
+ ready_tasks = workflow.get_ready_user_tasks(lane='A')
+
+If there were no tasks ready for the 'A' lane, you would get an empty list, and of course if you had no lane that was
+labeled 'A' you would *always* get an empty list.
+
+
+This is all well and good, but what if you want to give a user that is in either the 'A' or 'B' role a sense of where
+things were at in the workflow? Spiff workflow gives us a 'Nav List' that allows us to convey a sort of 'roadmap' to
+the user no matter if they are using lanes or not.
+
+Nav(igation) List
+=================
+
+.. sidebar:: Warning!
+
+ At the time of writing, the nav list does have some issues. The main issue is that there are 'issues' with showing
+ the tasks for a subprocess. For most self-contained workflows, the nav-list does a good job of showing all of the
+ events that are coming up and where a user is at in the process. Another set of features that have not been tested
+ with the navigation list are any kind of boundary events which may cause a non-linear flow which would be hard to
+ render.
+
+In order to get the navigation list, we can call the workflow.get_nav_list() function. This will return a list of dictionaries with information about each task and decision point in the workflow. Each item in this list returns some information about the tasks that are in the workflow, and how it relates to the other tasks.
+
+To give you an idea of what is in the list I'll include a segment from the documentation::
+
+ id - TaskSpec or Sequence flow id
+ task_id - The uuid of the actual task instance, if it exists.
+ name - The name of the task spec (or sequence)
+ description - Text description
+ backtracks - Boolean, if this backtracks back up the list or not
+ level - Depth in the tree - probably not needed
+ indent - A hint for indentation
+ child_count - The number of children that should be associated with
+ this item.
+ lane - This is the swimlane for the task if indicated.
+ state - Text based state (may be half baked in the case that we have
+ more than one state for a task spec - but I don't think those
+ are being reported in the list, so it may not matter)
+ Any task with a blank or None as the description are excluded from the list (i.e. gateways)
+
+
+Because the output from this list may be used in a variety of contexts, the implementation is left to the user, however, we do have some example code that creates a text based representation of the nav-list.
+
+The example code for this lives in 'testlanes.py' and runs the above workflow. The code to print the nav-list is in the printTaskTree function
+
+
+.. code:: python
+ :number-lines: 41
+
+ def printTaskTree(tree,currentID,data):
+ print("\n\n\n")
+ print("Current Tree")
+ print("-------------")
+ lookup = {'COMPLETED':'* ',
+ 'READY':'> ',
+ 'LIKELY':'0 ',
+ 'NOOP': ' '
+ }
+ for x in tree:
+ if x['id'] == currentID:
+ print('>>', end='')
+ else:
+ print(lookup.get(x['state'],'O '),end='')
+ print(" "*x['indent'], end='')
+ print(x['description'], end = '')
+ if x['task_id'] is not None:
+ print(' ---> ',end='')
+ print(str(x['lane']),end='')
+ if x.get('is_decision') and x.get('backtracks') is not None:
+ print()
+ print(' ' * x['indent'] + ' Returns to '+x['backtracks'][1],end='')
+ elif x.get('is_decision') and x.get('child_count',0)==0:
+ print()
+ print(' '*x['indent']+' Do Nothing', end='')
+ print()
+ print('\n\nCurrent Data')
+ print('-----------------')
+ print(str(data),end='')
+ print('\n\nReady Tasks')
+ print('-----------------')
+
+ for lane in ['A','B']:
+ print(lane+' Tasks')
+ for x in workflow.get_ready_user_tasks(lane=lane):
+ print(' ' + x.get_name())
+ print('\n\n\n')
+
+This code prints to the screen each time the code progresses and prints something like this:
+
+.. code::
+
+ Current Tree
+ -------------
+ * Request Feature ---> A
+ * Clarifying Questions? ---> B
+ * Do we need Clarifcation? ---> B
+ Yes
+ O Clarify Request
+ No
+ Do Nothing
+ >>Implement Feature ---> B
+
+
+ Current Data
+ -----------------
+ {'NewBPMNFeautre': 'Something new', 'NeedClarification': 'no'}
+
+ Ready Tasks
+ -----------------
+ A Tasks
+ B Tasks
+ Activity_B2
+
+
+If we look at the internals of the bpmn diagram, we can see that the last task that has the description 'Implement
+Feature' has the ID of Activity_B2, so the list above shows us that we are currently on the 'Implement Feature' task,
+that we have completed 'Request Feature', 'Clarifying Questions?' and the gateway 'Do We need Clarifications?' - and
+that we did not ask to clarify the request. It is also showing us the data we have collected so far in task.data
+which gets propagated down the workflow as we complete tasks.
+
diff --git a/doc/bpmn/serialize.rst b/doc/bpmn/serialize.rst
new file mode 100644
index 00000000..53b0cb7b
--- /dev/null
+++ b/doc/bpmn/serialize.rst
@@ -0,0 +1,143 @@
+Serialize / Deserialize
+===================================
+
+All of our examples so far have come in the context where we will run the workflow from beginning to end in one
+setting. This may not always be the case, we may be executing the workflow in the context of a web server where we
+may have a user request a web page where we open a specific workflow that we may be in the middle of, do one step of
+that workflow and then the user may be back in a few minutes, or maybe a few hours depending on the application.
+
+It is important then that we can save and load the workflow, including all of the data and information about where
+the user is at in the workflow.
+
+SpiffWorkflow has a few methods to help us with this.
+
+.. code:: python
+
+ state = BpmnSerializer().serialize_workflow(self.workflow, include_spec=include_spec)
+ workflow = BpmnSerializer().deserialize_workflow(state, workflow_spec=None)
+
+The first line will pack up the entire state of the workflow and put it in the variable 'state' and the second line
+will take what is in the 'state' variable and re-build the workflow.
+
+Lets re-use our example from the jinja documentation to show how we would put this into practice.
+
+.. figure:: images/multi_instance_array.png
+ :align: center
+
+ Example Workflow
+
+In that section, we used the ExampleCode-multi.py code to show how we could incorporate Jinja documentation string
+into a workflow. We'll take that exact same code and add the lines above (plus some support code) to make sure that
+if we get interrupted, our workflow can pick up exactly where it left off.
+
+Where we used to have
+
+.. code:: python
+ :number-lines: 31
+
+ x = CamundaParser()
+ x.add_bpmn_file('multi_instance_array.bpmn')
+ spec = x.get_spec('MultiInstanceArray')
+
+we change it to this
+
+.. code:: python
+ :number-lines: 31
+
+ if os.path.exists('ExampleSaveRestore.js'):
+ f = open('ExampleSaveRestore.js')
+ state = f.read()
+ f.close()
+ workflow = BpmnSerializer().deserialize_workflow(
+ state, workflow_spec=None)
+ else:
+ x = CamundaParser()
+ x.add_bpmn_file('multi_instance_array.bpmn')
+ spec = x.get_spec('MultiInstanceArray')
+
+.. sidebar:: Work to do
+
+ This works fine for the usual UserTask multi-instances, but I suspect we still have some work to do with other,
+ more esoteric things like a Script MultiInstance or a CallActivity MultiInstance
+
+Essentially, we look for a file that we save that contains the state in it, and if we find it, we re-load the
+workflow and use that, if not, we start the whole process from the beginning.
+
+Please note that when the workflow is entirely complete, we will save the state and if we run it again, it will
+happily find the state file and try to run a completed workflow. There will be nothing for SpiffWorkflow to do if
+this happens!!
+
+This is all well and good, but we need to make sure that there is a state file out there when we have progressed in
+the workflow. We do this by adding the following lines
+
+.. code:: python
+ :number-lines: 50
+
+ . . . #
+ else: #
+ print("Complete Task ", task.task_spec.name) # <- previous code for context
+ workflow.complete_task_from_id(task.id) #
+ state = BpmnSerializer().serialize_workflow(workflow, #
+ include_spec=True) #
+ f = open('ExampleSaveRestore.js', 'w') # <-- new code
+ f.write(state) #
+ f.close() #
+
+So, each time we do a step, we get the state from the workflow and write that out into the ExampleSaveRestore.js file
+. This data is just a big JSON string, and in some cases SpiffWorkflow uses a Python construct known as a 'pickle' to
+save more complicated data. (go ahead, look at it. It won't make much sense, but you can get an idea of what it is
+doing)
+
+.. code::
+
+ prompt--> python ExampleCode-multi-save-restore.py
+ {}
+ Please enter number of people :
+ Family Size? 3
+ ['Family', 'Size']
+ {'Family': {'Size': 3}}
+ {'FamilyMember': 1, 'Family': {'Size': 3}}
+ Please enter information for family member 1:
+ First Name? A
+ ['FamilyMember', 'FirstName']
+ {'FamilyMember': {'FirstName': 'A'}, 'Family': {'Size': 3}}
+ {'FamilyMember': 2, 'Family': {'Size': 3, 'Members': {1: {'FirstName': 'A'}}}}
+ Please enter information for family member 2:
+ First Name? B
+ ['FamilyMember', 'FirstName']
+ {'FamilyMember': {'FirstName': 'B'}, 'Family': {'Size': 3, 'Members': {1: {'FirstName': 'A'}}}}
+ {'FamilyMember': 3, 'Family': {'Size': 3, 'Members': {1: {'FirstName': 'A'}, 2: {'FirstName': 'B'}}}}
+ Please enter information for family member 3:
+ First Name? C
+ ['FamilyMember', 'FirstName']
+ {'FamilyMember': {'FirstName': 'C'}, 'Family': {'Size': 3, 'Members': {1: {'FirstName': 'A'}, 2: {'FirstName': 'B'}}}}
+ {'CurrentFamilyMember': {'FirstName': 'A'}, 'Family': {'Size': 3, 'Members': {1: {'FirstName': 'A'}, 2: {'FirstName': 'B'}, 3: {'FirstName': 'C'}}}, 'FamilyMember': {'FirstName': 'B'}}
+ Enter Birthday for A
+ Birthday? a
+ ['CurrentFamilyMember', 'Birthdate']
+ {'CurrentFamilyMember': {'FirstName': 'A', 'Birthdate': 'a'}, 'Family': {'Size': 3, 'Members': {1: {'FirstName': 'A'}, 2: {'FirstName': 'B'}, 3: {'FirstName': 'C'}}}, 'FamilyMember': {'FirstName': 'B'}}
+ {'CurrentFamilyMember': {'FirstName': 'B'}, 'Family': {'Size': 3, 'Members': {1: {'FirstName': 'A', 'Birthdate': 'a'}, 2: {'FirstName': 'B'}, 3: {'FirstName': 'C'}}}, 'FamilyMember': {'FirstName': 'B'}}
+ Enter Birthday for B
+ Birthday? ^CTraceback (most recent call last):
+ File "ExampleCode-multi-save-restore.py", line 49, in
+ show_form(task)
+ File "ExampleCode-multi-save-restore.py", line 26, in show_form
+ answer = input(prompt)
+ KeyboardInterrupt
+
+
+ prompt--> python ExampleCode-multi-save-restore.py
+ {'CurrentFamilyMember': {'FirstName': 'B'}, 'Family': {'Size': 3, 'Members': {1: {'FirstName': 'A', 'Birthdate': 'a'}, 2: {'FirstName': 'B'}, 3: {'FirstName': 'C'}}}, 'FamilyMember': {'FirstName': 'B'}}
+ Enter Birthday for B
+ Birthday? b
+ ['CurrentFamilyMember', 'Birthdate']
+ {'CurrentFamilyMember': {'FirstName': 'B', 'Birthdate': 'b'}, 'Family': {'Size': 3, 'Members': {1: {'FirstName': 'A', 'Birthdate': 'a'}, 2: {'FirstName': 'B'}, 3: {'FirstName': 'C'}}}, 'FamilyMember': {'FirstName': 'B'}}
+ {'CurrentFamilyMember': {'FirstName': 'C'}, 'Family': {'Size': 3, 'Members': {1: {'FirstName': 'A', 'Birthdate': 'a'}, 2: {'FirstName': 'B', 'Birthdate': 'b'}, 3: {'FirstName': 'C'}}}, 'FamilyMember': {'FirstName': 'B'}}
+ Enter Birthday for C
+ Birthday? c
+ ['CurrentFamilyMember', 'Birthdate']
+ {'CurrentFamilyMember': {'FirstName': 'C', 'Birthdate': 'c'}, 'Family': {'Size': 3, 'Members': {1: {'FirstName': 'A', 'Birthdate': 'a'}, 2: {'FirstName': 'B', 'Birthdate': 'b'}, 3: {'FirstName': 'C'}}}, 'FamilyMember': {'FirstName': 'B'}}
+ {'Family': {'Size': 3, 'Members': {1: {'FirstName': 'A', 'Birthdate': 'a'}, 2: {'FirstName': 'B', 'Birthdate': 'b'}, 3: {'FirstName': 'C', 'Birthdate': 'c'}}}, 'FamilyMember': {'FirstName': 'B'}}
+
+This is pretty verbose, but you can see where we were able to break the code, re-run the Python file, and pick up exactly where we left off, and see that all of the data that we had previously is still the same as it was when we saved the file. Pretty Spiffy!!
+
diff --git a/doc/conf.py b/doc/conf.py
index 0e06893e..17deaabe 100755
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -34,6 +34,7 @@ sys.path.insert(0, os.path.abspath('..'))
extensions = ['sphinx.ext.autodoc', # 'sphinx.ext.coverage',
'sphinx.ext.viewcode',
'sphinx.ext.autosummary',
+ 'sphinx_rtd_theme',
#'sphinx.ext.intersphinx',
]
@@ -58,9 +59,9 @@ copyright = u'2017, Samuel Abels'
# built documents.
#
# The short X.Y version.
-version = '0.3'
+version = '1.0'
# The full version, including alpha/beta/rc tags.
-release = '0.3.4'
+release = '1.0.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -101,7 +102,8 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'default'
+#html_theme = 'default'
+html_theme = "sphinx_rtd_theme"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
diff --git a/doc/index.rst b/doc/index.rst
index df8506e2..2cff90c2 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -1,20 +1,19 @@
-.. image:: https://travis-ci.org/knipknap/SpiffWorkflow.svg?branch=master
- :target: https://travis-ci.org/knipknap/SpiffWorkflow
+.. image:: https://travis-ci.com/sartography/SpiffWorkflow.svg?branch=master
+ :target: https://travis-ci.org/sartography/SpiffWorkflow
-.. image:: https://coveralls.io/repos/github/knipknap/SpiffWorkflow/badge.svg?branch=master
- :target: https://coveralls.io/github/knipknap/SpiffWorkflow?branch=master
+.. image:: https://sonarcloud.io/api/project_badges/measure?project=sartography_SpiffWorkflow&metric=alert_status
+ :target: https://sonarcloud.io/dashboard?id=sartography_SpiffWorkflow
-.. image:: https://lima.codeclimate.com/github/knipknap/SpiffWorkflow/badges/gpa.svg
- :target: https://lima.codeclimate.com/github/knipknap/SpiffWorkflow
- :alt: Code Climate
+.. image:: https://sonarcloud.io/api/project_badges/measure?project=sartography_SpiffWorkflow&metric=coverage
+ :target: https://sonarcloud.io/dashboard?id=sartography_SpiffWorkflow
+ :alt: Coverage
-.. image:: https://img.shields.io/github/stars/knipknap/SpiffWorkflow.svg
- :target: https://github.com/knipknap/SpiffWorkflow/stargazers
+.. image:: https://img.shields.io/github/stars/sartography/SpiffWorkflow.svg
+ :target: https://github.com/sartography/SpiffWorkflow/stargazers
-.. image:: https://img.shields.io/github/license/knipknap/SpiffWorkflow.svg
- :target: https://github.com/knipknap/SpiffWorkflow/blob/master/COPYING
+.. image:: https://img.shields.io/github/license/sartography/SpiffWorkflow.svg
+ :target: https://github.com/sartography/SpiffWorkflow/blob/master/COPYING
-|
What is SpiffWorkflow?
======================
@@ -31,7 +30,7 @@ You can find a list of supported workflow patterns in :ref:`features`.
In addition, Spiff Workflow provides a parser and workflow emulation
layer that can be used to create executable Spiff Workflow specifications
-from Business Process Model and Notation (BPMN) documents. See :ref:`bpmn_page`.
+from Business Process Model and Notation (BPMN) documents. See :doc:`/bpmn/intro`.
License
-------
@@ -40,14 +39,13 @@ Spiff Workflow is published under the terms of the
Contents
--------
-
.. toctree::
:maxdepth: 2
basics
+ bpmn/index
tutorial/index
custom-tasks/index
- bpmn
internals
features
- API Documentation
+
diff --git a/doc/tutorial/index.rst b/doc/tutorial/index.rst
index 491ec934..2ba30118 100644
--- a/doc/tutorial/index.rst
+++ b/doc/tutorial/index.rst
@@ -1,5 +1,5 @@
-Tutorial
-========
+Tutorial - Non-BPMN
+===================
Introduction
------------
diff --git a/setup.py b/setup.py
index f804848c..ec234926 100644
--- a/setup.py
+++ b/setup.py
@@ -1,37 +1,37 @@
# -*- coding: utf-8 -*-
from __future__ import division
+
+import pathlib
import sys
+
sys.path.insert(0, '.')
sys.path.insert(0, 'SpiffWorkflow')
-from version import __version__
from setuptools import setup, find_packages
-from os.path import dirname, join
-setup(name = 'SpiffWorkflow',
- version = __version__,
- description = 'A workflow framework based on www.workflowpatterns.com',
- long_description = \
-"""
-Spiff Workflow is a library implementing workflows in pure Python.
-It was designed to provide a clean API, and tries to be very easy to use.
+# The directory containing this file
+HERE = pathlib.Path(__file__).parent
-You can find a list of supported workflow patterns in the `README file`_
-included with the package.
-.. _README file: https://github.com/knipknap/SpiffWorkflow/blob/master/README.md
-""",
- author = 'Samuel Abels',
- author_email = 'cheeseshop.python.org@debain.org',
- license = 'lGPLv2',
- packages = find_packages(exclude=['tests', 'tests.*']),
- install_requires = ['future', 'configparser', 'lxml', 'celery'],
- keywords = 'spiff workflow bpmn engine',
- url = 'https://github.com/knipknap/SpiffWorkflow',
- classifiers = [
- 'Development Status :: 4 - Beta',
- 'Intended Audience :: Developers',
- 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
- 'Programming Language :: Python',
- 'Topic :: Other/Nonlisted Topic',
- 'Topic :: Software Development :: Libraries',
- 'Topic :: Software Development :: Libraries :: Python Modules'
+# The text of the README file
+README = (HERE / "README.md").read_text()
+
+setup(name='SpiffWorkflow',
+ version='1.0.0',
+ description='A workflow framework and BPMN/DMN Processor',
+ long_description=README,
+ long_description_content_type="text/markdown",
+ author='Sartography',
+ author_email='dan@sartography.com',
+ license='lGPLv2',
+ packages=find_packages(exclude=['tests', 'tests.*']),
+ install_requires=['future', 'configparser', 'lxml', 'celery'],
+ keywords='spiff workflow bpmn engine',
+ url='https://github.com/sartography/SpiffWorkflow',
+ classifiers=[
+ 'Development Status :: 4 - Beta',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
+ 'Programming Language :: Python',
+ 'Topic :: Other/Nonlisted Topic',
+ 'Topic :: Software Development :: Libraries',
+ 'Topic :: Software Development :: Libraries :: Python Modules'
])