Full Code of Blacksmoke16/oq for AI

master 5d2f04797a49 cached
32 files
103.1 KB
29.6k tokens
1 requests
Download .txt
Repository: Blacksmoke16/oq
Branch: master
Commit: 5d2f04797a49
Files: 32
Total size: 103.1 KB

Directory structure:
gitextract_301sijum/

├── .editorconfig
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── ci.yml
│       └── deployment.yml
├── .gitignore
├── LICENSE
├── README.md
├── shard.yml
├── snap/
│   └── snapcraft.yaml
├── spec/
│   ├── assets/
│   │   ├── data1.json
│   │   ├── data1.yml
│   │   ├── data2.json
│   │   ├── data2.yml
│   │   ├── raw.json
│   │   ├── stream-data.json
│   │   ├── stream-filter
│   │   ├── test.jq
│   │   └── test_filter
│   ├── converters/
│   │   ├── simple_yaml_spec.cr
│   │   ├── xml_spec.cr
│   │   └── yaml_spec.cr
│   ├── format_spec.cr
│   ├── oq_spec.cr
│   ├── processor_spec.cr
│   └── spec_helper.cr
└── src/
    ├── converters/
    │   ├── json.cr
    │   ├── processor_aware.cr
    │   ├── simple_yaml.cr
    │   ├── xml.cr
    │   └── yaml.cr
    ├── oq.cr
    └── oq_cli.cr

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*.cr]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true


================================================
FILE: .github/dependabot.yml
================================================
version: 2

updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "monthly"
    labels:
      - "kind:infrastructure"
      - "kind:enhancement"


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  pull_request:
    branches:
      - 'master'
  schedule:
    - cron: '0 21 * * *'

jobs:
  check_format:
    runs-on: ubuntu-latest
    container:
      image: crystallang/crystal:latest-alpine
    steps:
      - uses: actions/checkout@v6
      - name: Format
        run: crystal tool format --check
  coding_standards:
    runs-on: ubuntu-latest
    container:
      image: crystallang/crystal:latest
    steps:
      - uses: actions/checkout@v6
      - name: Install Dependencies
        run: shards install
      - name: Ameba
        run: ./bin/ameba
  test:
    strategy:
      fail-fast: false
      matrix:
        os:
          - ubuntu-latest
          - macos-latest
        crystal:
          - latest
          - nightly
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v6
      - name: Install Crystal
        uses: crystal-lang/install-crystal@v1
        with:
          crystal: ${{ matrix.crystal }}
      - name: Build
        run: shards build --production
      - name: Specs
        run: crystal spec --order=random --error-on-warnings


================================================
FILE: .github/workflows/deployment.yml
================================================
name: Deployment

on:
  release:
    types:
      - created

jobs:
  dist_linux:
    runs-on: ubuntu-latest
    container:
      image: crystallang/crystal:latest-alpine
    steps:
    - uses: actions/checkout@v6
    - name: Update Libs
      run: apk add --update --upgrade --no-cache --force-overwrite libxml2-dev yaml-dev yaml-static
    - name: Build
      run: shards build --production --release --static --no-debug --link-flags="-s -Wl,-z,relro,-z,now"
    - name: Upload
      uses: actions/upload-release-asset@v1.0.2
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        upload_url: ${{ github.event.release.upload_url }}
        asset_path: ./bin/oq
        asset_name: oq-${{ github.event.release.tag_name }}-linux-x86_64
        asset_content_type: binary/octet-stream
  dist_snap:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - name: Build Snap
        uses: snapcore/action-build@v1
        id: build
      - name: Publish Snap
        uses: snapcore/action-publish@v1
        env:
          SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.STORE_LOGIN }}
        with:
          snap: ${{ steps.build.outputs.snap }}
          release: stable
  dist_homebrew:
    runs-on: macos-latest
    steps:
      - run: git config --global user.email "george@dietrich.app"
      - run: git config --global user.name "George Dietrich"
      - name: Bump Formula
        uses: Homebrew/actions/bump-formulae@b5d9170bc1edf1103e40226592b5842b783dd1e0
        with:
          token: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }}
          formulae: oq
  deploy_docs:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    permissions:
      contents: read
      pages: write
      id-token: write
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - name: Install Crystal
        uses: crystal-lang/install-crystal@v1
      - name: Build
        run: crystal docs
      - name: Setup Pages
        uses: actions/configure-pages@v6
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v4
        with:
          path: 'docs/'
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v5


================================================
FILE: .gitignore
================================================
*.dwarf
*.snap
/.shards/
/bin/
/docs/
/lib/

# Libraries don't need dependency lock
# Dependencies will be locked in application that uses them
/shard.lock


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2021 George Dietrich

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
# oq

[![Built with Crystal](https://img.shields.io/badge/built%20with-crystal-000000.svg?logo=crystal)](https://crystal-lang.org/)
[![CI](https://github.com/blacksmoke16/oq/workflows/CI/badge.svg)](https://github.com/blacksmoke16/oq/actions?query=workflow%3ACI)
[![Latest release](https://img.shields.io/github/release/blacksmoke16/oq.svg?color=teal&logo=github)](https://github.com/blacksmoke16/oq/releases)
[![oq](https://snapcraft.io/oq/badge.svg)](https://snapcraft.io/oq)
[![oq](https://img.shields.io/aur/version/oq?label=oq&logo=arch-linux)](https://aur.archlinux.org/packages/oq/)
[![oq-bin](https://img.shields.io/aur/version/oq-bin?label=oq-bin&logo=arch-linux)](https://aur.archlinux.org/packages/oq-bin/)

A performant, portable [jq](https://github.com/stedolan/jq/) wrapper that facilitates the consumption and output of formats other than JSON; using `jq` filters to transform the data.

* Compiles to a single binary for easy portability.
* Performant, similar performance with JSON data compared to `jq`.  Slightly longer execution time when going to/from a non-JSON format.
* Supports various other input/output [formats](https://blacksmoke16.github.io/oq/OQ/Format.html), such as `XML` and `YAML`.
* Can be used as a dependency within other Crystal projects.

## Installation

### Linux

A statically linked binary for Linux `x86_64` as available on the [Releases](https://github.com/Blacksmoke16/oq/releases) tab.  Additionally it can also be installed via various package managers.

#### Snapcraft

For more on installing & using `snap` with your Linux distribution, see the [official documentation](https://docs.snapcraft.io/installing-snapd).

```sh
sudo snap install oq
```

#### Arch Linux

Using [yay](https://github.com/Jguer/yay):

```sh
yay -S oq
```

A pre-compiled version is also available:

```sh
yay -S oq-bin
```

### macOS

```sh
brew install oq
```

### From Source

If building from source, `jq` will need to be installed separately. Installation instructions can be found in the [official documentation](https://stedolan.github.io/jq/).

Requires Crystal to be installed, see the [installation documentation](https://crystal-lang.org/install).

```sh
git clone https://github.com/Blacksmoke16/oq.git
cd oq/
shards build --production --release
```

The built binary will be available as `./bin/oq`.  This can be relocated elsewhere on your machine; be sure it is in your `PATH` to access it as `oq`.

### Docker

`oq` can easily be included into a Docker image by fetching the static binary from Github for the version of `oq` that you want.

```dockerfile
# Set an arg to store the oq version that should be installed.
ARG OQ_VERSION=1.3.5

# Grab the binary from the latest Github release and make it executable; placing it within /usr/local/bin.  Can also put it elsewhere if you so desire.
RUN wget https://github.com/Blacksmoke16/oq/releases/download/v${OQ_VERSION}/oq-v${OQ_VERSION}-linux-x86_64 -O /usr/local/bin/oq && chmod +x /usr/local/bin/oq

# Or using curl (needs to follow Github's redirect):
RUN curl -L -o /usr/local/bin/oq https://github.com/Blacksmoke16/oq/releases/download/v${OQ_VERSION}/oq-v${OQ_VERSION}-linux-x86_64 && chmod +x /usr/local/bin/oq

# Also be sure to install jq if it is not already!
```

### Existing Crystal Project

Add the following to your `shard.yml` and run `shards install`.

```yaml
dependencies:
  oq:
    github: blacksmoke16/oq
    version: ~> 1.3.0
```

## Usage

### CLI

Use the `oq` binary, with a few optional custom arguments, see `oq --help`.  All other arguments get passed to `jq`. See [jq manual](https://stedolan.github.io/jq/manual/) for details.

### Library

Checkout the [API Documentation](https://blacksmoke16.github.io/oq/OQ/Processor.html) for using `oq` within an existing Crystal project.

### Examples

Consume JSON and output XML

```sh
$ echo '{"name": "Jim"}' | oq -o xml .
<?xml version="1.0" encoding="UTF-8"?>
<root>
  <name>Jim</name>
</root>
```

Consume YAML from a file and output XML

data.yaml

```yaml
---
name: Jim
numbers:
  - 1
  - 2
  - 3
```

```sh
$ oq -i yaml -o xml . data.yaml
<?xml version="1.0" encoding="UTF-8"?>
<root>
  <name>Jim</name>
  <numbers>1</numbers>
  <numbers>2</numbers>
  <numbers>3</numbers>
</root>
```

Use `oq` as a library, consuming some raw `JSON` input, convert it to `YAML`, and write the transformed data to a file.

```crystal
require "oq"

# This could be any `IO`, e.g. an `HTTP` request body, etc.
input_io = IO::Memory.new %({"name":"Jim"})

# Create a processor, specifying that we want the output format to be `YAML`.
processor = OQ::Processor.new output_format: :yaml

File.open("./out.yml", "w") do |file|
  # Process the data using our custom input and output IOs.
  # The first argument represents the input arguments;
  # i.e. the filter and/or any other arguments that should be passed to `jq`.
  processor.process ["."], input: input_io, output: file
end
```

## Contributing

1. Fork it (<https://github.com/Blacksmoke16/oq/fork>)
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request

## Contributors

- [George Dietrich](https://github.com/Blacksmoke16) - creator, maintainer
- [Michael Springer](https://github.com/sprngr) - contributor


================================================
FILE: shard.yml
================================================
name: oq

description: |
  A performant, and portable jq wrapper that facilitates the consumption and output of formats other than JSON; using jq filters to transform the data.

version: 1.3.5

authors:
  - George Dietrich <dev@dietrich.pub>

crystal: ~> 1.4

license: MIT

targets:
  oq:
    main: src/oq_cli.cr

development_dependencies:
  ameba:
    github: crystal-ameba/ameba
    version: ~> 1.5.0


================================================
FILE: snap/snapcraft.yaml
================================================
name: oq
version: '1.3.5'
summary: A performant, and portable jq wrapper to support formats other than JSON
description: |
  A performant, and portable jq wrapper that facilitates the consumption and output of formats other than JSON; using jq filters to transform the data.

contact: george@dietrich.app
issues: https://github.com/Blacksmoke16/oq/issues
website: https://github.com/Blacksmoke16/oq
source-code: https://github.com/Blacksmoke16/oq.git
license: MIT

grade: stable
confinement: strict
base: core20
type: app

apps:
  oq:
    command: bin/oq
    plugs:
      - home
      - removable-media

parts:
  oq:
    plugin: crystal
    crystal-build-options:
      - --release
      - --no-debug
      - '--link-flags=-s -Wl,-z,relro,-z,now'
    source: ./
    stage-packages:
      - jq
    override-pull: |
      snapcraftctl pull
      rm -rf $SNAPCRAFT_PART_SRC/lib $SNAPCRAFT_PART_SRC/bin


================================================
FILE: spec/assets/data1.json
================================================
{"name": "Jim"}


================================================
FILE: spec/assets/data1.yml
================================================
---
name: Jim


================================================
FILE: spec/assets/data2.json
================================================
{"name": "Bob"}


================================================
FILE: spec/assets/data2.yml
================================================
age: 17
name: Fred


================================================
FILE: spec/assets/raw.json
================================================
1
2
3


================================================
FILE: spec/assets/stream-data.json
================================================
{"machine": "possible_victim01", "domain": "evil.com", "timestamp":1435071870}
{"machine": "possible_victim01", "domain": "evil.com", "timestamp":1435071875}
{"machine": "possible_victim01", "domain": "soevil.com", "timestamp":1435071877}
{"machine": "possible_victim02", "domain": "bad.com", "timestamp":1435071877}
{"machine": "possible_victim03", "domain": "soevil.com", "timestamp":1435071879}


================================================
FILE: spec/assets/stream-filter
================================================
reduce inputs as $line
  ({};
   $line.machine as $machine
   | $line.domain as $domain
   | .[$machine].total as $total
   | .[$machine].evildoers as $evildoers
   | . + { ($machine): {"total": (1 + $total),
                        "evildoers": ($evildoers | (.[$domain] += 1)) }} )


================================================
FILE: spec/assets/test.jq
================================================
def increment: . + 1;


================================================
FILE: spec/assets/test_filter
================================================
.name

================================================
FILE: spec/converters/simple_yaml_spec.cr
================================================
require "../spec_helper"

# Essentially copied from the `YAML` spec, minus the `with anchors` test.
#
# TODO: Allow the test code to be more easily shared.
describe OQ::Converters::SimpleYAML do
  describe ".deserialize" do
    describe String do
      describe "not blank" do
        it "should output correctly" do
          run_binary(%(--- Jim), args: ["-i", "simpleyaml", "."]) do |output|
            output.should eq %("Jim"\n)
          end
        end
      end

      describe "blank" do
        it "should output correctly" do
          run_binary(%(--- ), args: ["-i", "simpleyaml", "."]) do |output|
            output.should eq "null\n"
          end
        end
      end

      describe "with a tag" do
        it "should output correctly" do
          run_binary(%(--- !!str 0.5), args: ["-i", "simpleyaml", "."]) do |output|
            output.should eq %("0.5"\n)
          end
        end
      end

      describe "that is single quoted" do
        it "should output correctly" do
          run_binary(%(---\nhowever: 'foobar'), args: ["-i", "simpleyaml", "-c", "."]) do |output|
            output.should eq %({"however":"foobar"}\n)
          end
        end
      end

      describe "that is double quoted" do
        it "should output correctly" do
          run_binary(%(---\nhowever: "foobar"), args: ["-i", "simpleyaml", "-c", "."]) do |output|
            output.should eq %({"however":"foobar"}\n)
          end
        end
      end

      describe "literal block" do
        it "should output correctly" do
          run_binary(LITERAL_BLOCK, args: ["-i", "simpleyaml", "-c", "."]) do |output|
            output.should eq %({"literal_block":"This entire block of text will be the value of the 'literal_block' key,\\nwith line breaks being preserved.\\n\\nThe literal continues until de-dented, and the leading indentation is\\nstripped.\\n\\n    Any lines that are 'more-indented' keep the rest of their indentation -\\n    these lines will be indented by 4 spaces."}\n)
          end
        end
      end

      describe "folded block" do
        it "should output correctly" do
          run_binary(FOLDED_BLOCK, args: ["-i", "simpleyaml", "-c", "."]) do |output|
            output.should eq %({"folded_style":"This entire block of text will be the value of 'folded_style', but this time, all newlines will be replaced with a single space.\\nBlank lines, like above, are converted to a newline character.\\n\\n    'More-indented' lines keep their newlines, too -\\n    this text will appear over two lines."}\n)
          end
        end
      end
    end

    describe Bool do
      it "should output correctly" do
        run_binary(%(--- true), args: ["-i", "simpleyaml", "."]) do |output|
          output.should eq "true\n"
        end
      end
    end

    describe Float do
      it "should output correctly" do
        run_binary(%(--- 10.50), args: ["-i", "simpleyaml", "."]) do |output|
          output.should eq "10.5\n"
        end
      end
    end

    describe Nil do
      it "should output correctly" do
        run_binary(%(--- ), args: ["-i", "simpleyaml", "."]) do |output|
          output.should eq "null\n"
        end
      end
    end

    describe Object do
      describe "a simple object" do
        it "should output correctly" do
          run_binary(%(---\nname: Jim), args: ["-i", "simpleyaml", "-c", "."]) do |output|
            output.should eq %({"name":"Jim"}\n)
          end
        end
      end

      describe "with spaces in the key" do
        it "should output correctly" do
          run_binary(%(---\nkey with spaces: value), args: ["-i", "simpleyaml", "-c", "."]) do |output|
            output.should eq %({"key with spaces":"value"}\n)
          end
        end
      end

      describe "with a quoted key key" do
        it "should output correctly" do
          run_binary(%(---\n'Keys can be quoted too.': value), args: ["-i", "simpleyaml", "-c", "."]) do |output|
            output.should eq %({"Keys can be quoted too.":"value"}\n)
          end
        end
      end

      describe "with nested object" do
        it "should output correctly" do
          run_binary(NESTED_OBJECT, args: ["-i", "simpleyaml", "-c", "."]) do |output|
            output.should eq %({"a_nested_map":{"key":"value","another_key":"Another Value","another_nested_map":{"hello":"hello"}}}\n)
          end
        end
      end

      describe "with a non string key" do
        it "should output correctly" do
          run_binary(%(---\n0.25: a float key), args: ["-i", "simpleyaml", "-c", "."]) do |output|
            output.should eq %({"0.25":"a float key"}\n)
          end
        end
      end

      describe "with JSON syntax" do
        describe "with quotes" do
          it "should output correctly" do
            run_binary(%(---\njson_seq: {"key": "value"}), args: ["-i", "simpleyaml", "-c", "."]) do |output|
              output.should eq %({"json_seq":{"key":"value"}}\n)
            end
          end
        end

        describe "without quotes" do
          it "should output correctly" do
            run_binary(%(---\njson_seq: {key: value}), args: ["-i", "simpleyaml", "-c", "."]) do |output|
              output.should eq %({"json_seq":{"key":"value"}}\n)
            end
          end
        end
      end

      describe "with a complex mapping key" do
        it "should output correctly" do
          run_binary(COMPLEX_MAPPING_KEY, args: ["-i", "simpleyaml", "-c", "."]) do |output|
            output.should eq %({"This is a key\\nthat has multiple lines\\n":"and this is its value"}\n)
          end
        end
      end

      describe "with set notation" do
        it "should output correctly" do
          run_binary(%(---\nset:\n  ? item1\n  ? item2), args: ["-i", "simpleyaml", "-c", "."]) do |output|
            output.should eq %({"set":{"item1":null,"item2":null}}\n)
          end
        end
      end

      pending "with a complex sequence key" do
        it "should output correctly" do
          run_binary(COMPLEX_SEQUENCE_KEY, args: ["-i", "simpleyaml", "-c", "."]) do |output|
            output.should eq %({"["Manchester United", "Real Madrid"]":["2001-01-01T00:00:00Z","2002-02-02T00:00:00Z"]}\n)
          end
        end
      end
    end

    describe Array do
      describe "with mixed/nested array values" do
        it "should output correctly" do
          run_binary(NESTED_ARRAY, args: ["-i", "simpleyaml", "-c", "."]) do |output|
            output.should eq %({"a_sequence":["Item 1","Item 2",0.5,"Item 4",{"key":"value","another_key":"another_value"},["This is a sequence","inside another sequence"],[["Nested sequence indicators","can be collapsed"]]]}\n)
          end
        end
      end

      describe "with JSON syntax" do
        describe "with quotes" do
          it "should output correctly" do
            run_binary(%(---\njson_seq: [3, 2, 1, "takeoff"]), args: ["-i", "simpleyaml", "-c", "."]) do |output|
              output.should eq %({"json_seq":[3,2,1,"takeoff"]}\n)
            end
          end
        end

        describe "without quotes" do
          it "should output correctly" do
            run_binary(%(---\njson_seq: [3, 2, 1, takeoff]), args: ["-i", "simpleyaml", "-c", "."]) do |output|
              output.should eq %({"json_seq":[3,2,1,"takeoff"]}\n)
            end
          end
        end
      end
    end
  end

  describe ".serialize" do
    describe String do
      describe "not blank" do
        it "should output correctly" do
          run_binary(%("Jim"), args: ["-o", "simpleyaml", "."]) do |output|
            output.should start_with <<-YAML
            --- Jim
            YAML
          end
        end
      end

      describe "blank" do
        it "should output correctly" do
          run_binary(%(""), args: ["-o", "simpleyaml", "."]) do |output|
            output.should eq(<<-YAML
              --- ""\n
              YAML
            )
          end
        end
      end
    end

    describe Bool do
      it "should output correctly" do
        run_binary(%(true), args: ["-o", "simpleyaml", "."]) do |output|
          output.should start_with <<-YAML
            --- true
            YAML
        end
      end
    end

    describe Float do
      it "should output correctly" do
        run_binary(%("1.5"), args: ["-o", "simpleyaml", "."]) do |output|
          output.should eq(<<-YAML
            --- "1.5"\n
            YAML
          )
        end
      end
    end

    describe Nil do
      it "should output correctly" do
        run_binary("null", args: ["-o", "simpleyaml", "."]) do |output|
          output.should start_with "---"
        end
      end
    end

    describe Array do
      describe "empty array on root" do
        it "should emit a self closing root tag" do
          run_binary("[]", args: ["-o", "simpleyaml", "."]) do |output|
            output.should eq(<<-YAML
              --- []\n
              YAML
            )
          end
        end
      end

      describe "array with values on root" do
        it "should emit item tags for non empty values" do
          run_binary(%(["x",{}]), args: ["-o", "simpleyaml", "."]) do |output|
            output.should eq(<<-YAML
              ---
              - x
              - {}\n
              YAML
            )
          end
        end
      end

      describe "object with empty array/values" do
        it "should emit self closing tags for each" do
          run_binary(%({"a":[],"b":{},"c":null}), args: ["-o", "simpleyaml", "."]) do |output|
            output.should start_with <<-YAML
              ---
              a: []
              b: {}
              c:
              YAML
          end
        end
      end

      describe "2D array object value" do
        it "should emit key name tag then self closing item tag" do
          run_binary(%({"a":[[]]}), args: ["-o", "simpleyaml", "."]) do |output|
            output.should eq(<<-YAML
              ---
              a:
              - []\n
              YAML
            )
          end
        end
      end

      describe "object value mixed/nested array values" do
        it "should emit correctly" do
          run_binary(%({"x":[1,[2,[3]]]}), args: ["-o", "simpleyaml", "."]) do |output|
            output.should eq(<<-YAML
              ---
              x:
              - 1
              - - 2
                - - 3\n
              YAML
            )
          end
        end
      end

      describe "object value array primitive values" do
        it "should emit correctly" do
          run_binary(%({"x":[1,2,3]}), args: ["-o", "simpleyaml", "."]) do |output|
            output.should eq(<<-YAML
              ---
              x:
              - 1
              - 2
              - 3\n
              YAML
            )
          end
        end
      end

      describe "when the jq filter doesn't return data" do
        it "should return an empty string" do
          run_binary(%([{"name":"foo"}]), args: ["-i", "simpleyaml", "-o", "simpleyaml", %<.[] | select(.name != "foo")>]) do |output|
            output.should be_empty
          end
        end
      end
    end

    describe Object do
      describe "simple key/value" do
        it "should output correctly" do
          run_binary(%({"name":"Jim"}), args: ["-o", "simpleyaml", "."]) do |output|
            output.should eq(<<-YAML
              ---
              name: Jim\n
              YAML
            )
          end
        end
      end

      describe "nested object" do
        it "should output correctly" do
          run_binary(%({"name":"Jim", "city": {"street":"forbs"}}), args: ["-o", "simpleyaml", "."]) do |output|
            output.should eq(<<-YAML
              ---
              name: Jim
              city:
                street: forbs\n
              YAML
            )
          end
        end
      end
    end
  end
end


================================================
FILE: spec/converters/xml_spec.cr
================================================
require "../spec_helper"

WITH_WHITESPACE = <<-XML
<item>
  <flagID>0</flagID>
  <itemID>0</itemID>
  <locationID>0</locationID>
  <ownerID>0</ownerID>
  <quantity>-1</quantity>
  <typeID>0</typeID>
</item>
XML

XML_SCALAR_ARRAY = <<-XML
<?xml version="1.0" encoding="utf-8"?>
<items>
  <number>1</number>
  <number>2</number>
  <number>3</number>
</items>
XML

XML_SCALAR_ARRAY_WITH_ATTRIBUTE = <<-XML
<?xml version="1.0" encoding="utf-8"?>
<items>
  <number>1</number>
  <number>2</number>
  <number foo="bar">3</number>
</items>
XML

XML_CDATA = <<-XML
<desc><![CDATA[<message>Some Description</message>]]></desc>
XML

XML_OBJECT_ARRAY = <<-XML
<?xml version="1.0" encoding="utf-8"?>
<items>
  <item>
    <flagID>0</flagID>
    <itemID>0</itemID>
    <locationID>0</locationID>
    <ownerID>0</ownerID>
    <quantity>-1</quantity>
    <typeID>0</typeID>
  </item>
  <item>
    <flagID>0</flagID>
    <itemID>1</itemID>
    <locationID>0</locationID>
    <ownerID>0</ownerID>
    <quantity>-1</quantity>
    <typeID>0</typeID>
  </item>
</items>
XML

XML_NESTED_OBJECT_ARRAY = <<-XML
<?xml version='1.0' ?>
<!DOCTYPE root SYSTEM "http://www.cs.washington.edu/research/projects/xmltk/xmldata/data/auctions/ebay.dtd">
<root>
 <listing>
   <seller_info>
       <seller_name> cubsfantony</seller_name>
       <seller_rating> 848</seller_rating>
   </seller_info>
   <payment_types>Visa/MasterCard, Money Order/Cashiers Checks, Personal Checks, See item description for payment methods accepted</payment_types>
</listing>
<listing>
   <seller_info>
       <seller_name> ct-inc</seller_name>
       <seller_rating> 403</seller_rating>
   </seller_info>
   <payment_types>Visa/MasterCard, Discover, Money Order/Cashiers Checks, Personal Checks, See item description for payment methods accepted</payment_types>
</listing>
</root>
XML

XML_INLINE_ARRAY = <<-XML
<article key="tr/ibm/RJ2144">
  <author>E. F. Codd</author>
  <author>Robert S. Arnold</author>
  <author>Jean-Marc Cadiou</author>
  <author>Chin-Liang Chang</author>
  <author>Nick Roussopoulos</author>
  <title>RENDEZVOUS Version 1: An Experimental English Language Query Formulation System for Casual Users of Relational Data Bases.</title>
  <journal>IBM Research Report</journal>
  <volume>RJ2144</volume>
  <month>January</month>
  <year>1978</year>
  <ee>db/labs/ibm/RJ2144.html</ee>
  <cdrom>ibmTR/rj2144.pdf</cdrom>
</article>
XML

XML_INLINE_ARRAY_WITHIN_ARRAY = <<-XML
<articles>
  <article key="tr/dec/SRC1997-018">
    <year>1997</year>
    <ee>db/labs/dec/SRC1997-018.html</ee>
    <ee>http://www.mcjones.org/System_R/SQL_Reunion_95/</ee>
  </article>
  <article key="tr/gte/TR-0263-08-94-165">
    <ee>db/labs/gte/TR-0263-08-94-165.html</ee>
    <year>1994</year>
  </article>
</articles>
XML

XML_DOCTYPE = <<-XML
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE dblp SYSTEM "dblp.dtd">
<dblp>
  <mastersthesis key="ms/Brown92">
    <author>Kurt P. Brown</author>
    <title>PRPL: A Database Workload Specification Language, v1.3.</title>
    <year>1992</year>
    <school>Univ. of Wisconsin-Madison</school>
  </mastersthesis>
</dblp>
XML

XML_ATTRIBUTE_IN_ARRAY = <<-XML
<jobs>
  <ad>
    <salary currency="CAD">80000</salary>
    <working_hours>full-time</working_hours>
  </ad>
  <ad>
    <working_hours>full-time</working_hours>
  </ad>
</jobs>
XML

XML_ATTRIBUTE_IN_ARRAY_ROOT_ELEMENT = <<-XML
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE dblp SYSTEM "dblp.dtd">
<dblp>
  <mastersthesis key="ms/Brown92">
    <author>Kurt P. Brown</author>
  </mastersthesis>
  <mastersthesis key="ms/Yurek97">
    <author>Tolga Yurek</author>
  </mastersthesis>
</dblp>
XML

XML_ALL_EMPTY = <<-XML
<root>
  <one> </one>
  <two>
  </two>
  <three/>
  <four></four>
</root>
XML

XML_NAMESPACE_ARRAY = <<-XML
<?xml version="1.0" encoding="utf-8"?>
<items xmlns:n="http://n">
  <n:number>1</n:number>
  <n:number>2</n:number>
  <number xmlns="http://default">3</number>
</items>
XML

XML_NAMESPACE_ARRAY_SCALAR_VALUE_PREFIX = <<-XML
<?xml version="1.0" encoding="utf-8"?>
<items xmlns:n="http://n">
  <n:number>1</n:number>
  <n:number>2</n:number>
  <n:number xmlns="http://default">3</n:number>
</items>
XML

XML_NAMESPACE_PREFIXES = <<-XML
<?xml version="1.0" ?>
<root xmlns:a="https://a">
  <foo>foo</foo>
  <a:bar>bar</a:bar>
</root>
XML

XML_NESTED_NAMESPACES = <<-XML
<?xml version="1.0" ?>
<root xmlns:a="https://a" xmlns="https://b">
  <a:foo>herp</a:foo>
  <foo>
    <bar xmlns="https://c">
      <baz xmlns="https://d"/>
    </bar>
  </foo>
</root>
XML

describe OQ::Converters::XML do
  describe ".deserialize" do
    # See https://www.xml.com/pub/a/2006/05/31/converting-between-xml-and-json.html
    describe "conventions" do
      describe "an empty element" do
        it "self closing" do
          run_binary("<e/>", args: ["-i", "xml", "-c", "."]) do |output|
            output.should eq %({"e":null}\n)
          end
        end

        it "non self closing" do
          run_binary("<e></e>", args: ["-i", "xml", "-c", "."]) do |output|
            output.should eq %({"e":null}\n)
          end
        end
      end

      it "an element with pure text content" do
        run_binary("<e>text</e>", args: ["-i", "xml", "-c", "."]) do |output|
          output.should eq %({"e":"text"}\n)
        end
      end

      it "an empty element with attributes" do
        run_binary(%(<e name="value" />), args: ["-i", "xml", "-c", "."]) do |output|
          output.should eq %({"e":{"@name":"value"}}\n)
        end
      end

      it "an element with pure text content and attributes" do
        run_binary(%(<e name="value">text</e>), args: ["-i", "xml", "-c", "."]) do |output|
          output.should eq %({"e":{"@name":"value","#text":"text"}}\n)
        end
      end

      it "an element containing elements with different names" do
        run_binary(%(<e> <a>text</a> <b>text</b> </e>), args: ["-i", "xml", "-c", "."]) do |output|
          output.should eq %({"e":{"a":"text","b":"text"}}\n)
        end
      end

      it "an element containing elements with identical names" do
        run_binary(%(<e> <a>text</a> <a>text</a> </e>), args: ["-i", "xml", "-c", "."]) do |output|
          output.should eq %({"e":{"a":["text","text"]}}\n)
        end
      end

      it "an element containing elements and contiguous text" do
        run_binary(%(<e>text<a>text</a></e>), args: ["-i", "xml", "-c", "."]) do |output|
          output.should eq %({"e":{"#text":"text","a":"text"}}\n)
        end
      end
    end

    describe "should raise if invalid" do
      it "should output correctly" do
        run_binary(%(<root id="1<child/></root>), args: ["-i", "xml", "-c", "."], success: false) do |_, _, error|
          error.should eq "oq error: Couldn't find end of Start Tag root\n"
        end
      end
    end

    describe Object do
      it "a key/value pair" do
        run_binary(%(<person>Fred</person>), args: ["-i", "xml", "-c", "."]) do |output|
          output.should eq %({"person":"Fred"}\n)
        end
      end

      describe "that has only empty children elements" do
        it "should output an object with null values" do
          run_binary(XML_ALL_EMPTY, args: ["-i", "xml", "-c", "."]) do |output|
            output.should eq %({"root":{"one":" ","two":"\\n  ","three":null,"four":null}}\n)
          end
        end
      end

      it "with whitespace" do
        run_binary(WITH_WHITESPACE, args: ["-i", "xml", "-c", "."]) do |output|
          output.should eq %({"item":{"flagID":"0","itemID":"0","locationID":"0","ownerID":"0","quantity":"-1","typeID":"0"}}\n)
        end
      end

      it "with the prolog" do
        run_binary(%(<?xml version="1.0" encoding="utf-8"?><item><typeID>0</typeID></item>), args: ["-i", "xml", "-c", "."]) do |output|
          output.should eq %({"item":{"typeID":"0"}}\n)
        end
      end

      it "a simple object" do
        run_binary(%(<person><firstname>Jane</firstname><lastname>Doe</lastname></person>), args: ["-i", "xml", "-c", "."]) do |output|
          output.should eq %({"person":{"firstname":"Jane","lastname":"Doe"}}\n)
        end
      end

      it "attributes" do
        run_binary(%(<person id="1" foo="bar"><firstname>Jane</firstname><lastname>Doe</lastname></person>), args: ["-i", "xml", "-c", "."]) do |output|
          output.should eq %({"person":{"@id":"1","@foo":"bar","firstname":"Jane","lastname":"Doe"}}\n)
        end
      end

      it "nested objects" do
        run_binary(%(<person><firstname>Jane</firstname><lastname>Doe</lastname><location><zip>15061</zip><address>123 Foo Street</address></location></person>), args: ["-i", "xml", "-c", "."]) do |output|
          output.should eq %({"person":{"firstname":"Jane","lastname":"Doe","location":{"zip":"15061","address":"123 Foo Street"}}}\n)
        end
      end

      it "complex object" do
        run_binary(%(<root><x a="1"><a>2</a></x><y b="3">4</y></root>), args: ["-i", "xml", "-c", "."]) do |output|
          output.should eq %({"root":{"x":{"@a":"1","a":"2"},"y":{"@b":"3","#text":"4"}}}\n)
        end
      end

      it "with mixed content" do
        run_binary(%(<root>x<y>z</y></root>), args: ["-i", "xml", "-c", ".root"]) do |output|
          output.should eq %({"#text":"x","y":"z"}\n)
        end
      end

      it "with an inline array" do
        run_binary(XML_INLINE_ARRAY, args: ["-i", "xml", "-c", "."]) do |output|
          output.should eq %({"article":{"@key":"tr/ibm/RJ2144","author":["E. F. Codd","Robert S. Arnold","Jean-Marc Cadiou","Chin-Liang Chang","Nick Roussopoulos"],"title":"RENDEZVOUS Version 1: An Experimental English Language Query Formulation System for Casual Users of Relational Data Bases.","journal":"IBM Research Report","volume":"RJ2144","month":"January","year":"1978","ee":"db/labs/ibm/RJ2144.html","cdrom":"ibmTR/rj2144.pdf"}}\n)
        end
      end

      it "with a doctype" do
        run_binary(XML_DOCTYPE, args: ["-i", "xml", "-c", "."]) do |output|
          output.should eq %({"dblp":{"mastersthesis":{"@key":"ms/Brown92","author":"Kurt P. Brown","title":"PRPL: A Database Workload Specification Language, v1.3.","year":"1992","school":"Univ. of Wisconsin-Madison"}}}\n)
        end
      end

      it "with CDATA" do
        run_binary(XML_CDATA, args: ["-i", "xml", "-c", "."]) do |output|
          output.should eq %({"desc":"<message>Some Description</message>"}\n)
        end
      end

      it "with a prefixed key" do
        run_binary(%(<?xml version="1.0"?><a:foo>bar</a:foo>), args: ["-i", "xml", "-c", "."]) do |output|
          output.should eq %({"a:foo":"bar"}\n)
        end
      end

      describe "with namespaces" do
        describe "without --xmlns" do
          it "retains prefixes but strips namespace declarations of a prefixed namespace" do
            run_binary(%(<?xml version="1.0"?><a:foo xmlns:a="http://www.w3.org/1999/xhtml">bar</a:foo>), args: ["-i", "xml", "-c", "."]) do |output|
              output.should eq %({"a:foo":"bar"}\n)
            end
          end

          it "does not add pefix if none was already present but strips namespace declarations" do
            run_binary(%(<?xml version="1.0"?><foo xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:a="http://www.w3.org/1999/xhtml">bar</foo>), args: ["-i", "xml", "-c", "."]) do |output|
              output.should eq %({"foo":"bar"}\n)
            end
          end

          it "adds namespace attribute properties only to declaring element and handles differentiating prefixed elements" do
            run_binary(XML_NESTED_NAMESPACES, args: ["-i", "xml", "-c", "."]) do |output|
              output.should eq %({"root":{"a:foo":"herp","foo":{"bar":{"baz":null}}}}\n)
            end
          end

          it "retains prefixes of scalar value elements" do
            run_binary(XML_NAMESPACE_PREFIXES, args: ["-i", "xml", "-c", "."]) do |output|
              output.should eq %({"root":{"foo":"foo","a:bar":"bar"}}\n)
            end
          end

          describe "with --xml-namespace-alias" do
            it "should error" do
              run_binary(%(<?xml version="1.0"?><a:foo xmlns:a="https://a-namespace">bar</a:foo>), args: ["-i", "xml", "-c", "--xml-namespace-alias", "aa=https://a-namespace", "."], success: false) do |_, _, error|
                error.should start_with "oq error:"
              end
            end
          end
        end

        describe "with --xmlns" do
          it "creates a namespace attribute property" do
            run_binary(%(<?xml version="1.0"?><a:foo xmlns:a="http://www.w3.org/1999/xhtml">bar</a:foo>), args: ["-i", "xml", "-c", "--xmlns", "."]) do |output|
              output.should eq %({"a:foo":{"@xmlns:a":"http://www.w3.org/1999/xhtml","#text":"bar"}}\n)
            end
          end

          it "does not add pefix if none was already present and creates multiple namespace attribute properties" do
            run_binary(%(<?xml version="1.0"?><foo xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:a="http://www.w3.org/1999/xhtml">bar</foo>), args: ["-i", "xml", "-c", "--xmlns", "."]) do |output|
              output.should eq %({"foo":{"@xmlns":"urn:oasis:names:tc:SAML:2.0:metadata","@xmlns:a":"http://www.w3.org/1999/xhtml","#text":"bar"}}\n)
            end
          end

          it "treats prefixed & unprefixed elements as unique elements" do
            run_binary(XML_NESTED_NAMESPACES, args: ["-i", "xml", "-c", "--xmlns", "."]) do |output|
              output.should eq %({"root":{"@xmlns:a":"https://a","@xmlns":"https://b","a:foo":"herp","foo":{"bar":{"@xmlns":"https://c","baz":{"@xmlns":"https://d"}}}}}\n)
            end
          end

          it "retains prefixes of scalar value elements and adds a namespace attribute property" do
            run_binary(XML_NAMESPACE_PREFIXES, args: ["-i", "xml", "-c", "--xmlns", "."]) do |output|
              output.should eq %({"root":{"@xmlns:a":"https://a","foo":"foo","a:bar":"bar"}}\n)
            end
          end

          describe "with --xml-namespace-alias" do
            it "normalizes the provided namespace" do
              run_binary(%(<?xml version="1.0"?><a:foo xmlns:a="https://a-namespace">bar</a:foo>), args: ["-i", "xml", "-c", "--xmlns", "--xml-namespace-alias", "aa=https://a-namespace", "."]) do |output|
                output.should eq %({"aa:foo":{"@xmlns:aa":"https://a-namespace","#text":"bar"}}\n)
              end
            end

            it "normalizes the default namespace" do
              run_binary(%(<?xml version="1.0"?><foo xmlns="https://a-namespace">bar</foo>), args: ["-i", "xml", "-c", "--xmlns", "--xml-namespace-alias", "aa=https://a-namespace", "."]) do |output|
                output.should eq %({"aa:foo":{"@xmlns:aa":"https://a-namespace","#text":"bar"}}\n)
              end
            end

            it "normalizes multiple namespaces" do
              run_binary(XML_NESTED_NAMESPACES, args: ["-i", "xml", "-c", "--xmlns", "--xml-namespace-alias", "=https://a", "--xml-namespace-alias", "bb=https://b", "."]) do |output|
                output.should eq %({"bb:root":{"@xmlns":"https://a","@xmlns:bb":"https://b","foo":"herp","bb:foo":{"bar":{"@xmlns":"https://c","baz":{"@xmlns":"https://d"}}}}}\n)
              end
            end
          end
        end
      end
    end

    describe Array do
      it "of scalar values" do
        run_binary(XML_SCALAR_ARRAY, args: ["-i", "xml", "-c", "."]) do |output|
          output.should eq %({"items":{"number":["1","2","3"]}}\n)
        end
      end

      it "of scalar values with attribute" do
        run_binary(XML_SCALAR_ARRAY_WITH_ATTRIBUTE, args: ["-i", "xml", "-c", "."]) do |output|
          output.should eq %({"items":{"number":["1","2",{"@foo":"bar","#text":"3"}]}}\n)
        end
      end

      describe "of objects" do
        describe "with no nested objects" do
          it "should output correctly" do
            run_binary(XML_OBJECT_ARRAY, args: ["-i", "xml", "-c", "."]) do |output|
              output.should eq %({"items":{"item":[{"flagID":"0","itemID":"0","locationID":"0","ownerID":"0","quantity":"-1","typeID":"0"},{"flagID":"0","itemID":"1","locationID":"0","ownerID":"0","quantity":"-1","typeID":"0"}]}}\n)
            end
          end
        end

        describe "with an inline array" do
          it "should output correctly" do
            run_binary(XML_INLINE_ARRAY_WITHIN_ARRAY, args: ["-i", "xml", "-c", "."]) do |output|
              output.should eq %({"articles":{"article":[{"@key":"tr/dec/SRC1997-018","year":"1997","ee":["db/labs/dec/SRC1997-018.html","http://www.mcjones.org/System_R/SQL_Reunion_95/"]},{"@key":"tr/gte/TR-0263-08-94-165","ee":"db/labs/gte/TR-0263-08-94-165.html","year":"1994"}]}}\n)
            end
          end
        end

        describe "with nested objects" do
          it "should output correctly" do
            run_binary(XML_NESTED_OBJECT_ARRAY, args: ["-i", "xml", "-c", ".root.listing"]) do |output|
              output.should eq %([{"seller_info":{"seller_name":" cubsfantony","seller_rating":" 848"},"payment_types":"Visa/MasterCard, Money Order/Cashiers Checks, Personal Checks, See item description for payment methods accepted"},{"seller_info":{"seller_name":" ct-inc","seller_rating":" 403"},"payment_types":"Visa/MasterCard, Discover, Money Order/Cashiers Checks, Personal Checks, See item description for payment methods accepted"}]\n)
            end
          end
        end
      end

      describe "with object that has an attribute" do
        it "should output correctly" do
          run_binary(XML_ATTRIBUTE_IN_ARRAY, args: ["-i", "xml", "-c", "."]) do |output|
            output.should eq %({"jobs":{"ad":[{"salary":{"@currency":"CAD","#text":"80000"},"working_hours":"full-time"},{"working_hours":"full-time"}]}}\n)
          end
        end
      end

      describe "where array object element has an attribute" do
        it "should output correctly" do
          run_binary(XML_ATTRIBUTE_IN_ARRAY_ROOT_ELEMENT, args: ["-i", "xml", "-c", "."]) do |output|
            output.should eq %({"dblp":{"mastersthesis":[{"@key":"ms/Brown92","author":"Kurt P. Brown"},{"@key":"ms/Yurek97","author":"Tolga Yurek"}]}}\n)
          end
        end
      end

      describe "with namespaces" do
        describe "without --xmlns" do
          it "treats prefixed & unprefixed elements as unique elements" do
            run_binary(XML_NAMESPACE_ARRAY, args: ["-i", "xml", "-c", "."]) do |output|
              output.should eq %({"items":{"n:number":["1","2"],"number":"3"}}\n)
            end
          end

          it "ignores the namespace declaration" do
            run_binary(XML_NAMESPACE_ARRAY_SCALAR_VALUE_PREFIX, args: ["-i", "xml", "-c", "."]) do |output|
              output.should eq %({"items":{"n:number":["1","2","3"]}}\n)
            end
          end
        end

        describe "with --xmlns" do
          it "treats prefixed & unprefixed elements as unique elements, adding namespace attribute property as needed" do
            run_binary(XML_NAMESPACE_ARRAY, args: ["-i", "xml", "-c", "--xmlns", "."]) do |output|
              output.should eq %({"items":{"@xmlns:n":"http://n","n:number":["1","2"],"number":{"@xmlns":"http://default","#text":"3"}}}\n)
            end
          end

          it "expands the scalar value to include a namespace attribute property" do
            run_binary(XML_NAMESPACE_ARRAY_SCALAR_VALUE_PREFIX, args: ["-i", "xml", "-c", "--xmlns", "."]) do |output|
              output.should eq %({"items":{"@xmlns:n":"http://n","n:number":["1","2",{"@xmlns":"http://default","#text":"3"}]}}\n)
            end
          end

          describe "with --xml-namespace-alias" do
            it do
              run_binary(XML_NAMESPACE_ARRAY, args: ["-i", "xml", "-c", "--xmlns", "--xml-namespace-alias", "num=http://n", "."]) do |output|
                output.should eq %({"items":{"@xmlns:num":"http://n","num:number":["1","2"],"number":{"@xmlns":"http://default","#text":"3"}}}\n)
              end
            end

            it do
              run_binary(XML_NAMESPACE_ARRAY, args: ["-i", "xml", "-c", "--xmlns", "--xml-namespace-alias", "=http://n", "--xml-namespace-alias", "d=http://default", "."]) do |output|
                output.should eq %({"items":{"@xmlns":"http://n","number":["1","2"],"d:number":{"@xmlns:d":"http://default","#text":"3"}}}\n)
              end
            end
          end
        end
      end

      describe "with a single element" do
        it "without --xml-force-array" do
          run_binary(%(<foo><item/></foo>), args: ["-i", "xml", "-c", "."]) do |output|
            output.should eq %({"foo":{"item":null}}\n)
          end
        end

        describe "with --xml-force-array" do
          it "force parses it as an array" do
            run_binary(%(<foo><item/></foo>), args: ["-i", "xml", "--xml-force-array", "item", "-c", "."]) do |output|
              output.should eq %({"foo":{"item":[null]}}\n)
            end
          end

          it "with an attribute" do
            run_binary(%(<foo><item id="1"/></foo>), args: ["-i", "xml", "--xml-force-array", "item", "-c", "."]) do |output|
              output.should eq %({"foo":{"item":[{"@id":"1"}]}}\n)
            end
          end

          it "with a namespace" do
            run_binary(%(<foo><item xmlns="https://ns"/></foo>), args: ["-i", "xml", "--xmlns", "--xml-force-array", "item", "-c", "."]) do |output|
              output.should eq %({"foo":{"item":[{"@xmlns":"https://ns"}]}}\n)
            end
          end

          it "with an aliased namespace" do
            run_binary(%(<foo><i:item xmlns:i="https://ns"/></foo>), args: ["-i", "xml", "--xmlns", "--xml-force-array", "item:item", "--xml-namespace-alias", "item=https://ns", "-c", "."]) do |output|
              output.should eq %({"foo":{"item:item":[{"@xmlns:item":"https://ns"}]}}\n)
            end
          end
        end
      end
    end
  end

  describe ".serialize" do
    it "allows not emitting the xml prolog" do
      run_binary("1", args: ["-o", "xml", "--no-prolog", "."]) do |output|
        output.should eq(<<-XML
          <root>1</root>\n
          XML
        )
      end
    end

    describe "allows setting the root element" do
      describe "to another string" do
        it "should use the provided name" do
          run_binary("1", args: ["-o", "xml", "--xml-root", "foo", "."]) do |output|
            output.should eq(<<-XML
              <?xml version="1.0" encoding="UTF-8"?>
              <foo>1</foo>\n
              XML
            )
          end
        end
      end

      describe "to an empty string" do
        it "should not be emitted" do
          run_binary("1", args: ["-o", "xml", "--xml-root", "", "."]) do |output|
            output.should eq(<<-XML
              <?xml version="1.0" encoding="UTF-8"?>
              1
              XML
            )
          end
        end
      end
    end

    describe "it allows changing the array item name" do
      describe "with a single nesting level" do
        it "should emit item tags for non empty values" do
          run_binary(%(["x",{}]), args: ["-o", "xml", "--xml-item", "foo", "."]) do |output|
            output.should eq(<<-XML
                <?xml version="1.0" encoding="UTF-8"?>
                <root>
                  <foo>x</foo>
                  <foo/>
                </root>\n
                XML
            )
          end
        end
      end

      describe "with a larger nesting level" do
        it "should emit item tags for non empty values" do
          run_binary(%({"a":[[]]}), args: ["-o", "xml", "--xml-item", "foo", "."]) do |output|
            output.should eq(<<-XML
                <?xml version="1.0" encoding="UTF-8"?>
                <root>
                  <a/>
                </root>\n
                XML
            )
          end
        end
      end
    end

    describe "it allows changing the indent" do
      describe "more spaces" do
        it "should emit the extra spaces" do
          run_binary(%({"name": "Jim", "age": 12}), args: ["-o", "xml", "--indent", "4", "."]) do |output|
            output.should eq(<<-XML
                <?xml version="1.0" encoding="UTF-8"?>
                <root>
                    <name>Jim</name>
                    <age>12</age>
                </root>\n
                XML
            )
          end
        end
      end

      describe "to tabs" do
        it "should emit the indent as tabs" do
          run_binary(%({"name": "Jim", "age": 12}), args: ["-o", "xml", "--indent", "3", "--tab", "."]) do |output|
            output.should eq(<<-XML
                <?xml version="1.0" encoding="UTF-8"?>
                <root>
                \t\t\t<name>Jim</name>
                \t\t\t<age>12</age>
                </root>\n
                XML
            )
          end
        end
      end
    end

    describe String do
      describe "not blank" do
        it "should output correctly" do
          run_binary(%("Jim"), args: ["-o", "xml", "."]) do |output|
            output.should eq(<<-XML
              <?xml version="1.0" encoding="UTF-8"?>
              <root>Jim</root>\n
              XML
            )
          end
        end
      end

      describe "blank" do
        it "should output correctly" do
          run_binary(%(""), args: ["-o", "xml", "."]) do |output|
            output.should eq(<<-XML
            <?xml version="1.0" encoding="UTF-8"?>
            <root></root>\n
            XML
            )
          end
        end
      end

      describe "with HTML content" do
        it "should escape the HTMl content" do
          run_binary(%({"x":"<p>Hello World!</p>"}), args: ["-o", "xml", "."]) do |output|
            output.should eq(<<-XML
              <?xml version="1.0" encoding="UTF-8"?>
              <root>
                <x>&lt;p&gt;Hello World!&lt;/p&gt;</x>
              </root>\n
              XML
            )
          end
        end

        it "should be wrapped in CDATA if the json key starts with '!'" do
          run_binary(%({"!x":"<p>Hello World!</p>"}), args: ["-o", "xml", "."]) do |output|
            output.should eq(<<-XML
              <?xml version="1.0" encoding="UTF-8"?>
              <root>
                <x><![CDATA[<p>Hello World!</p>]]></x>
              </root>\n
              XML
            )
          end
        end

        it "should produce an empty CDATA if the json key starts with '!' and the value is null" do
          run_binary(%({"!x":null}), args: ["-o", "xml", "."]) do |output|
            output.should eq(<<-XML
              <?xml version="1.0" encoding="UTF-8"?>
              <root>
                <x><![CDATA[]]></x>
              </root>\n
              XML
            )
          end
        end
      end
    end

    describe Bool do
      it "should output correctly" do
        run_binary(%(true), args: ["-o", "xml", "."]) do |output|
          output.should eq(<<-XML
            <?xml version="1.0" encoding="UTF-8"?>
            <root>true</root>\n
            XML
          )
        end
      end
    end

    describe Float do
      it "should output correctly" do
        run_binary(%("1.5"), args: ["-o", "xml", "."]) do |output|
          output.should eq(<<-XML
            <?xml version="1.0" encoding="UTF-8"?>
            <root>1.5</root>\n
            XML
          )
        end
      end
    end

    describe Nil do
      it "should output correctly" do
        run_binary("null", args: ["-o", "xml", "."]) do |output|
          output.should eq(<<-XML
            <?xml version="1.0" encoding="UTF-8"?>
            <root/>\n
            XML
          )
        end
      end
    end

    describe Array do
      describe "empty array on root" do
        it "should emit a self closing root tag" do
          run_binary("[]", args: ["-o", "xml", "."]) do |output|
            output.should eq(<<-XML
              <?xml version="1.0" encoding="UTF-8"?>
              <root/>\n
              XML
            )
          end
        end
      end

      describe "array with values on root" do
        it "should emit item tags for non empty values" do
          run_binary(%(["x",{}]), args: ["-o", "xml", "."]) do |output|
            output.should eq(<<-XML
              <?xml version="1.0" encoding="UTF-8"?>
              <root>
                <item>x</item>
                <item/>
              </root>\n
              XML
            )
          end
        end
      end

      describe "object with empty array/values" do
        it "should emit self closing tags for each" do
          run_binary(%({"a":[],"b":{},"c":null}), args: ["-o", "xml", "."]) do |output|
            output.should eq(<<-XML
              <?xml version="1.0" encoding="UTF-8"?>
              <root>
                <b/>
                <c/>
              </root>\n
              XML
            )
          end
        end
      end

      describe "2D array object value" do
        it "should emit key name tag then self closing item tag" do
          run_binary(%({"a":[[]]}), args: ["-o", "xml", "."]) do |output|
            output.should eq(<<-XML
              <?xml version="1.0" encoding="UTF-8"?>
              <root>
                <a/>
              </root>\n
              XML
            )
          end
        end
      end

      it "object value mixed/nested array values" do
        run_binary(%({"x":[1,[2,[3]]]}), args: ["-o", "xml", "."]) do |output|
          output.should eq(<<-XML
            <?xml version="1.0" encoding="UTF-8"?>
            <root>
              <x>1</x>
              <x>
                <item>2</item>
                <item>
                  <item>3</item>
                </item>
              </x>
            </root>\n
            XML
          )
        end
      end

      it "object value array primitive values" do
        run_binary(%({"x":[1,2,3]}), args: ["-o", "xml", "."]) do |output|
          output.should eq(<<-XML
            <?xml version="1.0" encoding="UTF-8"?>
            <root>
              <x>1</x>
              <x>2</x>
              <x>3</x>
            </root>\n
            XML
          )
        end
      end
    end

    describe Object do
      it "simple key/value" do
        run_binary(%({"name":"Jim"}), args: ["-o", "xml", "."]) do |output|
          output.should eq(<<-XML
            <?xml version="1.0" encoding="UTF-8"?>
            <root>
              <name>Jim</name>
            </root>\n
            XML
          )
        end
      end

      it "nested object" do
        run_binary(%({"name":"Jim", "city": {"street":"forbs"}}), args: ["-o", "xml", "."]) do |output|
          output.should eq(<<-XML
            <?xml version="1.0" encoding="UTF-8"?>
            <root>
              <name>Jim</name>
              <city>
                <street>forbs</street>
              </city>
            </root>\n
            XML
          )
        end
      end

      it "with an attribute" do
        run_binary(%({"name":"Jim", "city": {"@street":"forbs"}}), args: ["-o", "xml", "."]) do |output|
          output.should eq(<<-XML
            <?xml version="1.0" encoding="UTF-8"?>
            <root>
              <name>Jim</name>
              <city street="forbs"/>
            </root>\n
            XML
          )
        end
      end

      it "with an attribute and #text" do
        run_binary(%({"name":"Jim", "city": {"@street":"forbs", "#text": "Atlantic"}}), args: ["-o", "xml", "."]) do |output|
          output.should eq(<<-XML
            <?xml version="1.0" encoding="UTF-8"?>
            <root>
              <name>Jim</name>
              <city street="forbs">Atlantic</city>
            </root>\n
            XML
          )
        end
      end

      it "with attributes" do
        run_binary(%({"name":"Jim", "city": {"@street":"forbs", "@post": 123}}), args: ["-o", "xml", "."]) do |output|
          output.should eq(<<-XML
            <?xml version="1.0" encoding="UTF-8"?>
            <root>
              <name>Jim</name>
              <city street="forbs" post="123"/>
            </root>\n
            XML
          )
        end
      end

      it "with attributes and #text" do
        run_binary(%({"name":"Jim", "city": {"@street":"forbs", "@post": 123, "#text": "Atlantic"}}), args: ["-o", "xml", "."]) do |output|
          output.should eq(<<-XML
            <?xml version="1.0" encoding="UTF-8"?>
            <root>
              <name>Jim</name>
              <city street="forbs" post="123">Atlantic</city>
            </root>\n
            XML
          )
        end
      end

      it "with a prefixed key" do
        run_binary(%({"foo:name":"Jim"}), args: ["-o", "xml", "."]) do |output|
          output.should eq(<<-XML
            <?xml version="1.0" encoding="UTF-8"?>
            <root>
              <foo:name>Jim</foo:name>
            </root>\n
            XML
          )
        end
      end
    end
  end
end


================================================
FILE: spec/converters/yaml_spec.cr
================================================
require "../spec_helper"

LITERAL_BLOCK = <<-YAML
---
literal_block: |
    This entire block of text will be the value of the 'literal_block' key,
    with line breaks being preserved.

    The literal continues until de-dented, and the leading indentation is
    stripped.

        Any lines that are 'more-indented' keep the rest of their indentation -
        these lines will be indented by 4 spaces.
YAML

FOLDED_BLOCK = <<-YAML
folded_style: >
    This entire block of text will be the value of 'folded_style', but this
    time, all newlines will be replaced with a single space.

    Blank lines, like above, are converted to a newline character.

        'More-indented' lines keep their newlines, too -
        this text will appear over two lines.
YAML

NESTED_OBJECT = <<-YAML
a_nested_map:
  key: value
  another_key: Another Value
  another_nested_map:
    hello: hello
YAML

COMPLEX_MAPPING_KEY = <<-YAML
? |
  This is a key
  that has multiple lines
: and this is its value
YAML

COMPLEX_SEQUENCE_KEY = <<-YAML
? - Manchester United
  - Real Madrid
: [2001-01-01, 2002-02-02]
YAML

NESTED_ARRAY = <<-YAML
a_sequence:
  - Item 1
  - Item 2
  - 0.5  # sequences can contain disparate types.
  - Item 4
  - key: value
    another_key: another_value
  -
    - This is a sequence
    - inside another sequence
  - - - Nested sequence indicators
      - can be collapsed
YAML

ANCHORS = <<-YAML
base: &base
  name: Everyone has same name
foo: &foo
  <<: *base
  age: 10
bar: &bar
  <<: *base
  age: 20
YAML

describe OQ::Converters::YAML do
  describe ".deserialize" do
    describe String do
      describe "not blank" do
        it "should output correctly" do
          run_binary(%(--- Jim), args: ["-i", "yaml", "."]) do |output|
            output.should eq %("Jim"\n)
          end
        end
      end

      describe "blank" do
        it "should output correctly" do
          run_binary(%(--- ), args: ["-i", "yaml", "."]) do |output|
            output.should eq "null\n"
          end
        end
      end

      describe "with a tag" do
        it "should output correctly" do
          run_binary(%(--- !!str 0.5), args: ["-i", "yaml", "."]) do |output|
            output.should eq %("0.5"\n)
          end
        end
      end

      describe "that is single quoted" do
        it "should output correctly" do
          run_binary(%(---\nhowever: 'foobar'), args: ["-i", "yaml", "-c", "."]) do |output|
            output.should eq %({"however":"foobar"}\n)
          end
        end
      end

      describe "that is double quoted" do
        it "should output correctly" do
          run_binary(%(---\nhowever: "foobar"), args: ["-i", "yaml", "-c", "."]) do |output|
            output.should eq %({"however":"foobar"}\n)
          end
        end
      end

      describe "literal block" do
        it "should output correctly" do
          run_binary(LITERAL_BLOCK, args: ["-i", "yaml", "-c", "."]) do |output|
            output.should eq %({"literal_block":"This entire block of text will be the value of the 'literal_block' key,\\nwith line breaks being preserved.\\n\\nThe literal continues until de-dented, and the leading indentation is\\nstripped.\\n\\n    Any lines that are 'more-indented' keep the rest of their indentation -\\n    these lines will be indented by 4 spaces."}\n)
          end
        end
      end

      describe "folded block" do
        it "should output correctly" do
          run_binary(FOLDED_BLOCK, args: ["-i", "yaml", "-c", "."]) do |output|
            output.should eq %({"folded_style":"This entire block of text will be the value of 'folded_style', but this time, all newlines will be replaced with a single space.\\nBlank lines, like above, are converted to a newline character.\\n\\n    'More-indented' lines keep their newlines, too -\\n    this text will appear over two lines."}\n)
          end
        end
      end
    end

    describe Bool do
      it "should output correctly" do
        run_binary(%(--- true), args: ["-i", "yaml", "."]) do |output|
          output.should eq "true\n"
        end
      end
    end

    describe Float do
      it "should output correctly" do
        run_binary(%(--- 10.50), args: ["-i", "yaml", "."]) do |output|
          output.should eq "10.5\n"
        end
      end
    end

    describe Nil do
      it "should output correctly" do
        run_binary(%(--- ), args: ["-i", "yaml", "."]) do |output|
          output.should eq "null\n"
        end
      end
    end

    describe Object do
      describe "a simple object" do
        it "should output correctly" do
          run_binary(%(---\nname: Jim), args: ["-i", "yaml", "-c", "."]) do |output|
            output.should eq %({"name":"Jim"}\n)
          end
        end
      end

      describe "with spaces in the key" do
        it "should output correctly" do
          run_binary(%(---\nkey with spaces: value), args: ["-i", "yaml", "-c", "."]) do |output|
            output.should eq %({"key with spaces":"value"}\n)
          end
        end
      end

      describe "with a quoted key key" do
        it "should output correctly" do
          run_binary(%(---\n'Keys can be quoted too.': value), args: ["-i", "yaml", "-c", "."]) do |output|
            output.should eq %({"Keys can be quoted too.":"value"}\n)
          end
        end
      end

      describe "with nested object" do
        it "should output correctly" do
          run_binary(NESTED_OBJECT, args: ["-i", "yaml", "-c", "."]) do |output|
            output.should eq %({"a_nested_map":{"key":"value","another_key":"Another Value","another_nested_map":{"hello":"hello"}}}\n)
          end
        end
      end

      describe "with a non string key" do
        it "should output correctly" do
          run_binary(%(---\n0.25: a float key), args: ["-i", "yaml", "-c", "."]) do |output|
            output.should eq %({"0.25":"a float key"}\n)
          end
        end
      end

      describe "with JSON syntax" do
        describe "with quotes" do
          it "should output correctly" do
            run_binary(%(---\njson_seq: {"key": "value"}), args: ["-i", "yaml", "-c", "."]) do |output|
              output.should eq %({"json_seq":{"key":"value"}}\n)
            end
          end
        end

        describe "without quotes" do
          it "should output correctly" do
            run_binary(%(---\njson_seq: {key: value}), args: ["-i", "yaml", "-c", "."]) do |output|
              output.should eq %({"json_seq":{"key":"value"}}\n)
            end
          end
        end
      end

      describe "with a complex mapping key" do
        it "should output correctly" do
          run_binary(COMPLEX_MAPPING_KEY, args: ["-i", "yaml", "-c", "."]) do |output|
            output.should eq %({"This is a key\\nthat has multiple lines\\n":"and this is its value"}\n)
          end
        end
      end

      describe "with set notation" do
        it "should output correctly" do
          run_binary(%(---\nset:\n  ? item1\n  ? item2), args: ["-i", "yaml", "-c", "."]) do |output|
            output.should eq %({"set":{"item1":null,"item2":null}}\n)
          end
        end
      end

      pending "with a complex sequence key" do
        it "should output correctly" do
          run_binary(COMPLEX_SEQUENCE_KEY, args: ["-i", "yaml", "-c", "."]) do |output|
            output.should eq %({"["Manchester United", "Real Madrid"]":["2001-01-01T00:00:00Z","2002-02-02T00:00:00Z"]}\n)
          end
        end
      end

      describe "with anchors" do
        it "should output correctly" do
          run_binary(ANCHORS, args: ["-i", "yaml", "-c", "."]) do |output|
            output.should eq %({"base":{"name":"Everyone has same name"},"foo":{"name":"Everyone has same name","age":10},"bar":{"name":"Everyone has same name","age":20}}\n)
          end
        end
      end
    end

    describe Array do
      describe "with mixed/nested array values" do
        it "should output correctly" do
          run_binary(NESTED_ARRAY, args: ["-i", "yaml", "-c", "."]) do |output|
            output.should eq %({"a_sequence":["Item 1","Item 2",0.5,"Item 4",{"key":"value","another_key":"another_value"},["This is a sequence","inside another sequence"],[["Nested sequence indicators","can be collapsed"]]]}\n)
          end
        end
      end

      describe "with JSON syntax" do
        describe "with quotes" do
          it "should output correctly" do
            run_binary(%(---\njson_seq: [3, 2, 1, "takeoff"]), args: ["-i", "yaml", "-c", "."]) do |output|
              output.should eq %({"json_seq":[3,2,1,"takeoff"]}\n)
            end
          end
        end

        describe "without quotes" do
          it "should output correctly" do
            run_binary(%(---\njson_seq: [3, 2, 1, takeoff]), args: ["-i", "yaml", "-c", "."]) do |output|
              output.should eq %({"json_seq":[3,2,1,"takeoff"]}\n)
            end
          end
        end
      end
    end
  end

  describe ".serialize" do
    describe String do
      describe "not blank" do
        it "should output correctly" do
          run_binary(%("Jim"), args: ["-o", "yaml", "."]) do |output|
            output.should start_with <<-YAML
            --- Jim
            YAML
          end
        end
      end

      describe "blank" do
        it "should output correctly" do
          run_binary(%(""), args: ["-o", "yaml", "."]) do |output|
            output.should eq(<<-YAML
              --- ""\n
              YAML
            )
          end
        end
      end
    end

    describe Bool do
      it "should output correctly" do
        run_binary(%(true), args: ["-o", "yaml", "."]) do |output|
          output.should start_with <<-YAML
            --- true
            YAML
        end
      end
    end

    describe Float do
      it "should output correctly" do
        run_binary(%("1.5"), args: ["-o", "yaml", "."]) do |output|
          output.should eq(<<-YAML
            --- "1.5"\n
            YAML
          )
        end
      end
    end

    describe Nil do
      it "should output correctly" do
        run_binary("null", args: ["-o", "yaml", "."]) do |output|
          output.should start_with "---"
        end
      end
    end

    describe Array do
      describe "empty array on root" do
        it "should emit a self closing root tag" do
          run_binary("[]", args: ["-o", "yaml", "."]) do |output|
            output.should eq(<<-YAML
              --- []\n
              YAML
            )
          end
        end
      end

      describe "array with values on root" do
        it "should emit item tags for non empty values" do
          run_binary(%(["x",{}]), args: ["-o", "yaml", "."]) do |output|
            output.should eq(<<-YAML
              ---
              - x
              - {}\n
              YAML
            )
          end
        end
      end

      describe "object with empty array/values" do
        it "should emit self closing tags for each" do
          run_binary(%({"a":[],"b":{},"c":null}), args: ["-o", "yaml", "."]) do |output|
            output.should start_with <<-YAML
              ---
              a: []
              b: {}
              c:
              YAML
          end
        end
      end

      describe "2D array object value" do
        it "should emit key name tag then self closing item tag" do
          run_binary(%({"a":[[]]}), args: ["-o", "yaml", "."]) do |output|
            output.should eq(<<-YAML
              ---
              a:
              - []\n
              YAML
            )
          end
        end
      end

      describe "object value mixed/nested array values" do
        it "should emit correctly" do
          run_binary(%({"x":[1,[2,[3]]]}), args: ["-o", "yaml", "."]) do |output|
            output.should eq(<<-YAML
              ---
              x:
              - 1
              - - 2
                - - 3\n
              YAML
            )
          end
        end
      end

      describe "object value array primitive values" do
        it "should emit correctly" do
          run_binary(%({"x":[1,2,3]}), args: ["-o", "yaml", "."]) do |output|
            output.should eq(<<-YAML
              ---
              x:
              - 1
              - 2
              - 3\n
              YAML
            )
          end
        end
      end

      describe "when the jq filter doesn't return data" do
        it "should return an empty string" do
          run_binary(%([{"name":"foo"}]), args: ["-i", "yaml", "-o", "yaml", %<.[] | select(.name != "foo")>]) do |output|
            output.should be_empty
          end
        end
      end
    end

    describe Object do
      describe "simple key/value" do
        it "should output correctly" do
          run_binary(%({"name":"Jim"}), args: ["-o", "yaml", "."]) do |output|
            output.should eq(<<-YAML
              ---
              name: Jim\n
              YAML
            )
          end
        end
      end

      describe "nested object" do
        it "should output correctly" do
          run_binary(%({"name":"Jim", "city": {"street":"forbs"}}), args: ["-o", "yaml", "."]) do |output|
            output.should eq(<<-YAML
              ---
              name: Jim
              city:
                street: forbs\n
              YAML
            )
          end
        end
      end
    end
  end
end


================================================
FILE: spec/format_spec.cr
================================================
require "./spec_helper"

describe OQ::Format do
  describe ".to_s" do
    it "returns a comma separated list of the formats" do
      OQ::Format.to_s.should eq "json, simpleyaml, xml, yaml"
    end
  end
end


================================================
FILE: spec/oq_spec.cr
================================================
require "./spec_helper"

private SIMPLE_JSON_OBJECT = <<-JSON
{
  "name": "Jim"
}
JSON

private NESTED_JSON_OBJECT = <<-JSON
{"foo":{"bar":{"baz":5}}}
JSON

private ARRAY_JSON_OBJECT = <<-JSON
{"names":[1,2,3]}
JSON

describe OQ do
  describe "when given a filter file" do
    it "should return the correct output" do
      run_binary(input: SIMPLE_JSON_OBJECT, args: ["-f", "spec/assets/test_filter"]) do |output|
        output.should eq %("Jim"\n)
      end
    end
  end

  it "with a simple filter" do
    run_binary(input: SIMPLE_JSON_OBJECT, args: [".name"]) do |output|
      output.should eq %("Jim"\n)
    end
  end

  it "with a filter to get nested values" do
    run_binary(input: NESTED_JSON_OBJECT, args: [".foo.bar.baz"]) do |output|
      output.should eq "5\n"
    end
  end

  it "should colorize the output with the -C option" do
    run_binary(input: SIMPLE_JSON_OBJECT, args: ["-c", "-C", "."]) do |output|
      output.should start_with "\e"
      output.should contain %("name")
      output.should contain %("Jim")
      output.should end_with "\e[0m\n"
    end
  end

  describe "with a non-JSON output format" do
    it "should convert the JSON to that format" do
      run_binary(input: SIMPLE_JSON_OBJECT, args: ["-o", "yaml", "."]) do |output|
        output.should eq "---\nname: Jim\n"
      end
    end

    describe "with the -C option" do
      it "should remove the -C option" do
        run_binary(input: SIMPLE_JSON_OBJECT, args: ["-o", "yaml", "-C", "."]) do |output|
          output.should eq "---\nname: Jim\n"
        end
      end
    end
  end

  describe "files" do
    describe "with a file input" do
      it "should return the correct output" do
        run_binary(args: [".", "spec/assets/data1.json"]) do |output|
          output.should eq "#{SIMPLE_JSON_OBJECT}\n"
        end
      end
    end

    describe "with multiple JSON file input" do
      it "raw data" do
        run_binary(args: ["-c", ".", "spec/assets/data1.json", "spec/assets/data2.json"]) do |output|
          output.should eq %({"name":"Jim"}\n{"name":"Bob"}\n)
        end
      end

      it "--slurp" do
        run_binary(args: ["-c", "--slurp", ".", "spec/assets/data1.json", "spec/assets/data2.json"]) do |output|
          output.should eq %([{"name":"Jim"},{"name":"Bob"}]\n)
        end
      end
    end

    describe "with multiple non JSON file input" do
      it "raw data" do
        run_binary(args: ["-i", "yaml", "-c", ".", "spec/assets/data1.yml", "spec/assets/data2.yml"]) do |output|
          output.should eq %({"name":"Jim"}\n{"age":17,"name":"Fred"}\n)
        end
      end

      it "--slurp" do
        run_binary(args: ["-i", "yaml", "-c", "--slurp", ".", "spec/assets/data1.yml", "spec/assets/data2.yml"]) do |output|
          output.should eq %([{"name":"Jim"},{"age":17,"name":"Fred"}]\n)
        end
      end
    end

    it "with multiple --arg" do
      run_binary(args: ["-c", "-r", "--arg", "chart", "stolon", "--arg", "version", "1.5.10", "$version", "spec/assets/data1.json"]) do |output|
        output.should eq %(1.5.10\n)
      end
    end
  end

  it "should minify the output with the -c option" do
    run_binary(input: NESTED_JSON_OBJECT, args: ["-c", "."]) do |output|
      output.should eq %({"foo":{"bar":{"baz":5}}}\n)
    end
  end

  it "should format the output without the -c option" do
    run_binary(input: NESTED_JSON_OBJECT, args: ["."]) do |output|
      output.should eq(<<-JSON
        {
          "foo": {
            "bar": {
              "baz": 5
            }
          }
        }\n
        JSON
      )
    end
  end

  describe "with null input option" do
    describe "with a scalar value" do
      it "should return the correct output" do
        run_binary(args: ["-n", "0"]) do |output|
          output.should eq "0\n"
        end
      end

      it "should return the correct output" do
        run_binary(args: ["--null-input", "0"]) do |output|
          output.should eq "0\n"
        end
      end
    end

    describe "with a JSON object string" do
      it "should return the correct output" do
        run_binary(args: ["-cn", %([{"foo":"bar"},{"foo":"baz"}])]) do |output|
          output.should eq %([{"foo":"bar"},{"foo":"baz"}]\n)
        end
      end
    end

    describe "with input from STDIN" do
      it "should return the correct output" do
        run_binary(input: "foo", args: ["-n", "."]) do |output|
          output.should eq "null\n"
        end
      end
    end

    it "should not block waiting for input" do
      run_binary(input: Process::Redirect::Inherit, args: ["-n", "."]) do |output|
        output.should eq "null\n"
      end
    end
  end

  describe "with a custom indent value with JSON" do
    it "should return the correct output" do
      run_binary(input: SIMPLE_JSON_OBJECT, args: ["--indent", "1", "."]) do |output|
        output.should eq %({\n "name": "Jim"\n}\n)
      end
    end
  end

  describe "when streaming input" do
    it "should return the correct output" do
      run_binary(input: %({"a": [1, 2.2, true, "abc", null]}), args: ["-nc", "--stream", "fromstream( 1|truncate_stream(inputs) |  select(length>1) | .[0] |= .[1:] )"]) do |output|
        output.should eq %(1\n2.2\ntrue\n"abc"\nnull\n)
      end
    end
  end

  describe "when using 'input'" do
    it "should return the correct output" do
      run_binary(args: ["-cn", "-f", "spec/assets/stream-filter", "spec/assets/stream-data.json"]) do |output|
        output.should eq %({"possible_victim01":{"total":3,"evildoers":{"evil.com":2,"soevil.com":1}},"possible_victim02":{"total":1,"evildoers":{"bad.com":1}},"possible_victim03":{"total":1,"evildoers":{"soevil.com":1}}}\n)
      end
    end
  end

  describe "with the -L option" do
    it "should be passed correctly without a space" do
      run_binary(args: ["-n", "-L#{__DIR__}/assets", %(import "test" as test; 9 | test::increment)]) do |output|
        output.should eq %(10\n)
      end
    end

    it "should be passed correctly with a space" do
      run_binary(args: ["-n", "-L", "#{__DIR__}/assets", %(import "test" as test; 9 | test::increment)]) do |output|
        output.should eq %(10\n)
      end
    end
  end

  describe "--arg" do
    it "single arg" do
      run_binary(args: ["-cn", "--arg", "foo", "bar", %({"name":$foo})]) do |output|
        output.should eq %({"name":"bar"}\n)
      end
    end

    it "multiple arg" do
      run_binary(args: ["-rcn", "-r", "--arg", "chart", "stolon", "--arg", "version", "1.5.10", "$version"]) do |output|
        output.should eq %(1.5.10\n)
      end
    end

    it "different option in between args" do
      run_binary(args: ["-rcn", "--arg", "chart", "stolon", "--arg", "version", "1.5.10", "$version"]) do |output|
        output.should eq %(1.5.10\n)
      end
    end

    it "when the arg name matches a directory name" do
      run_binary(args: ["-rcn", "--arg", "spec", "dir", "$spec"]) do |output|
        output.should eq %(dir\n)
      end
    end
  end

  describe "with the --argjson option" do
    it "should be passed correctly" do
      run_binary(args: ["-rcn", "--argjson", "foo", "123", %({"id":$foo})]) do |output|
        output.should eq %({"id":123}\n)
      end
    end

    it "when the arg name matches a directory name" do
      run_binary(args: ["-rcn", "--argjson", "spec", "123", "$spec"]) do |output|
        output.should eq %(123\n)
      end
    end
  end

  describe "with the --slurpfile option" do
    it "should be passed correctly" do
      run_binary(args: ["-rcn", "--slurpfile", "ids", "spec/assets/raw.json", %({"ids":$ids})]) do |output|
        output.should eq %({"ids":[1,2,3]}\n)
      end
    end
  end

  describe "with the --rawfile option" do
    it "should be passed correctly" do
      run_binary(args: ["-rcn", "--rawfile", "ids", "spec/assets/raw.json", %({"ids":$ids})]) do |output|
        output.should eq %({"ids":"1\\n2\\n3\\n"}\n)
      end
    end
  end

  describe "with the --args option" do
    it "should be passed correctly" do
      run_binary(args: ["-rcn", %({"ids":$ARGS.positional}), "--args", "1", "2", "3"]) do |output|
        output.should eq %({"ids":["1","2","3"]}\n)
      end
    end
  end

  describe "with the --jsonargs option" do
    it "should be passed correctly" do
      run_binary(args: ["-rcn", %({"ids":$ARGS.positional}), "--jsonargs", "1", "2", "3"]) do |output|
        output.should eq %({"ids":[1,2,3]}\n)
      end
    end
  end

  describe "when there is a jq error" do
    it "should return the error and correct exit code" do
      run_binary(input: ARRAY_JSON_OBJECT, args: [".names | .[] | .name"], success: false) do |_, _, error|
        error.should eq %(jq: error (at <stdin>:0): Cannot index number with string "name"\n)
      end
    end
  end

  describe "with an invalid input format" do
    it "should return the error and correct exit code" do
      run_binary(input: SIMPLE_JSON_OBJECT, args: ["-i", "foo"], success: false) do |_, _, error|
        error.should eq %(Invalid input format: 'foo'\n)
      end
    end
  end

  describe "with an invalid output format" do
    it "should return the error and correct exit code" do
      run_binary(input: SIMPLE_JSON_OBJECT, args: ["-o", "foo"], success: false) do |_, _, error|
        error.should eq %(Invalid output format: 'foo'\n)
      end
    end
  end
end


================================================
FILE: spec/processor_spec.cr
================================================
require "./spec_helper"

describe OQ::Processor do
  describe "custom IOs" do
    it "works with \"STDIN\" input" do
      input_io = IO::Memory.new %({"name":"Jim"})
      output_io = IO::Memory.new

      OQ::Processor.new.process input_args: [".name"], input: input_io, output: output_io

      output_io.to_s.should eq %("Jim"\n)
    end

    it "works with custom error output" do
      input_io = IO::Memory.new %({"name:"Jim"})
      output_io = IO::Memory.new
      error_io = IO::Memory.new

      expect_raises RuntimeError do
        OQ::Processor.new.process input_args: [".name"], input: input_io, output: output_io, error: error_io
      end

      output_io.to_s.should be_empty
      error_io.to_s.should contain "parse error: Invalid numeric literal at line 1, column 12\n"
    end

    describe "file input" do
      it "single file" do
        output_io = IO::Memory.new

        OQ::Processor.new.process input_args: [".", "-c", "spec/assets/data1.json"], output: output_io

        output_io.to_s.should eq %({"name":"Jim"}\n)
      end

      it "single file, standard input IO" do
        input_io = IO::Memory.new
        output_io = IO::Memory.new

        OQ::Processor.new.process input_args: [".", "-c", "spec/assets/data1.json"], input: input_io, output: output_io

        output_io.to_s.should eq %({"name":"Jim"}\n)
      end

      it "multiple file" do
        output_io = IO::Memory.new

        OQ::Processor.new.process input_args: [".", "-c", "spec/assets/data1.json", "spec/assets/data2.json"], output: output_io

        output_io.to_s.should eq %({"name":"Jim"}\n{"name":"Bob"}\n)
      end

      it "multiple files and --slurp" do
        output_io = IO::Memory.new

        OQ::Processor.new.process input_args: [".", "-c", "-s", "spec/assets/data1.json", "spec/assets/data2.json"], output: output_io

        output_io.to_s.should eq %([{"name":"Jim"},{"name":"Bob"}]\n)
      end
    end
  end
end


================================================
FILE: spec/spec_helper.cr
================================================
require "spec"
require "../src/oq"

# Runs the binary with the given *name* and *args*.
def run_binary(input : String | Process::Redirect | Nil = nil, name : String = "bin/oq", args : Array(String) = [] of String, *, success : Bool = true, file = __FILE__, line = __LINE__, & : String, Process::Status, String -> Nil)
  buffer_io = IO::Memory.new
  error_io = IO::Memory.new
  input_io = IO::Memory.new

  if input.is_a? Process::Redirect
    input_io = input
  else
    input_io << input if input
    input_io = input_io.rewind
  end

  status = Process.run(name, args, output: buffer_io, input: input_io, error: error_io)

  if success
    status.success?.should be_true, file: file, line: line, failure_message: error_io.to_s
  else
    status.success?.should_not be_true, file: file, line: line, failure_message: error_io.to_s
  end

  yield buffer_io.to_s, status, error_io.to_s
end


================================================
FILE: src/converters/json.cr
================================================
# Converter for the `OQ::Format::JSON` format.
module OQ::Converters::JSON
  def self.deserialize(input : IO, output : IO) : Nil
    IO.copy input, output
  end

  def self.serialize(input : IO, output : IO) : Nil
    IO.copy input, output
  end
end


================================================
FILE: src/converters/processor_aware.cr
================================================
# :nodoc:
#
# Denotes a converter exposes the related `OQ::Processor`
# instance in order to read configuration options off of it.
module OQ::Converters::ProcessorAware
  macro extended
    class_property! processor : OQ::Processor
  end
end


================================================
FILE: src/converters/simple_yaml.cr
================================================
require "./yaml"

# Converter for the `OQ::Format::SimpleYAML` format.
module OQ::Converters::SimpleYAML
  extend OQ::Converters::YAML
  extend self

  # ameba:disable Metrics/CyclomaticComplexity
  def deserialize(input : IO, output : IO) : Nil
    yaml = ::YAML::PullParser.new(input)
    json = ::JSON::Builder.new(output)

    yaml.read_stream do
      loop do
        case yaml.kind
        when .document_start?
          json.start_document
        when .document_end?
          json.end_document
          yaml.read_next
          break
        when .scalar?
          string = yaml.value

          if json.next_is_object_key?
            json.scalar(string)
          else
            scalar = ::YAML::Schema::Core.parse_scalar(yaml)
            case scalar
            when Nil
              json.scalar(scalar)
            when Bool
              json.scalar(scalar)
            when Int64
              json.scalar(scalar)
            when Float64
              json.scalar(scalar)
            else
              json.scalar(string)
            end
          end
        when .sequence_start?
          json.start_array
        when .sequence_end?
          json.end_array
        when .mapping_start?
          json.start_object
        when .mapping_end?
          json.end_object
        end
        yaml.read_next
      end
    end
  end
end


================================================
FILE: src/converters/xml.cr
================================================
# Converter for the `OQ::Format::XML` format.
module OQ::Converters::XML
  extend OQ::Converters::ProcessorAware

  def self.deserialize(input : IO, output : IO) : Nil
    builder = ::JSON::Builder.new output
    xml = ::XML::Reader.new input

    # Set reader to first element
    xml.read

    # Raise an error if the document is invalid and could not be read
    raise ::XML::Error.new LibXML.xmlGetLastError if xml.node_type.none?

    builder.document do
      builder.object do
        # Skip non element nodes, i.e. the prolog or DOCTYPE, etc.
        until xml.node_type.element?
          xml.read
        end

        process_element_node xml.expand, builder
      end
    end
  end

  private def self.process_element_node(node : ::XML::Node, builder : ::JSON::Builder) : Nil
    # If the node doesn't have nested elements nor attributes nor a namespace (with --xmlns); just emit a scalar value
    if self.scalar_node? node
      return builder.field self.normalize_node_name(node), get_node_value node
    end

    # Otherwise process the node as a key/value pair
    builder.field self.normalize_node_name node do
      builder.object do
        process_children node, builder
      end
    end
  end

  private def self.process_array_node(name : String, children : Array(::XML::Node), builder : ::JSON::Builder) : Nil
    builder.field name do
      builder.array do
        children.each do |node|
          # If the node doesn't have nested elements nor attributes nor a namespace (with --xmlns); just emit a scalar value
          if self.scalar_node? node
            builder.scalar get_node_value node
          else
            # Otherwise process the node within an object
            builder.object do
              process_children node, builder
            end
          end
        end
      end
    end
  end

  private def self.process_children(node : ::XML::Node, builder : ::JSON::Builder) : Nil
    # Process node attributes
    node.attributes.each do |attr|
      builder.field "@#{attr.name}", attr.content
    end

    # Include attributes for namespaces defined on this node
    # TODO: Make this the default behavior in oq 2.x
    if self.processor.xmlns?
      node.namespace_definitions.each do |ns|
        builder.field "@#{self.normalize_namespace_prefix ns}", ns.href
      end
    end

    # Determine how to process a node's children
    node.children.group_by(&->normalize_node_name(::XML::Node)).each do |name, children|
      # Skip non significant whitespace; Skip mixed character input
      if children.first.text? && has_nested_elements?(node)
        # Only emit text content if there is only one child
        if children.size == 1
          builder.field "#text", children.first.content
        end

        next
      end

      # Array
      if children.size > 1 || self.processor.xml_forced_arrays.includes? name
        process_array_node name, children, builder
      else
        if children.first.text?
          # node content in attribute object
          builder.field "#text", children.first.content
        else
          # Element
          process_element_node children.first, builder
        end
      end
    end
  end

  private def self.has_nested_elements?(node : ::XML::Node) : Bool
    node.children.any? { |child| !child.text? && !child.cdata? }
  end

  # TODO: Make checking for namespaces the default behavior in oq 2.x
  private def self.scalar_node?(node : ::XML::Node) : Bool
    !self.has_nested_elements?(node) && node.attributes.empty? && ((self.processor.xmlns? && node.namespace_definitions.empty?) || !self.processor.xmlns?)
  end

  private def self.get_node_value(node : ::XML::Node) : String?
    node.children.empty? ? nil : node.children.first.content
  end

  private def self.normalize_node_name(node : ::XML::Node) : String
    return node.name unless namespace = node.namespace
    (prefix = (self.processor.xml_namespaces[namespace.href]? || namespace.prefix).presence) ? "#{prefix}:#{node.name}" : node.name
  end

  private def self.normalize_namespace_prefix(namespace : ::XML::Namespace) : String
    (prefix = (self.processor.xml_namespaces[namespace.href]? || namespace.prefix).presence) ? "xmlns:#{prefix}" : "xmlns"
  end

  def self.serialize(input : IO, output : IO) : Nil
    json = ::JSON::PullParser.new input
    builder = ::XML::Builder.new output

    builder.indent = ((self.processor.tab? ? "\t" : " ")*self.processor.indent)

    builder.start_document "1.0", "UTF-8" if self.processor.xml_prolog?

    if root = self.processor.xml_root.presence
      builder.start_element root
    end

    loop do
      emit builder, json
      break if json.kind.eof?
    end

    if self.processor.xml_root.presence
      builder.end_element
    end

    builder.end_document if self.processor.xml_prolog?
    builder.flush unless self.processor.xml_prolog?
  end

  private def self.emit(builder : ::XML::Builder, json : ::JSON::PullParser, key : String? = nil, array_key : String? = nil) : Nil
    case json.kind
    when .null?                           then json.read_null
    when .string?, .int?, .float?, .bool? then builder.text get_value json
    when .begin_object?                   then handle_object builder, json, key, array_key
    when .begin_array?                    then handle_array builder, json, key, array_key
    else
      nil
    end
  end

  private def self.handle_object(builder : ::XML::Builder, json : ::JSON::PullParser, key : String? = nil, array_key : String? = nil) : Nil
    json.read_object do |k|
      if k.starts_with?('@')
        builder.attribute k.lchop('@'), get_value json
      elsif k.starts_with?('!')
        builder.element k.lchop('!') do
          builder.cdata get_value json
        end
      elsif json.kind.begin_array? || k == "#text"
        emit builder, json, k, k
      else
        builder.element k do
          emit builder, json, k
        end
      end
    end
  end

  private def self.handle_array(builder : ::XML::Builder, json : ::JSON::PullParser, key : String? = nil, array_key : String? = nil) : Nil
    json.read_begin_array
    array_key = array_key || self.processor.xml_item

    if json.kind.end_array?
      # If the array is empty don't emit anything
    else
      until json.kind.end_array?
        builder.element array_key do
          emit builder, json, key
        end
      end
    end

    json.read_end_array
  end

  private def self.get_value(json : ::JSON::PullParser) : String
    case json.kind
    when .string? then json.read_string
    when .int?    then json.read_int
    when .float?  then json.read_float
    when .bool?   then json.read_bool
    when .null?   then json.read_null
    else
      ""
    end.to_s
  end
end


================================================
FILE: src/converters/yaml.cr
================================================
# Converter for the `OQ::Format::YAML` format.
module OQ::Converters::YAML
  extend self

  def deserialize(input : IO, output : IO) : Nil
    ::YAML.parse(input).to_json output
  end

  # ameba:disable Metrics/CyclomaticComplexity
  def serialize(input : IO, output : IO) : Nil
    json = ::JSON::PullParser.new input
    yaml = ::YAML::Builder.new output

    # Return early is there is no JSON to be read.
    return if json.kind.eof?

    yaml.stream do
      yaml.document do
        loop do
          case json.kind
          when .null?
            yaml.scalar nil
          when .bool?
            yaml.scalar json.bool_value
          when .int?
            yaml.scalar json.int_value
          when .float?
            yaml.scalar json.float_value
          when .string?
            if ::YAML::Schema::Core.reserved_string? json.string_value
              yaml.scalar json.string_value, style: :double_quoted
            else
              yaml.scalar json.string_value
            end
          when .begin_array?
            yaml.start_sequence
          when .end_array?
            yaml.end_sequence
          when .begin_object?
            yaml.start_mapping
          when .end_object?
            yaml.end_mapping
          when .eof?
            break
          end
          json.read_next
        end
      end
    end
  end
end


================================================
FILE: src/oq.cr
================================================
require "json"
require "xml"
require "yaml"

require "./converters/*"

# A performant, and portable jq wrapper that facilitates the consumption and output of formats other than JSON; using jq filters to transform the data.
module OQ
  VERSION = "1.3.5"

  # The support formats that can be converted to/from.
  enum Format
    # The [JSON](https://www.json.org/) format.
    JSON

    # Same as `YAML`, but does not support [anchors or aliases](https://yaml.org/spec/1.2/spec.html#id2765878);
    # thus allowing for the input conversion to be streamed, reducing the memory usage for large inputs.
    SimpleYAML

    # The [XML](https://en.wikipedia.org/wiki/XML) format.
    #
    # NOTE: Conversion to and from `JSON` uses [this](https://www.xml.com/pub/a/2006/05/31/converting-between-xml-and-json.html) spec.
    XML

    # The [YAML](https://yaml.org/) format.
    YAML

    # Returns the list of supported formats.
    def self.to_s(io : IO) : Nil
      self.names.join(io, ", ") { |str, join_io| str.downcase join_io }
    end

    # Maps a given format to its converter.
    def converter(processor : OQ::Processor)
      case self
      in .json?        then OQ::Converters::JSON
      in .simple_yaml? then OQ::Converters::SimpleYAML
      in .xml?         then OQ::Converters::XML
      in .yaml?        then OQ::Converters::YAML
      end.tap { |converter| converter.processor = processor if converter.is_a? OQ::Converters::ProcessorAware }
    end
  end

  # Handles the logic of converting the input format (if needed),
  # processing it via [jq](https://stedolan.github.io/jq/),
  # and converting the output format (if needed).
  #
  # ```
  # require "oq"
  #
  # # This could be any `IO`, e.g. an `HTTP` request body, etc.
  # input_io = IO::Memory.new %({"name":"Jim"})
  #
  # # Create a processor, specifying that we want the output format to be `YAML`.
  # processor = OQ::Processor.new output_format: :yaml
  #
  # File.open("./out.yml", "w") do |file|
  #   # Process the data using our custom input and output IOs.
  #   # The first argument represents the input arguments;
  #   # i.e. the filter and/or any other arguments that should be passed to `jq`.
  #   processor.process ["."], input: input_io, output: file
  # end
  # ```
  class Processor
    # The format that the input data is in.
    property input_format : Format

    # The format that the output should be transcoded into.
    property output_format : Format

    # The root of the XML document when transcoding to XML.
    property xml_root : String

    # If the XML prolog should be emitted.
    property? xml_prolog : Bool

    # The name for XML array elements without keys.
    property xml_item : String

    # The number of spaces to use for indentation.
    property indent : Int32

    # If a tab for each indentation level instead of spaces.
    property? tab : Bool

    # Do not read any input, using `null` as the singular input value.
    property? null : Bool

    # If XML namespaces should be parsed as well.
    # TODO: Remove this in oq 2.0 as it'll becomethe default.
    property? xmlns : Bool

    # Mapping to namespace aliases to their related namespace.
    protected getter xml_namespaces = Hash(String, String).new

    # Set of elements who should be force expanded to an array.
    protected getter xml_forced_arrays = Set(String).new

    # The args that'll be passed to `jq`.
    @args : Array(String) = [] of String

    # Keep a reference to the created temp files in order to delete them later.
    @tmp_files = Set(File).new

    def initialize(
      @input_format : Format = Format::JSON,
      @output_format : Format = Format::JSON,
      @xml_root : String = "root",
      @xml_prolog : Bool = true,
      @xml_item : String = "item",
      @indent : Int32 = 2,
      @tab : Bool = false,
      @null : Bool = false,
      @xmlns : Bool = false,
    )
    end

    @[Deprecated("Use `Processor#tab?` instead.")]
    def tab : Bool
      self.tab?
    end

    @[Deprecated("Use `Processor#xml_prolog?` instead.")]
    def xml_prolog : Bool
      self.xml_prolog?
    end

    # Adds the provided *value* to the internal args array.
    def add_arg(value : String) : Nil
      @args << value
    end

    def add_xml_namespace(prefix : String, href : String) : Nil
      @xml_namespaces[href] = prefix
    end

    def add_forced_array(name : String) : Nil
      xml_forced_arrays << name
    end

    # Consumes `#input_format` data from the provided *input* `IO`, along with any *input_args*.
    # The data is then converted to `JSON`, passed to `jq`, and then converted to `#output_format` while being written to the *output* `IO`.
    # Any errors are written to the *error* `IO`.
    def process(input_args : Array(String) = ARGV, input : IO = ARGF, output : IO = STDOUT, error : IO = STDERR) : Nil
      # Register an at_exit handler to cleanup temp files.
      at_exit { @tmp_files.each &.delete }

      # Parse out --rawfile, --argfile, --slurpfile,-f/--from-file, and -L before processing additional args
      # since these options use a file that should not be used as input.
      self.consume_file_args input_args, "--rawfile", "--argfile", "--slurpfile"
      self.consume_file_args input_args, "-f", "--from-file", "-L", count: 1

      # Also parse out --arg, and --argjson as they may include identifiers that also exist as a directory/file
      # which would result in incorrect arg extraction.
      self.consume_file_args input_args, "--arg", "--argjson"

      # Extract `jq` arguments from `ARGV`.
      self.extract_args input_args, output

      # The --xml-namespace-alias option must be used with the --xmlns option.
      # TODO: Remove this in oq 2.x
      raise ArgumentError.new "The `--xml-namespace-alias` option must be used with the `--xmlns` option." if !@xmlns && !@xml_namespaces.empty?

      # Replace the *input* with a fake `ARGF` `IO` to handle both file and `IO` inputs in case `ARGV` is not being used for the input arguments.
      #
      # If using `null` input, set the input to an empty memory `IO` to essentially consume nothing.
      input = @null ? IO::Memory.new : IO::ARGF.new input_args, input

      input_read, input_write = IO.pipe
      output_read, output_write = IO.pipe

      channel = Channel(Bool | Exception).new

      # If the input format is not JSON and there is more than 1 file in ARGV,
      # convert each file to JSON from the `#input_format` and save it to a temp file.
      # Then replace ARGV with the temp files.
      if !@input_format.json? && input_args.size > 1
        input_args.replace(input_args.map do |file_name|
          File.tempfile ".#{File.basename file_name}" do |tmp_file|
            File.open file_name do |file|
              @input_format.converter(self).deserialize file, tmp_file
            end
          end
            .tap { |tf| @tmp_files << tf }
            .path
        end)

        # Conversion has already been completed by this point, so reset input format back to JSON.
        @input_format = :json
      end

      spawn do
        @input_format.converter(self).deserialize input, input_write
        input_write.close
        channel.send true
      rescue ex
        input_write.close
        channel.send ex
      end

      spawn do
        output_write.close
        @output_format.converter(self).serialize output_read, output
        channel.send true
      rescue ex
        channel.send ex
      end

      run = Process.run(
        "jq",
        @args,
        input: input_read,
        output: output_write,
        error: error
      )

      unless run.success?
        # Raise this to represent a jq error.
        # jq writes its errors directly to the *error* IO so no need to include a message.
        raise RuntimeError.new
      end

      2.times do
        case v = channel.receive
        when Exception then raise v
        end
      end
    end

    # Parses the *input_args*, extracting `jq` arguments while leaving files
    private def extract_args(input_args : Array(String), output : IO) : Nil
      # Add color option if *output* is a tty
      # and the output format is JSON
      # (Since it will go straight to *output* and not converted)
      input_args.unshift "-C" if output.tty? && @output_format.json? && !input_args.includes? "-C"

      # If the -C option was explicitly included
      # and the output format is not JSON;
      # remove it from *input_args* to prevent
      # conversion errors
      input_args.delete("-C") if !@output_format.json?

      # If there are any files within the *input_args*, ignore "." as it's both a valid file and filter
      idx = if first_file_idx = input_args.index { |a| a != "." && File.exists? a }
              # extract everything else
              first_file_idx - 1
            else
              # otherwise just take it all
              -1
            end

      @args.concat input_args.delete_at 0..idx
    end

    # Extracts *arg_name* from the provided *input_args* if it exists;
    # concatenating the result to the internal arg array.
    private def consume_file_arg(input_args : Array(String), arg_name : String, count : Int32 = 2) : Nil
      input_args.index(arg_name).try { |idx| @args.concat input_args.delete_at idx..(idx + count) }
    end

    private def consume_file_args(input_args : Array(String), *arg_names : String, count : Int32 = 2) : Nil
      arg_names.each { |name| consume_file_arg input_args, name, count }
    end
  end
end


================================================
FILE: src/oq_cli.cr
================================================
require "option_parser"

require "./oq"

processor = OQ::Processor.new

OptionParser.parse do |parser|
  parser.banner = "Usage: oq [--help] [oq-arguments] [jq-arguments] jq_filter [file [files...]]"
  parser.on("-h", "--help", "Show this help message.") do
    output = IO::Memory.new
    version = IO::Memory.new

    Process.run("jq", ["-h"], output: output)
    Process.run("jq", ["--version"], output: version)

    puts "oq version: #{OQ::VERSION}, jq version: #{version}", parser, output.to_s.lines.map(&.gsub('\t', "    ")).tap(&.delete_at(0..1)).join('\n')
    exit
  end
  parser.on("-V", "--version", "Returns the current versions of oq and jq.") do
    output = IO::Memory.new

    Process.run("jq", ["--version"], output: output)

    puts "jq: #{output}", "oq: #{OQ::VERSION}"
    exit
  end
  parser.on("-i FORMAT", "--input FORMAT", "Format of the input data. Supported formats: #{OQ::Format}") { |format| (f = OQ::Format.parse?(format)) ? processor.input_format = f : abort "Invalid input format: '#{format}'" }
  parser.on("-o FORMAT", "--output FORMAT", "Format of the output data. Supported formats: #{OQ::Format}") { |format| (f = OQ::Format.parse?(format)) ? processor.output_format = f : abort "Invalid output format: '#{format}'" }
  parser.on("--indent NUMBER", "Use the given number of spaces for indentation (JSON/XML only).") { |n| processor.indent = n.to_i; processor.add_arg "--indent"; processor.add_arg n }
  parser.on("--tab", "Use a tab for each indentation level instead of two spaces.") { processor.tab = true; processor.add_arg "--tab" }
  parser.on("-n", "--null-input", "Don't read any input at all, running the filter once using `null` as the input.") { processor.null = true; processor.add_arg "--null-input" }
  parser.on("--no-prolog", "Whether the XML prolog should be emitted if converting to XML.") { processor.xml_prolog = false }
  parser.on("--xml-item NAME", "The name for XML array elements without keys.") { |i| processor.xml_item = i }
  parser.on("--xmlns", "If XML namespaces should be parsed.  NOTE: This will become the default in oq 2.x.") { processor.xmlns = true }
  parser.on("--xml-force-array NAME", "Forces an element with the provided name to be parsed as an array even if it only contains one item.") { |n| processor.add_forced_array n }
  parser.on("--xml-namespace-alias ALIAS", "Value should be in the form of: `key=namespace`. Elements within the provided namespace are normalized to the provided key.  NOTE: Requires the `--xmlns` option to be passed as well.") { |a| k, v = a.split('=', 2); processor.add_xml_namespace k, v }
  parser.on("--xml-root ROOT", "Name of the root XML element if converting to XML.") { |r| processor.xml_root = r }
  parser.invalid_option { }
end

begin
  processor.process
rescue ex : RuntimeError
  # ignore jq errors as it writes directly to error output.
  exit 1
rescue ex
  abort "oq error: #{ex.message}"
end
Download .txt
gitextract_301sijum/

├── .editorconfig
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── ci.yml
│       └── deployment.yml
├── .gitignore
├── LICENSE
├── README.md
├── shard.yml
├── snap/
│   └── snapcraft.yaml
├── spec/
│   ├── assets/
│   │   ├── data1.json
│   │   ├── data1.yml
│   │   ├── data2.json
│   │   ├── data2.yml
│   │   ├── raw.json
│   │   ├── stream-data.json
│   │   ├── stream-filter
│   │   ├── test.jq
│   │   └── test_filter
│   ├── converters/
│   │   ├── simple_yaml_spec.cr
│   │   ├── xml_spec.cr
│   │   └── yaml_spec.cr
│   ├── format_spec.cr
│   ├── oq_spec.cr
│   ├── processor_spec.cr
│   └── spec_helper.cr
└── src/
    ├── converters/
    │   ├── json.cr
    │   ├── processor_aware.cr
    │   ├── simple_yaml.cr
    │   ├── xml.cr
    │   └── yaml.cr
    ├── oq.cr
    └── oq_cli.cr
Condensed preview — 32 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (115K chars).
[
  {
    "path": ".editorconfig",
    "chars": 150,
    "preview": "root = true\n\n[*.cr]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\nindent_size = 2\ntr"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 189,
    "preview": "version: 2\n\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthly\"\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 1096,
    "preview": "name: CI\n\non:\n  pull_request:\n    branches:\n      - 'master'\n  schedule:\n    - cron: '0 21 * * *'\n\njobs:\n  check_format:"
  },
  {
    "path": ".github/workflows/deployment.yml",
    "chars": 2262,
    "preview": "name: Deployment\n\non:\n  release:\n    types:\n      - created\n\njobs:\n  dist_linux:\n    runs-on: ubuntu-latest\n    containe"
  },
  {
    "path": ".gitignore",
    "chars": 156,
    "preview": "*.dwarf\n*.snap\n/.shards/\n/bin/\n/docs/\n/lib/\n\n# Libraries don't need dependency lock\n# Dependencies will be locked in app"
  },
  {
    "path": "LICENSE",
    "chars": 1082,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2021 George Dietrich\n\nPermission is hereby granted, free of charge, to any person o"
  },
  {
    "path": "README.md",
    "chars": 5372,
    "preview": "# oq\n\n[![Built with Crystal](https://img.shields.io/badge/built%20with-crystal-000000.svg?logo=crystal)](https://crystal"
  },
  {
    "path": "shard.yml",
    "chars": 403,
    "preview": "name: oq\n\ndescription: |\n  A performant, and portable jq wrapper that facilitates the consumption and output of formats "
  },
  {
    "path": "snap/snapcraft.yaml",
    "chars": 899,
    "preview": "name: oq\nversion: '1.3.5'\nsummary: A performant, and portable jq wrapper to support formats other than JSON\ndescription:"
  },
  {
    "path": "spec/assets/data1.json",
    "chars": 16,
    "preview": "{\"name\": \"Jim\"}\n"
  },
  {
    "path": "spec/assets/data1.yml",
    "chars": 14,
    "preview": "---\nname: Jim\n"
  },
  {
    "path": "spec/assets/data2.json",
    "chars": 16,
    "preview": "{\"name\": \"Bob\"}\n"
  },
  {
    "path": "spec/assets/data2.yml",
    "chars": 19,
    "preview": "age: 17\nname: Fred\n"
  },
  {
    "path": "spec/assets/raw.json",
    "chars": 6,
    "preview": "1\n2\n3\n"
  },
  {
    "path": "spec/assets/stream-data.json",
    "chars": 398,
    "preview": "{\"machine\": \"possible_victim01\", \"domain\": \"evil.com\", \"timestamp\":1435071870}\n{\"machine\": \"possible_victim01\", \"domain\""
  },
  {
    "path": "spec/assets/stream-filter",
    "chars": 284,
    "preview": "reduce inputs as $line\n  ({};\n   $line.machine as $machine\n   | $line.domain as $domain\n   | .[$machine].total as $total"
  },
  {
    "path": "spec/assets/test.jq",
    "chars": 22,
    "preview": "def increment: . + 1;\n"
  },
  {
    "path": "spec/assets/test_filter",
    "chars": 5,
    "preview": ".name"
  },
  {
    "path": "spec/converters/simple_yaml_spec.cr",
    "chars": 11917,
    "preview": "require \"../spec_helper\"\n\n# Essentially copied from the `YAML` spec, minus the `with anchors` test.\n#\n# TODO: Allow the "
  },
  {
    "path": "spec/converters/xml_spec.cr",
    "chars": 33073,
    "preview": "require \"../spec_helper\"\n\nWITH_WHITESPACE = <<-XML\n<item>\n  <flagID>0</flagID>\n  <itemID>0</itemID>\n  <locationID>0</loc"
  },
  {
    "path": "spec/converters/yaml_spec.cr",
    "chars": 13405,
    "preview": "require \"../spec_helper\"\n\nLITERAL_BLOCK = <<-YAML\n---\nliteral_block: |\n    This entire block of text will be the value o"
  },
  {
    "path": "spec/format_spec.cr",
    "chars": 208,
    "preview": "require \"./spec_helper\"\n\ndescribe OQ::Format do\n  describe \".to_s\" do\n    it \"returns a comma separated list of the form"
  },
  {
    "path": "spec/oq_spec.cr",
    "chars": 9374,
    "preview": "require \"./spec_helper\"\n\nprivate SIMPLE_JSON_OBJECT = <<-JSON\n{\n  \"name\": \"Jim\"\n}\nJSON\n\nprivate NESTED_JSON_OBJECT = <<-"
  },
  {
    "path": "spec/processor_spec.cr",
    "chars": 1944,
    "preview": "require \"./spec_helper\"\n\ndescribe OQ::Processor do\n  describe \"custom IOs\" do\n    it \"works with \\\"STDIN\\\" input\" do\n   "
  },
  {
    "path": "spec/spec_helper.cr",
    "chars": 888,
    "preview": "require \"spec\"\nrequire \"../src/oq\"\n\n# Runs the binary with the given *name* and *args*.\ndef run_binary(input : String | "
  },
  {
    "path": "src/converters/json.cr",
    "chars": 250,
    "preview": "# Converter for the `OQ::Format::JSON` format.\nmodule OQ::Converters::JSON\n  def self.deserialize(input : IO, output : I"
  },
  {
    "path": "src/converters/processor_aware.cr",
    "chars": 242,
    "preview": "# :nodoc:\n#\n# Denotes a converter exposes the related `OQ::Processor`\n# instance in order to read configuration options "
  },
  {
    "path": "src/converters/simple_yaml.cr",
    "chars": 1359,
    "preview": "require \"./yaml\"\n\n# Converter for the `OQ::Format::SimpleYAML` format.\nmodule OQ::Converters::SimpleYAML\n  extend OQ::Co"
  },
  {
    "path": "src/converters/xml.cr",
    "chars": 6730,
    "preview": "# Converter for the `OQ::Format::XML` format.\nmodule OQ::Converters::XML\n  extend OQ::Converters::ProcessorAware\n\n  def "
  },
  {
    "path": "src/converters/yaml.cr",
    "chars": 1351,
    "preview": "# Converter for the `OQ::Format::YAML` format.\nmodule OQ::Converters::YAML\n  extend self\n\n  def deserialize(input : IO, "
  },
  {
    "path": "src/oq.cr",
    "chars": 9509,
    "preview": "require \"json\"\nrequire \"xml\"\nrequire \"yaml\"\n\nrequire \"./converters/*\"\n\n# A performant, and portable jq wrapper that faci"
  },
  {
    "path": "src/oq_cli.cr",
    "chars": 2915,
    "preview": "require \"option_parser\"\n\nrequire \"./oq\"\n\nprocessor = OQ::Processor.new\n\nOptionParser.parse do |parser|\n  parser.banner ="
  }
]

About this extraction

This page contains the full source code of the Blacksmoke16/oq GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 32 files (103.1 KB), approximately 29.6k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!