![Crossplane Logo](https://raw.githubusercontent.com/nginxinc/crossplane/master/ext/crossplane-logo.png)
<h1 align="center">crossplane</h1>
<h3 align="center">Reliable and fast NGINX configuration file parser and builder</h3>

<p align="center">
<a href="https://travis-ci.org/nginxinc/crossplane"><img src="https://img.shields.io/travis/nginxinc/crossplane/master.svg"></a>
<a href="https://github.com/nginxinc/crossplane/releases"><img src="https://img.shields.io/github/release/nginxinc/crossplane.svg"></a>
<a href="https://pypi.python.org/pypi/crossplane"><img src="https://img.shields.io/pypi/l/crossplane.svg"></a>
<a href="https://pypi.python.org/pypi/crossplane"><img src="https://img.shields.io/pypi/pyversions/crossplane.svg"></a>
</p>

  - [Install](#install)
  - [Command Line Interface](#command-line-interface)
      - [crossplane parse](#crossplane-parse)
      - [crossplane build](#crossplane-build)
      - [crossplane lex](#crossplane-lex)
      - [crossplane format](#crossplane-format)
      - [crossplane minify](#crossplane-minify)
  - [Python Module](#python-module)
      - [crossplane.parse()](#crossplaneparse)
      - [crossplane.build()](#crossplanebuild)
      - [crossplane.lex()](#crossplanelex)
  - [Other Languages](#other-languages)

## Install

You can install both the [Command Line
Interface](#command-line-interface) and [Python Module](#python-module)
via:

    pip install crossplane

## Command Line Interface

```
usage: crossplane <command> [options]

various operations for nginx config files

optional arguments:
  -h, --help            show this help message and exit
  -V, --version         show program's version number and exit

commands:
  parse                 parses a json payload for an nginx config
  build                 builds an nginx config from a json payload
  lex                   lexes tokens from an nginx config file
  minify                removes all whitespace from an nginx config
  format                formats an nginx config file
  help                  show help for commands
```

### crossplane parse

This command will take a path to a main NGINX config file as input, then
parse the entire config into the schema defined below, and dumps the
entire thing as a JSON payload.

```
usage: crossplane parse [-h] [-o OUT] [-i NUM] [--ignore DIRECTIVES]
                        [--no-catch] [--tb-onerror] [--single-file]
                        [--include-comments] [--strict]
                        filename

parses a json payload for an nginx config

positional arguments:
  filename              the nginx config file

optional arguments:
  -h, --help            show this help message and exit
  -o OUT, --out OUT     write output to a file
  -i NUM, --indent NUM  number of spaces to indent output
  --ignore DIRECTIVES   ignore directives (comma-separated)
  --no-catch            only collect first error in file
  --tb-onerror          include tracebacks in config errors
  --combine             use includes to create one single file
  --single-file         do not include other config files
  --include-comments    include comments in json
  --strict              raise errors for unknown directives
```

**Privacy and Security**

Since `crossplane` is usually used to create payloads that are sent to
different servers, it's important to keep security in mind. For that
reason, the `--ignore` option was added. It can be used to keep certain
sensitive directives out of the payload output entirely.

For example, we always use the equivalent of this flag in the [NGINX Amplify
Agent](https://github.com/nginxinc/nginx-amplify-agent/) out of respect
for our users'
    privacy:

    --ignore=auth_basic_user_file,secure_link_secret,ssl_certificate_key,ssl_client_certificate,ssl_password_file,ssl_stapling_file,ssl_trusted_certificate

#### Schema

**Response Object**

```js
{
    "status": String, // "ok" or "failed" if "errors" is not empty
    "errors": Array,  // aggregation of "errors" from Config objects
    "config": Array   // Array of Config objects
}
```

**Config Object**

```js
{
    "file": String,   // the full path of the config file
    "status": String, // "ok" or "failed" if errors is not empty array
    "errors": Array,  // Array of Error objects
    "parsed": Array   // Array of Directive objects
}
```

**Directive Object**

```js
{
    "directive": String, // the name of the directive
    "line": Number,      // integer line number the directive started on
    "args": Array,       // Array of String arguments
    "includes": Array,   // Array of integers (included iff this is an include directive)
    "block": Array       // Array of Directive Objects (included iff this is a block)
}
```

<div class="note">

<div class="admonition-title">

Note

</div>

If this is an `include` directive and the `--single-file` flag was not
used, an `"includes"` value will be used that holds an Array of indices
of the configs that are included by this directive.

If this is a block directive, a `"block"` value will be used that holds
an Array of more Directive Objects that define the block context.

</div>

**Error Object**

```js
{
    "file": String,     // the full path of the config file
    "line": Number,     // integer line number the directive that caused the error
    "error": String,    // the error message
    "callback": Object  // only included iff an "onerror" function was passed to parse()
}
```

<div class="note">

<div class="admonition-title">

Note

</div>

If the `--tb-onerror` flag was used by crossplane parse, `"callback"`
will contain a string that represents the traceback that the error
caused.

</div>

#### Example

The main NGINX config file is at `/etc/nginx/nginx.conf`:

```nginx
events {
    worker_connections 1024;
}

http {
    include conf.d/*.conf;
}
```

And this config file is at `/etc/nginx/conf.d/servers.conf`:

```nginx
server {
    listen 8080;
    location / {
        try_files 'foo bar' baz;
    }
}

server {
    listen 8081;
    location / {
        return 200 'success!';
    }
}
```

So then if you run this:

    crossplane parse --indent=4 /etc/nginx/nginx.conf

The prettified JSON output would look like this:

```js
{
    "status": "ok",
    "errors": [],
    "config": [
        {
            "file": "/etc/nginx/nginx.conf",
            "status": "ok",
            "errors": [],
            "parsed": [
                {
                    "directive": "events",
                    "line": 1,
                    "args": [],
                    "block": [
                        {
                            "directive": "worker_connections",
                            "line": 2,
                            "args": [
                                "1024"
                            ]
                        }
                    ]
                },
                {
                    "directive": "http",
                    "line": 5,
                    "args": [],
                    "block": [
                        {
                            "directive": "include",
                            "line": 6,
                            "args": [
                                "conf.d/*.conf"
                            ],
                            "includes": [
                                1
                            ]
                        }
                    ]
                }
            ]
        },
        {
            "file": "/etc/nginx/conf.d/servers.conf",
            "status": "ok",
            "errors": [],
            "parsed": [
                {
                    "directive": "server",
                    "line": 1,
                    "args": [],
                    "block": [
                        {
                            "directive": "listen",
                            "line": 2,
                            "args": [
                                "8080"
                            ]
                        },
                        {
                            "directive": "location",
                            "line": 3,
                            "args": [
                                "/"
                            ],
                            "block": [
                                {
                                    "directive": "try_files",
                                    "line": 4,
                                    "args": [
                                        "foo bar",
                                        "baz"
                                    ]
                                }
                            ]
                        }
                    ]
                },
                {
                    "directive": "server",
                    "line": 8,
                    "args": [],
                    "block": [
                        {
                            "directive": "listen",
                            "line": 9,
                            "args": [
                                "8081"
                            ]
                        },
                        {
                            "directive": "location",
                            "line": 10,
                            "args": [
                                "/"
                            ],
                            "block": [
                                {
                                    "directive": "return",
                                    "line": 11,
                                    "args": [
                                        "200",
                                        "success!"
                                    ]
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    ]
}
```

#### crossplane parse (advanced)

This tool uses two flags that can change how `crossplane` handles
errors.

The first, `--no-catch`, can be used if you'd prefer that crossplane
quit parsing after the first error it finds.

The second, `--tb-onerror`, will add a `"callback"` key to all error
objects in the JSON output, each containing a string representation of
the traceback that would have been raised by the parser if the exception
had not been caught. This can be useful for logging purposes.

### crossplane build

This command will take a path to a file as input. The file should
contain a JSON representation of an NGINX config that has the structure
defined above. Saving and using the output from `crossplane parse` to
rebuild your config files should not cause any differences in content
except for the formatting.

```
usage: crossplane build [-h] [-d PATH] [-f] [-i NUM | -t] [--no-headers]
                        [--stdout] [-v]
                        filename

builds an nginx config from a json payload

positional arguments:
  filename              the file with the config payload

optional arguments:
  -h, --help            show this help message and exit
  -v, --verbose         verbose output
  -d PATH, --dir PATH   the base directory to build in
  -f, --force           overwrite existing files
  -i NUM, --indent NUM  number of spaces to indent output
  -t, --tabs            indent with tabs instead of spaces
  --no-headers          do not write header to configs
  --stdout              write configs to stdout instead
```

### crossplane lex

This command takes an NGINX config file, splits it into tokens by
removing whitespace and comments, and dumps the list of tokens as a JSON
array.

```
usage: crossplane lex [-h] [-o OUT] [-i NUM] [-n] filename

lexes tokens from an nginx config file

positional arguments:
  filename              the nginx config file

optional arguments:
  -h, --help            show this help message and exit
  -o OUT, --out OUT     write output to a file
  -i NUM, --indent NUM  number of spaces to indent output
  -n, --line-numbers    include line numbers in json payload
```

#### Example

Passing in this NGINX config file at `/etc/nginx/nginx.conf`:

```nginx
events {
    worker_connections 1024;
}

http {
    include conf.d/*.conf;
}
```

By running:

    crossplane lex /etc/nginx/nginx.conf

Will result in this JSON
output:

```js
["events","{","worker_connections","1024",";","}","http","{","include","conf.d/*.conf",";","}"]
```

However, if you decide to use the `--line-numbers` flag, your output
will look
like:

```js
[["events",1],["{",1],["worker_connections",2],["1024",2],[";",2],["}",3],["http",5],["{",5],["include",6],["conf.d/*.conf",6],[";",6],["}",7]]
```

### crossplane format

This is a quick and dirty tool that uses [crossplane
parse](#crossplane-parse) internally to format an NGINX config file.
It serves the purpose of demonstrating what you can do with `crossplane`'s
parsing abilities. It is not meant to be a fully fleshed out, feature-rich
formatting tool. If that is what you are looking for, then you may want to
look writing your own using crossplane's Python API.

```
usage: crossplane format [-h] [-o OUT] [-i NUM | -t] filename

formats an nginx config file

positional arguments:
  filename              the nginx config file

optional arguments:
  -h, --help            show this help message and exit
  -o OUT, --out OUT     write output to a file
  -i NUM, --indent NUM  number of spaces to indent output
  -t, --tabs            indent with tabs instead of spaces
```

### crossplane minify

This is a simple and fun little tool that uses [crossplane
lex](#crossplane-lex) internally to remove as much whitespace from an
NGINX config file as possible without affecting what it does. It can't
imagine it will have much of a use to most people, but it demonstrates
the kinds of things you can do with `crossplane`'s lexing abilities.

```
usage: crossplane minify [-h] [-o OUT] filename

removes all whitespace from an nginx config

positional arguments:
  filename           the nginx config file

optional arguments:
  -h, --help         show this help message and exit
  -o OUT, --out OUT  write output to a file
```

## Python Module

In addition to the command line tool, you can import `crossplane` as a
python module. There are two basic functions that the module will
provide you: `parse` and `lex`.

### crossplane.parse()

```python
import crossplane
payload = crossplane.parse('/etc/nginx/nginx.conf')
```

This will return the same payload as described in the [crossplane
parse](#crossplane-parse) section, except it will be Python dicts and
not one giant JSON string.

### crossplane.build()

```python
import crossplane
config = crossplane.build(
    [{
        "directive": "events",
        "args": [],
        "block": [{
            "directive": "worker_connections",
            "args": ["1024"]
        }]
    }]
)
```

This will return a single string that contains an entire NGINX config
file.

### crossplane.lex()

```python
import crossplane
tokens = crossplane.lex('/etc/nginx/nginx.conf')
```

`crossplane.lex` generates 2-tuples. Inserting these pairs into a list
will result in a long list similar to what you can see in the
[crossplane lex](#crossplane-lex) section when the `--line-numbers` flag
is used, except it will obviously be a Python list of tuples and not one
giant JSON string.

## Other Languages

- Ruby port by [@gdanko](https://github.com/gdanko):
    <https://github.com/gdanko/crossplane>
