From 484eb1d4fa8b3ecda683bad5a201be26e0c1fb82 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Sun, 18 Jan 2026 12:13:59 -0800 Subject: [PATCH 01/19] Cleaning up examples, switching to mkdocs --- Makefile | 4 +- {website => docs}/.hugo_build.lock | 0 docs/Schema_Description.md | 114 +++ {website/content => docs}/_index.md | 0 docs/{ => assets}/css/darcula.css | 0 docs/{ => assets}/css/dark.css | 0 docs/{ => assets}/css/flexboxgrid.css | 0 docs/{ => assets}/css/funnel.css | 0 docs/{ => assets}/css/highlight.min.css | 0 docs/{ => assets}/css/html5reset.css | 0 docs/{ => assets}/css/hybrid.css | 0 docs/{ => assets}/css/monokai-sublime.css | 0 docs/{ => assets}/css/poole.css | 0 docs/{ => assets}/css/syntax.css | 0 docs/{ => assets}/css/theme.css | 0 docs/{ => assets}/sifter_example.png | Bin docs/categories/index.xml | 11 - {website/content => docs}/docs.md | 0 {website/content => docs}/docs/example.md | 0 docs/docs/example/index.html | 507 --------- docs/docs/index.html | 528 ---------- docs/docs/index.xml | 256 ----- {website/content => docs}/docs/inputs.md | 0 .../content => docs}/docs/inputs/avroLoad.md | 0 docs/docs/inputs/avroload/index.html | 396 -------- .../content => docs}/docs/inputs/embedded.md | 0 docs/docs/inputs/embedded/index.html | 387 ------- {website/content => docs}/docs/inputs/glob.md | 0 docs/docs/inputs/glob/index.html | 423 -------- docs/docs/inputs/gripperload/index.html | 350 ------- docs/docs/inputs/index.html | 380 ------- .../content => docs}/docs/inputs/jsonLoad.md | 0 docs/docs/inputs/jsonload/index.html | 405 -------- .../content => docs}/docs/inputs/plugin.md | 0 docs/docs/inputs/plugin/index.html | 437 -------- .../content => docs}/docs/inputs/sqldump.md | 0 docs/docs/inputs/sqldump/index.html | 416 -------- .../docs/inputs/sqliteLoad.md | 0 docs/docs/inputs/sqliteload/index.html | 410 -------- .../content => docs}/docs/inputs/tableLoad.md | 0 docs/docs/inputs/tableload/index.html | 445 -------- .../content => docs}/docs/inputs/xmlLoad.md | 0 docs/docs/inputs/xmlload/index.html | 401 -------- docs/docs/playbook/index.html | 382 ------- {website/content => docs}/docs/transforms.md | 0 .../docs/transforms/accumulate.md | 0 docs/docs/transforms/accumulate/index.html | 407 -------- .../content => docs}/docs/transforms/clean.md | 0 docs/docs/transforms/clean/index.html | 413 -------- .../content => docs}/docs/transforms/debug.md | 0 docs/docs/transforms/debug/index.html | 405 -------- .../docs/transforms/distinct.md | 0 docs/docs/transforms/distinct/index.html | 401 -------- .../content => docs}/docs/transforms/emit.md | 0 docs/docs/transforms/emit/index.html | 401 -------- .../docs/transforms/fieldParse.md | 0 .../docs/transforms/fieldProcess.md | 0 .../docs/transforms/fieldType.md | 0 docs/docs/transforms/fieldparse/index.html | 379 ------- docs/docs/transforms/fieldprocess/index.html | 415 -------- docs/docs/transforms/fieldtype/index.html | 391 ------- .../docs/transforms/filter.md | 0 docs/docs/transforms/filter/index.html | 437 -------- .../docs/transforms/flatmap.md | 0 docs/docs/transforms/flatmap/index.html | 379 ------- .../content => docs}/docs/transforms/from.md | 0 docs/docs/transforms/from/index.html | 393 ------- .../docs/transforms/graphBuild.md | 0 docs/docs/transforms/graphbuild/index.html | 385 ------- .../content => docs}/docs/transforms/hash.md | 0 docs/docs/transforms/hash/index.html | 412 -------- docs/docs/transforms/index.html | 380 ------- .../docs/transforms/lookup.md | 0 docs/docs/transforms/lookup/index.html | 467 --------- .../content => docs}/docs/transforms/map.md | 0 docs/docs/transforms/map/index.html | 421 -------- .../docs/transforms/objectValidate.md | 0 .../docs/transforms/objectvalidate/index.html | 407 -------- .../docs/transforms/plugin.md | 0 docs/docs/transforms/plugin/index.html | 425 -------- .../docs/transforms/project.md | 0 docs/docs/transforms/project/index.html | 408 -------- .../docs/transforms/reduce.md | 0 docs/docs/transforms/reduce/index.html | 428 -------- .../docs/transforms/regexReplace.md | 0 docs/docs/transforms/regexreplace/index.html | 379 ------- .../content => docs}/docs/transforms/split.md | 0 docs/docs/transforms/split/index.html | 407 -------- .../docs/transforms/tableWrite.md | 0 docs/docs/transforms/tablewrite/index.html | 379 ------- .../content => docs}/docs/transforms/uuid.md | 0 docs/docs/transforms/uuid/index.html | 379 ------- docs/index.html | 71 -- docs/index.xml | 263 ----- docs/sitemap.xml | 85 -- docs/tags/index.xml | 11 - examples/cbio.yaml | 251 ++--- examples/gdc-convert.yaml | 165 +-- examples/gene-table.yaml | 41 +- examples/genome.yaml | 148 +-- examples/hugo-ensembl.yaml | 44 +- examples/vcfload.yaml | 27 - website/archetypes/default.md | 6 - website/config.yaml | 4 - website/layouts/_default/single.html | 40 - website/layouts/index.html | 18 - website/layouts/partials/head.html | 45 - website/layouts/partials/tail.html | 3 - website/public/categories/index.xml | 10 - website/public/css/darcula.css | 77 -- website/public/css/dark.css | 63 -- website/public/css/flexboxgrid.css | 960 ------------------ website/public/css/funnel.css | 245 ----- website/public/css/highlight.min.css | 1 - website/public/css/html5reset.css | 96 -- website/public/css/hybrid.css | 102 -- website/public/css/monokai-sublime.css | 83 -- website/public/css/poole.css | 283 ------ website/public/css/syntax.css | 66 -- website/public/css/theme.css | 223 ---- website/public/docs/example/index.html | 469 --------- website/public/docs/index.html | 365 ------- website/public/docs/index.xml | 304 ------ .../public/docs/inputs/avroload/index.html | 358 ------- .../public/docs/inputs/embedded/index.html | 349 ------- website/public/docs/inputs/glob/index.html | 385 ------- .../public/docs/inputs/gripperload/index.html | 341 ------- website/public/docs/inputs/index.html | 342 ------- .../public/docs/inputs/jsonload/index.html | 367 ------- website/public/docs/inputs/sqldump/index.html | 378 ------- .../public/docs/inputs/sqliteload/index.html | 372 ------- .../public/docs/inputs/tableload/index.html | 341 ------- website/public/docs/inputs/xmlload/index.html | 363 ------- website/public/docs/playbook/index.html | 373 ------- .../docs/transforms/accumulate/index.html | 341 ------- .../public/docs/transforms/clean/index.html | 341 ------- .../public/docs/transforms/debug/index.html | 341 ------- .../docs/transforms/distinct/index.html | 341 ------- .../public/docs/transforms/emit/index.html | 341 ------- .../docs/transforms/fieldparse/index.html | 341 ------- .../docs/transforms/fieldprocess/index.html | 341 ------- .../docs/transforms/fieldtype/index.html | 341 ------- .../public/docs/transforms/filter/index.html | 341 ------- .../public/docs/transforms/from/index.html | 355 ------- .../docs/transforms/graphbuild/index.html | 341 ------- .../public/docs/transforms/hash/index.html | 341 ------- website/public/docs/transforms/index.html | 342 ------- .../public/docs/transforms/lookup/index.html | 393 ------- website/public/docs/transforms/map/index.html | 383 ------- .../docs/transforms/objectcreate/index.html | 341 ------- .../public/docs/transforms/project/index.html | 341 ------- .../public/docs/transforms/reduce/index.html | 390 ------- .../docs/transforms/regexreplace/index.html | 341 ------- website/public/index.html | 67 -- website/public/index.xml | 315 ------ website/public/sitemap.xml | 78 -- website/public/tags/index.xml | 10 - website/static/css/darcula.css | 77 -- website/static/css/dark.css | 63 -- website/static/css/flexboxgrid.css | 960 ------------------ website/static/css/funnel.css | 245 ----- website/static/css/highlight.min.css | 1 - website/static/css/html5reset.css | 96 -- website/static/css/hybrid.css | 102 -- website/static/css/monokai-sublime.css | 83 -- website/static/css/poole.css | 283 ------ website/static/css/syntax.css | 66 -- website/static/css/theme.css | 223 ---- website/static/sifter_example.png | Bin 124447 -> 0 bytes 169 files changed, 453 insertions(+), 33341 deletions(-) rename {website => docs}/.hugo_build.lock (100%) create mode 100644 docs/Schema_Description.md rename {website/content => docs}/_index.md (100%) rename docs/{ => assets}/css/darcula.css (100%) rename docs/{ => assets}/css/dark.css (100%) rename docs/{ => assets}/css/flexboxgrid.css (100%) rename docs/{ => assets}/css/funnel.css (100%) rename docs/{ => assets}/css/highlight.min.css (100%) rename docs/{ => assets}/css/html5reset.css (100%) rename docs/{ => assets}/css/hybrid.css (100%) rename docs/{ => assets}/css/monokai-sublime.css (100%) rename docs/{ => assets}/css/poole.css (100%) rename docs/{ => assets}/css/syntax.css (100%) rename docs/{ => assets}/css/theme.css (100%) rename docs/{ => assets}/sifter_example.png (100%) delete mode 100644 docs/categories/index.xml rename {website/content => docs}/docs.md (100%) rename {website/content => docs}/docs/example.md (100%) delete mode 100644 docs/docs/example/index.html delete mode 100644 docs/docs/index.html delete mode 100644 docs/docs/index.xml rename {website/content => docs}/docs/inputs.md (100%) rename {website/content => docs}/docs/inputs/avroLoad.md (100%) delete mode 100644 docs/docs/inputs/avroload/index.html rename {website/content => docs}/docs/inputs/embedded.md (100%) delete mode 100644 docs/docs/inputs/embedded/index.html rename {website/content => docs}/docs/inputs/glob.md (100%) delete mode 100644 docs/docs/inputs/glob/index.html delete mode 100644 docs/docs/inputs/gripperload/index.html delete mode 100644 docs/docs/inputs/index.html rename {website/content => docs}/docs/inputs/jsonLoad.md (100%) delete mode 100644 docs/docs/inputs/jsonload/index.html rename {website/content => docs}/docs/inputs/plugin.md (100%) delete mode 100644 docs/docs/inputs/plugin/index.html rename {website/content => docs}/docs/inputs/sqldump.md (100%) delete mode 100644 docs/docs/inputs/sqldump/index.html rename {website/content => docs}/docs/inputs/sqliteLoad.md (100%) delete mode 100644 docs/docs/inputs/sqliteload/index.html rename {website/content => docs}/docs/inputs/tableLoad.md (100%) delete mode 100644 docs/docs/inputs/tableload/index.html rename {website/content => docs}/docs/inputs/xmlLoad.md (100%) delete mode 100644 docs/docs/inputs/xmlload/index.html delete mode 100644 docs/docs/playbook/index.html rename {website/content => docs}/docs/transforms.md (100%) rename {website/content => docs}/docs/transforms/accumulate.md (100%) delete mode 100644 docs/docs/transforms/accumulate/index.html rename {website/content => docs}/docs/transforms/clean.md (100%) delete mode 100644 docs/docs/transforms/clean/index.html rename {website/content => docs}/docs/transforms/debug.md (100%) delete mode 100644 docs/docs/transforms/debug/index.html rename {website/content => docs}/docs/transforms/distinct.md (100%) delete mode 100644 docs/docs/transforms/distinct/index.html rename {website/content => docs}/docs/transforms/emit.md (100%) delete mode 100644 docs/docs/transforms/emit/index.html rename {website/content => docs}/docs/transforms/fieldParse.md (100%) rename {website/content => docs}/docs/transforms/fieldProcess.md (100%) rename {website/content => docs}/docs/transforms/fieldType.md (100%) delete mode 100644 docs/docs/transforms/fieldparse/index.html delete mode 100644 docs/docs/transforms/fieldprocess/index.html delete mode 100644 docs/docs/transforms/fieldtype/index.html rename {website/content => docs}/docs/transforms/filter.md (100%) delete mode 100644 docs/docs/transforms/filter/index.html rename {website/content => docs}/docs/transforms/flatmap.md (100%) delete mode 100644 docs/docs/transforms/flatmap/index.html rename {website/content => docs}/docs/transforms/from.md (100%) delete mode 100644 docs/docs/transforms/from/index.html rename {website/content => docs}/docs/transforms/graphBuild.md (100%) delete mode 100644 docs/docs/transforms/graphbuild/index.html rename {website/content => docs}/docs/transforms/hash.md (100%) delete mode 100644 docs/docs/transforms/hash/index.html delete mode 100644 docs/docs/transforms/index.html rename {website/content => docs}/docs/transforms/lookup.md (100%) delete mode 100644 docs/docs/transforms/lookup/index.html rename {website/content => docs}/docs/transforms/map.md (100%) delete mode 100644 docs/docs/transforms/map/index.html rename {website/content => docs}/docs/transforms/objectValidate.md (100%) delete mode 100644 docs/docs/transforms/objectvalidate/index.html rename {website/content => docs}/docs/transforms/plugin.md (100%) delete mode 100644 docs/docs/transforms/plugin/index.html rename {website/content => docs}/docs/transforms/project.md (100%) delete mode 100644 docs/docs/transforms/project/index.html rename {website/content => docs}/docs/transforms/reduce.md (100%) delete mode 100644 docs/docs/transforms/reduce/index.html rename {website/content => docs}/docs/transforms/regexReplace.md (100%) delete mode 100644 docs/docs/transforms/regexreplace/index.html rename {website/content => docs}/docs/transforms/split.md (100%) delete mode 100644 docs/docs/transforms/split/index.html rename {website/content => docs}/docs/transforms/tableWrite.md (100%) delete mode 100644 docs/docs/transforms/tablewrite/index.html rename {website/content => docs}/docs/transforms/uuid.md (100%) delete mode 100644 docs/docs/transforms/uuid/index.html delete mode 100644 docs/index.html delete mode 100644 docs/index.xml delete mode 100644 docs/sitemap.xml delete mode 100644 docs/tags/index.xml delete mode 100644 examples/vcfload.yaml delete mode 100644 website/archetypes/default.md delete mode 100644 website/config.yaml delete mode 100644 website/layouts/_default/single.html delete mode 100644 website/layouts/index.html delete mode 100644 website/layouts/partials/head.html delete mode 100644 website/layouts/partials/tail.html delete mode 100644 website/public/categories/index.xml delete mode 100644 website/public/css/darcula.css delete mode 100644 website/public/css/dark.css delete mode 100644 website/public/css/flexboxgrid.css delete mode 100644 website/public/css/funnel.css delete mode 100644 website/public/css/highlight.min.css delete mode 100755 website/public/css/html5reset.css delete mode 100644 website/public/css/hybrid.css delete mode 100644 website/public/css/monokai-sublime.css delete mode 100644 website/public/css/poole.css delete mode 100644 website/public/css/syntax.css delete mode 100644 website/public/css/theme.css delete mode 100644 website/public/docs/example/index.html delete mode 100644 website/public/docs/index.html delete mode 100644 website/public/docs/index.xml delete mode 100644 website/public/docs/inputs/avroload/index.html delete mode 100644 website/public/docs/inputs/embedded/index.html delete mode 100644 website/public/docs/inputs/glob/index.html delete mode 100644 website/public/docs/inputs/gripperload/index.html delete mode 100644 website/public/docs/inputs/index.html delete mode 100644 website/public/docs/inputs/jsonload/index.html delete mode 100644 website/public/docs/inputs/sqldump/index.html delete mode 100644 website/public/docs/inputs/sqliteload/index.html delete mode 100644 website/public/docs/inputs/tableload/index.html delete mode 100644 website/public/docs/inputs/xmlload/index.html delete mode 100644 website/public/docs/playbook/index.html delete mode 100644 website/public/docs/transforms/accumulate/index.html delete mode 100644 website/public/docs/transforms/clean/index.html delete mode 100644 website/public/docs/transforms/debug/index.html delete mode 100644 website/public/docs/transforms/distinct/index.html delete mode 100644 website/public/docs/transforms/emit/index.html delete mode 100644 website/public/docs/transforms/fieldparse/index.html delete mode 100644 website/public/docs/transforms/fieldprocess/index.html delete mode 100644 website/public/docs/transforms/fieldtype/index.html delete mode 100644 website/public/docs/transforms/filter/index.html delete mode 100644 website/public/docs/transforms/from/index.html delete mode 100644 website/public/docs/transforms/graphbuild/index.html delete mode 100644 website/public/docs/transforms/hash/index.html delete mode 100644 website/public/docs/transforms/index.html delete mode 100644 website/public/docs/transforms/lookup/index.html delete mode 100644 website/public/docs/transforms/map/index.html delete mode 100644 website/public/docs/transforms/objectcreate/index.html delete mode 100644 website/public/docs/transforms/project/index.html delete mode 100644 website/public/docs/transforms/reduce/index.html delete mode 100644 website/public/docs/transforms/regexreplace/index.html delete mode 100644 website/public/index.html delete mode 100644 website/public/index.xml delete mode 100644 website/public/sitemap.xml delete mode 100644 website/public/tags/index.xml delete mode 100644 website/static/css/darcula.css delete mode 100644 website/static/css/dark.css delete mode 100644 website/static/css/flexboxgrid.css delete mode 100644 website/static/css/funnel.css delete mode 100644 website/static/css/highlight.min.css delete mode 100755 website/static/css/html5reset.css delete mode 100644 website/static/css/hybrid.css delete mode 100644 website/static/css/monokai-sublime.css delete mode 100644 website/static/css/poole.css delete mode 100644 website/static/css/syntax.css delete mode 100644 website/static/css/theme.css delete mode 100644 website/static/sifter_example.png diff --git a/Makefile b/Makefile index e8b4980..4e3c39c 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -SIFTER_VERSION=0.1.5 +SIFTER_VERSION=0.2.0 #hack to get around submodule weirdness in automated docker builds hub-build: @@ -30,5 +30,3 @@ test: .TEST .TEST: go test ./test -docs: - @go run docschema/main.go | ./docschema/schema-to-markdown.py > Playbook.md diff --git a/website/.hugo_build.lock b/docs/.hugo_build.lock similarity index 100% rename from website/.hugo_build.lock rename to docs/.hugo_build.lock diff --git a/docs/Schema_Description.md b/docs/Schema_Description.md new file mode 100644 index 0000000..71d43cb --- /dev/null +++ b/docs/Schema_Description.md @@ -0,0 +1,114 @@ +# Sifter Playbook Schema and Documentation + +This document provides a comprehensive description of the Sifter Playbook format, its input methods (extractors), and its transformation steps. + +## Playbook Structure + +A Playbook is a YAML file that defines an ETL pipeline. + +| Field | Type | Description | +| :--- | :--- | :--- | +| `class` | string | Should be `sifter`. | +| `name` | string | Unique name of the playbook. | +| `docs` | string | Documentation string for the playbook. | +| `outdir` | string | Default output directory for emitted files. | +| `config` | map | Configuration variables with optional defaults and types (`File`, `Dir`). | +| `inputs` | map | Named extractor definitions. | +| `pipelines` | map | Named transformation pipelines (arrays of steps). | + +--- + +## Configuration Variables (`config`) + +Configuration variables allow playbooks to be parameterized. + +```yaml +config: + variableName: + type: File # or Dir + default: "path/to/default" +``` + +--- + +## Input Methods (Extractors) + +Extractors produce a stream of messages from various sources. + +### `tableLoad` +Loads data from a delimited file (TSV/CSV). +- `input`: Path to the file. +- `sep`: Separator (default `\t`). +- `rowSkip`: Number of header rows to skip. +- `columns`: Optional list of column names. +- `extraColumns`: Field name to store any columns beyond the declared ones. +- `comment`: Comment character (default `#`). +- `lazyQuotes`: Allow lazy quoting in CSV. + +### `jsonLoad` +Loads data from a JSON file (standard or line-delimited). +- `input`: Path to the file. + +### `avroLoad` +Loads data from an Avro object file. +- `input`: Path to the file. + +### `xmlLoad` +Loads and parses XML data. +- `input`: Path to the file. +- `level`: Depth level to start breaking XML into discrete messages. + +### `sqliteLoad` +Loads data from a SQLite database. +- `input`: Path to the database file. +- `query`: SQL SELECT statement. + +### `transposeLoad` +Loads a TSV and transposes it (making rows from columns). +- `input`: Path to the file. +- `rowSkip`: Rows to skip. +- `sep`: Separator. +- `useDB`: Use a temporary disk database for large transpositions. + +### `plugin` (Extractor) +Runs an external command that produces JSON messages to stdout. +- `commandLine`: The command to execute. + +--- + +## Transformation Steps + +Transformation pipelines are arrays of steps. Each step can be one of the following: + +### Core Processing +- `from`: Start a pipeline from a named input or another pipeline. +- `emit`: Write messages to a JSON file. Fields: `name`, `useName` (bool). +- `objectValidate`: Validate messages against a JSON schema. Fields: `title`, `schema` (directory), `uri`. +- `debug`: Print message contents to stdout. +- `plugin` (Transform): Pipe messages through an external script via stdin/stdout. + +### Mapping and Projection +- `project`: Map templates into new fields. Fields: `mapping` (key-template pairs), `rename` (simple rename). +- `map`: Apply a Python/GPython function to each record. Fields: `method` (function name), `python` (code string), `gpython` (path or code). +- `flatMap`: Similar to `map`, but flattens list responses into multiple messages. +- `fieldParse`: Parse a string field (e.g. `key1=val1;key2=val2`) into individual keys. Fields: `field`, `sep`. +- `fieldType`: Cast fields to specific types (`int`, `float`, `list`). Represented as a map of `fieldName: type`. + +### Filtering and Cleaning +- `filter`: Drop messages based on criteria. Fields: `field`, `value`, `match`, `check` (`exists`/`hasValue`/`not`), or `python`/`gpython` code. +- `clean`: Remove fields. Fields: `fields` (list of kept fields), `removeEmpty` (bool), `storeExtra` (target field for extras). +- `dropNull`: Remove fields with `null` values from a message. +- `distinct`: Only emit messages with a unique value once. Field: `value` (template). + +### Grouping and Lookups +- `reduce`: Merge messages sharing a key. Fields: `field` (key), `method`, `python`/`gpython`, `init` (initial data). +- `accumulate`: Group all messages sharing a key into a list. Fields: `field` (key), `dest` (target list field). +- `lookup`: Join data from external files (TSV/JSON). Fields: `tsv`, `json`, `replace`, `lookup`, `copy` (mapping of fields to copy). +- `intervalIntersect`: Match genomic intervals. Fields: `match` (CHR), `start`, `end`, `field` (dest), `json` (source file). + +### Specialized +- `hash`: Generate a hash of a field. Fields: `field` (dest), `value` (template), `method` (`md5`, `sha1`, `sha256`). +- `uuid`: Generate a UUID. Fields: `field`, `value` (seed), `namespace`. +- `graphBuild`: Convert messages into graph vertices and edges using schema definitions. +- `tableWrite`: Write specific fields to a delimited output file. Fields: `output`, `columns`, `sep`, `header`, `skipColumnHeader`. +- `split`: Split a single message into multiple based on a list field. diff --git a/website/content/_index.md b/docs/_index.md similarity index 100% rename from website/content/_index.md rename to docs/_index.md diff --git a/docs/css/darcula.css b/docs/assets/css/darcula.css similarity index 100% rename from docs/css/darcula.css rename to docs/assets/css/darcula.css diff --git a/docs/css/dark.css b/docs/assets/css/dark.css similarity index 100% rename from docs/css/dark.css rename to docs/assets/css/dark.css diff --git a/docs/css/flexboxgrid.css b/docs/assets/css/flexboxgrid.css similarity index 100% rename from docs/css/flexboxgrid.css rename to docs/assets/css/flexboxgrid.css diff --git a/docs/css/funnel.css b/docs/assets/css/funnel.css similarity index 100% rename from docs/css/funnel.css rename to docs/assets/css/funnel.css diff --git a/docs/css/highlight.min.css b/docs/assets/css/highlight.min.css similarity index 100% rename from docs/css/highlight.min.css rename to docs/assets/css/highlight.min.css diff --git a/docs/css/html5reset.css b/docs/assets/css/html5reset.css similarity index 100% rename from docs/css/html5reset.css rename to docs/assets/css/html5reset.css diff --git a/docs/css/hybrid.css b/docs/assets/css/hybrid.css similarity index 100% rename from docs/css/hybrid.css rename to docs/assets/css/hybrid.css diff --git a/docs/css/monokai-sublime.css b/docs/assets/css/monokai-sublime.css similarity index 100% rename from docs/css/monokai-sublime.css rename to docs/assets/css/monokai-sublime.css diff --git a/docs/css/poole.css b/docs/assets/css/poole.css similarity index 100% rename from docs/css/poole.css rename to docs/assets/css/poole.css diff --git a/docs/css/syntax.css b/docs/assets/css/syntax.css similarity index 100% rename from docs/css/syntax.css rename to docs/assets/css/syntax.css diff --git a/docs/css/theme.css b/docs/assets/css/theme.css similarity index 100% rename from docs/css/theme.css rename to docs/assets/css/theme.css diff --git a/docs/sifter_example.png b/docs/assets/sifter_example.png similarity index 100% rename from docs/sifter_example.png rename to docs/assets/sifter_example.png diff --git a/docs/categories/index.xml b/docs/categories/index.xml deleted file mode 100644 index 4b26c88..0000000 --- a/docs/categories/index.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - Categories on Sifter - https://bmeg.github.io/sifter/categories/ - Recent content in Categories on Sifter - Hugo -- gohugo.io - en-us - - - diff --git a/website/content/docs.md b/docs/docs.md similarity index 100% rename from website/content/docs.md rename to docs/docs.md diff --git a/website/content/docs/example.md b/docs/docs/example.md similarity index 100% rename from website/content/docs/example.md rename to docs/docs/example.md diff --git a/docs/docs/example/index.html b/docs/docs/example/index.html deleted file mode 100644 index af38dec..0000000 --- a/docs/docs/example/index.html +++ /dev/null @@ -1,507 +0,0 @@ - - - - - - - - - - - Example · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

Example Pipeline

-

Our first task will be to convert a ZIP code TSV into a set of county level -entries.

-

The input file looks like:

-
ZIP,COUNTYNAME,STATE,STCOUNTYFP,CLASSFP
-36003,Autauga County,AL,01001,H1
-36006,Autauga County,AL,01001,H1
-36067,Autauga County,AL,01001,H1
-36066,Autauga County,AL,01001,H1
-36703,Autauga County,AL,01001,H1
-36701,Autauga County,AL,01001,H1
-36091,Autauga County,AL,01001,H1
-

First is the header of the pipeline. This declares the -unique name of the pipeline and it’s output directory.

-
name: zipcode_map
-outdir: ./
-docs: Converts zipcode TSV into graph elements
-

Next the configuration is declared. In this case the only input is the zipcode TSV. -There is a default value, so the pipeline can be invoked without passing in -any parameters. However, to apply this pipeline to a new input file, the -input parameter zipcode could be used to define the source file.

-
config:
-  schema: ../covid19_datadictionary/gdcdictionary/schemas/
-  zipcode: ../data/ZIP-COUNTY-FIPS_2017-06.csv
-

The inputs section declares data input sources. In this pipeline, there is -only one input, which is to run the table loader.

-
inputs:
-  tableLoad:
-    input: "{{config.zipcode}}"
-    sep: ","
-

Tableload operaters of the input file that was originally passed in using the -inputs stanza. SIFTER string parsing is based on mustache template system. -To access the string passed in the template is {{config.zipcode}}. -The seperator in the file input file is a , so that is also passed in as a -parameter to the extractor.

-

The tableLoad extractor opens up the TSV and generates a one message for -every row in the file. It uses the header of the file to map the column values -into a dictionary. The first row would produce the message:

-
{
-    "ZIP" : "36003",
-    "COUNTYNAME" : "Autauga County",
-    "STATE" : "AL",
-    "STCOUNTYFP" : "01001",
-    "CLASSFP" : "H1"
-}
-

The stream of messages are then passed into the steps listed in the transform -section of the tableLoad extractor.

-

For the current tranform, we want to produce a single entry per STCOUNTYFP, -however, the file has a line per ZIP. We need to run a reduce transform, -that collects rows togeather using a field key, which in this case is "{{row.STCOUNTYFP}}", -and then runs a function merge that takes two messages, merges them togeather -and produces a single output message.

-

The two messages:

-
{ "ZIP" : "36003", "COUNTYNAME" : "Autauga County", "STATE" : "AL", "STCOUNTYFP" : "01001", "CLASSFP" : "H1"}
-{ "ZIP" : "36006", "COUNTYNAME" : "Autauga County", "STATE" : "AL", "STCOUNTYFP" : "01001", "CLASSFP" : "H1"}
-

Would be merged into the message:

-
{ "ZIP" : ["36003", "36006"], "COUNTYNAME" : "Autauga County", "STATE" : "AL", "STCOUNTYFP" : "01001", "CLASSFP" : "H1"}
-

The reduce transform step uses a block of python code to describe the function. -The method field names the function, in this case merge that will be used -as the reduce function.

-
  zipReduce:
-    - from: zipcode
-    - reduce:
-        field: STCOUNTYFP
-        method: merge
-        python: >
-          def merge(x,y):
-            a = x.get('zipcodes', []) + [x['ZIP']]
-            b = y.get('zipcodes', []) + [y['ZIP']]
-            x['zipcodes'] = a + b
-            return x
-

The original messages produced by the loader have all of the information required -by the summary_location object type as described by the JSON schema that was linked -to in the header stanza. However, the data is all under the wrong field names. -To remap the data, we use a project tranformation that uses the template engine -to project data into new files in the message. The template engine has the current -message data in the value row. So the value -FIPS:{{row.STCOUNTYFP}} is mapped into the field id.

-
  - project:
-      mapping:
-        id: "FIPS:{{row.STCOUNTYFP}}"
-        province_state: "{{row.STATE}}"
-        summary_locations: "{{row.STCOUNTYFP}}"
-        county: "{{row.COUNTYNAME}}"
-        submitter_id: "{{row.STCOUNTYFP}}"
-        type: summary_location
-        projects: []
-

Using this projection, the message:

-
{
-  "ZIP" : ["36003", "36006"],
-  "COUNTYNAME" : "Autauga County",
-  "STATE" : "AL",
-  "STCOUNTYFP" : "01001",
-  "CLASSFP" : "H1"
-}
-

would become

-
{
-  "id" : "FIPS:01001",
-  "province_state" : "AL",
-  "summary_locations" : "01001",
-  "county" : "Autauga County",
-  "submitter_id" : "01001",
-  "type" : "summary_location"
-  "projects" : [],
-  "ZIP" : ["36003", "36006"],
-  "COUNTYNAME" : "Autauga County",
-  "STATE" : "AL",
-  "STCOUNTYFP" : "01001",
-  "CLASSFP" : "H1"
-}
-

Now that the data has been remapped, we pass the data into the ‘objectCreate’ -transformation, which will read in the schema for summary_location, check the -message to make sure it matches and then output it.

-
  - objectCreate:
-        class: summary_location
-

Outputs

-

To create an output table, with two columns connecting -ZIP values to STCOUNTYFP values. The STCOUNTYFP is a county level FIPS -code, used by the census office. A single FIPS code my contain many ZIP codes, -and we can use this table later for mapping ids when loading the data into a database.

-
outputs:
-  zip2fips:
-    tableWrite:
-      from: 
-      output: zip2fips
-      columns:
-        - ZIP
-        - STCOUNTYFP
-
-
- -
- - diff --git a/docs/docs/index.html b/docs/docs/index.html deleted file mode 100644 index 6e55370..0000000 --- a/docs/docs/index.html +++ /dev/null @@ -1,528 +0,0 @@ - - - - - - - - - - - Overview · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

Sifter pipelines

-

Sifter pipelines process steams of nested JSON messages. Sifter comes with a number of -file extractors that operate as inputs to these pipelines. The pipeline engine -connects togeather arrays of transform steps into directed acylic graph that is processed -in parallel.

-

Example Message:

-
{
-  "firstName" : "bob",
-  "age" : "25"
-  "friends" : [ "Max", "Alex"]
-}
-

Once a stream of messages are produced, that can be run through a transform -pipeline. A transform pipeline is an array of transform steps, each transform -step can represent a different way to alter the data. The array of transforms link -togeather into a pipe that makes multiple alterations to messages as they are -passed along. There are a number of different transform steps types that can -be done in a transform pipeline these include:

-
    -
  • Projection: creating new fields using a templating engine driven by existing values
  • -
  • Filtering: removing messages
  • -
  • Programmatic transformation: alter messages using an embedded python interpreter
  • -
  • Table based field translation
  • -
  • Outputing the message as a JSON Schema checked object
  • -
-

Script structure

-

Pipeline File

-

An sifter pipeline file is in YAML format and describes an entire processing pipelines. -If is composed of the following sections: config, inputs, pipelines, outputs. In addition, -for tracking, the file will also include name and class entries.

-

-class: sifter
-name: <script name>
-outdir: <where output files should go, relative to this file>
-
-config:
-  <config key>: <config value>
-  <config key>: <config value> 
-  # values that are referenced in pipeline parameters for 
-  # files will be treated like file paths and be 
-  # translated to full paths
-
-inputs:
-  <input name>:
-    <input driver>:
-      <driver config>
-
-pipelines:
-  <pipeline name>:
-    # all pipelines must start with a from step
-    - from: <name of input or pipeline> 
-    - <transform name>:
-       <transform parameters>
-
-outputs:
-  <output name>:
-    <output driver>:
-      <driver config>
-
-

Each sifter file starts with a set of field to let the software know this is a sifter script, and not some random YAML file. There is also a name field for the script. This name will be used for output file creation and logging. Finally, there is an outdir that defines the directory where all output files will be placed. All paths are relative to the script file, so the outdir set to my-results will create the directory my-results in the same directory as the script file, regardless of where the sifter command is invoked.

-
class : sifter
-name: <name of script>
-outdir: <where files should be stored>
-

Config and templating

-

The config section is a set of defined keys that are used throughout the rest of the script.

-

Example config:

-
config:
-  sqlite:  ../../source/chembl/chembl_33/chembl_33_sqlite/chembl_33.db
-  uniprot2ensembl: ../../tables/uniprot2ensembl.tsv
-  schema: ../../schema/
-

Various fields in the script file will be be parsed using a Mustache template engine. For example, to access the various values within the config block, the template {{config.sqlite}}.

-

Inputs

-

The input block defines the various data extractors that will be used to open resources and create streams of JSON messages for processing. The possible input engines include:

-
    -
  • AVRO
  • -
  • JSON
  • -
  • XML
  • -
  • SQL-dump
  • -
  • SQLite
  • -
  • TSV/CSV
  • -
  • GLOB
  • -
-

For any other file types, there is also a plugin option to allow the user to call their own code for opening files.

-

Pipeline

-

The pipelines defined a set of named processing pipelines that can be used to transform data. Each pipeline starts with a from statement that defines where data comes from. It then defines a linear set of transforms that are chained togeather to do processing. Pipelines may used emit steps to output messages to disk. The possible data transform steps include:

-
    -
  • Accumulate
  • -
  • Clean
  • -
  • Distinct
  • -
  • DropNull
  • -
  • Field Parse
  • -
  • Field Process
  • -
  • Field Type
  • -
  • Filter
  • -
  • FlatMap
  • -
  • GraphBuild
  • -
  • Hash
  • -
  • JSON Parse
  • -
  • Lookup
  • -
  • Value Mapping
  • -
  • Object Validation
  • -
  • Project
  • -
  • Reduce
  • -
  • Regex
  • -
  • Split
  • -
  • UUID Generation
  • -
-

Additionally, users are able to define their one transform step types using the plugin step.

-

Example script

-
class: sifter
-
-name: go
-outdir: ../../output/go/
-
-config:
-  oboFile: ../../source/go/go.obo
-  schema: ../../schema
-
-inputs:
-  oboData:
-    plugin:
-      commandLine: ../../util/obo_reader.py {{config.oboFile}}
-
-pipelines:
-  transform:
-    - from: oboData
-    - project:
-        mapping:
-          submitter_id: "{{row.id[0]}}"
-          case_id: "{{row.id[0]}}"
-          id: "{{row.id[0]}}"
-          go_id: "{{row.id[0]}}"
-          project_id: "gene_onotology"
-          namespace: "{{row.namespace[0]}}"
-          name: "{{row.name[0]}}"
-    - map: 
-        method: fix
-        gpython: | 
-          def fix(row):
-            row['definition'] = row['def'][0].strip('"')
-            if 'xref' not in row:
-              row['xref'] = []
-            if 'synonym' not in row:
-              row['synonym'] = []
-            return row
-    - objectValidate:
-        title: GeneOntologyTerm
-        schema: "{{config.schema}}"
-    - emit:
-        name: term
-
-
- -
- - diff --git a/docs/docs/index.xml b/docs/docs/index.xml deleted file mode 100644 index 6262a25..0000000 --- a/docs/docs/index.xml +++ /dev/null @@ -1,256 +0,0 @@ - - - - Docs on Sifter - https://bmeg.github.io/sifter/docs/ - Recent content in Docs on Sifter - Hugo -- gohugo.io - en-us - - - accumulate - https://bmeg.github.io/sifter/docs/transforms/accumulate/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/accumulate/ - accumulate Gather sequential rows into a single record, based on matching a field Parameters name Type Description field string (field path) Field used to match rows dest string field to store accumulated records Example - accumulate: field: model_id dest: rows - - - avroLoad - https://bmeg.github.io/sifter/docs/inputs/avroload/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/avroload/ - avroLoad Load an AvroFile Parameters name Description input Path to input file - - - clean - https://bmeg.github.io/sifter/docs/transforms/clean/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/clean/ - clean Remove fields that don&rsquo;t appear in the desingated list. Parameters name Type Description fields [] string Fields to keep removeEmpty bool Fields with empty values will also be removed storeExtra string Field name to store removed fields Example - clean: fields: - id - synonyms - - - debug - https://bmeg.github.io/sifter/docs/transforms/debug/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/debug/ - debug Print out copy of stream to logging Parameters name Type Description label string Label for log output format bool Use multiline spaced output Example - debug: {} - - - distinct - https://bmeg.github.io/sifter/docs/transforms/distinct/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/distinct/ - distinct Using templated value, allow only the first record for each distinct key Parameters name Type Description value string Key used for distinct value Example - distinct: value: &#34;{{row.key}}&#34; - - - embedded - https://bmeg.github.io/sifter/docs/inputs/embedded/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/embedded/ - embedded Load data from embedded structure Example inputs: data: embedded: - { &#34;name&#34; : &#34;Alice&#34;, &#34;age&#34;: 28 } - { &#34;name&#34; : &#34;Bob&#34;, &#34;age&#34;: 27 } - - - emit - https://bmeg.github.io/sifter/docs/transforms/emit/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/emit/ - emit Send data to output file. The naming of the file is outdir/script name.pipeline name.emit name.json.gz Parameters name Type Description name string Name of emit value example - emit: name: protein_compound_association - - - Example - https://bmeg.github.io/sifter/docs/example/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/example/ - Example Pipeline Our first task will be to convert a ZIP code TSV into a set of county level entries. The input file looks like: ZIP,COUNTYNAME,STATE,STCOUNTYFP,CLASSFP 36003,Autauga County,AL,01001,H1 36006,Autauga County,AL,01001,H1 36067,Autauga County,AL,01001,H1 36066,Autauga County,AL,01001,H1 36703,Autauga County,AL,01001,H1 36701,Autauga County,AL,01001,H1 36091,Autauga County,AL,01001,H1 First is the header of the pipeline. This declares the unique name of the pipeline and it&rsquo;s output directory. name: zipcode_map outdir: ./ docs: Converts zipcode TSV into graph elements Next the configuration is declared. - - - fieldParse - https://bmeg.github.io/sifter/docs/transforms/fieldparse/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/fieldparse/ - - - - fieldProcess - https://bmeg.github.io/sifter/docs/transforms/fieldprocess/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/fieldprocess/ - fieldProcess Create stream of objects based on the contents of a field. If the selected field is an array each of the items in the array will become an independent row. Parameters name Type Description field string Name of field to be processed mapping map[string]string Project templated values into child element itemField string If processing an array of non-dict elements, create a dict as {itemField:element} example - fieldProcess: field: portions mapping: sample: &#34;{{row. - - - fieldType - https://bmeg.github.io/sifter/docs/transforms/fieldtype/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/fieldtype/ - fieldType Set field to specific type, ie cast as float or integer example - fieldType: t_depth: int t_ref_count: int t_alt_count: int n_depth: int n_ref_count: int n_alt_count: int start: int - - - filter - https://bmeg.github.io/sifter/docs/transforms/filter/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/filter/ - filter Filter rows in stream using a number of different methods Parameters name Type Description field string (field path) Field used to match rows value string (template string) Template string to match against match string String to match against check string How to check value, &rsquo;exists&rsquo; or &lsquo;hasValue&rsquo; method string Method name python string Python code string gpython string Python code string run using (https://github.com/go-python/gpython) Example Field based match - filter: field: table match: source_statistics Check based match - - - flatMap - https://bmeg.github.io/sifter/docs/transforms/flatmap/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/flatmap/ - - - - from - https://bmeg.github.io/sifter/docs/transforms/from/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/from/ - from Parmeters Name of data source Example inputs: profileReader: tableLoad: input: &#34;{{config.profiles}}&#34; pipelines: profileProcess: - from: profileReader - - - glob - https://bmeg.github.io/sifter/docs/inputs/glob/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/glob/ - glob Scan files using * based glob statement and open all files as input. Parameters Name Description storeFilename Store value of filename in parameter each row input Path of avro object file to transform xmlLoad xmlLoad configutation tableLoad Run transform pipeline on a TSV or CSV jsonLoad Run a transform pipeline on a multi line json file avroLoad Load data from avro file Example inputs: pubmedRead: glob: input: &#34;{{config.baseline}}/*.xml.gz&#34; xmlLoad: {} - - - graphBuild - https://bmeg.github.io/sifter/docs/transforms/graphbuild/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/graphbuild/ - graphBuild Build graph elements from JSON objects using the JSON Schema graph extensions. example - graphBuild: schema: &#34;{{config.allelesSchema}}&#34; title: Allele - - - hash - https://bmeg.github.io/sifter/docs/transforms/hash/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/hash/ - hash Parameters name Type Description field string Field to store hash value value string Templated string of value to be hashed method string Hashing method: sha1/sha256/md5 example - hash: value: &#34;{{row.contents}}&#34; field: contents-sha1 method: sha1 - - - input plugin - https://bmeg.github.io/sifter/docs/inputs/plugin/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/plugin/ - plugin Run user program for customized data extraction. Example inputs: oboData: plugin: commandLine: ../../util/obo_reader.py {{config.oboFile}} The plugin program is expected to output JSON messages, one per line, to STDOUT that will then be passed to the transform pipelines. Example Plugin The obo_reader.py plugin, it reads a OBO file, such as the kind the describe the GeneOntology, and emits the records as single line JSON messages. #!/usr/bin/env python import re import sys import json re_section = re. - - - Inputs - https://bmeg.github.io/sifter/docs/inputs/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/ - Every playbook consists of a series of inputs. - - - jsonLoad - https://bmeg.github.io/sifter/docs/inputs/jsonload/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/jsonload/ - jsonLoad Load data from a JSON file. Default behavior expects a single dictionary per line. Each line is a seperate entry. The multiline parameter reads all of the lines of the files and returns a single object. Parameters name Description input Path of JSON file to transform multiline Load file as a single multiline JSON object Example inputs: caseData: jsonLoad: input: &#34;{{config.casesJSON}}&#34; - - - lookup - https://bmeg.github.io/sifter/docs/transforms/lookup/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/lookup/ - lookup Using key from current row, get values from a reference source Parameters name Type Description replace string (field path) Field to replace lookup string (template string) Key to use for looking up data copy map[string]string Copy values from record that was found by lookup. The Key/Value record uses the Key as the destination field and copies the field from the retrieved records using the field named in Value tsv TSVTable TSV translation table file json JSONTable JSON data file table LookupTable Inline lookup table pipeline PipelineLookup Use output of a pipeline as a lookup table Example JSON file based lookup The JSON file defined by config. - - - map - https://bmeg.github.io/sifter/docs/transforms/map/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/map/ - map Run function on every row Parameters name Description method Name of function to call python Python code to be run gpython Python code to be run using GPython Example - map: method: response gpython: | def response(x): s = sorted(x[&#34;curve&#34;].items(), key=lambda x:float(x[0])) x[&#39;dose_um&#39;] = [] x[&#39;response&#39;] = [] for d, r in s: try: dn = float(d) rn = float(r) x[&#39;dose_um&#39;].append(dn) x[&#39;response&#39;].append(rn) except ValueError: pass return x - - - objectValidate - https://bmeg.github.io/sifter/docs/transforms/objectvalidate/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/objectvalidate/ - objectValidate Use JSON schema to validate row contents parameters name Type Description title string Title of object to use for validation schema string Path to JSON schema definition example - objectValidate: title: Aliquot schema: &#34;{{config.schema}}&#34; - - - Pipeline Steps - https://bmeg.github.io/sifter/docs/transforms/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/ - Transforms alter the data - - - project - https://bmeg.github.io/sifter/docs/transforms/project/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/project/ - project Populate row with templated values parameters name Type Description mapping map[string]any New fields to be generated from template rename map[string]string Rename field (no template engine) Example - project: mapping: type: sample id: &#34;{{row.sample_id}}&#34; - - - reduce - https://bmeg.github.io/sifter/docs/transforms/reduce/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/reduce/ - reduce Using key from rows, reduce matched records into a single entry Parameters name Type Description field string (field path) Field used to match rows method string Method name python string Python code string gpython string Python code string run using (https://github.com/go-python/gpython) init map[string]any Data to use for first reduce Example - reduce: field: dataset_name method: merge init: { &#34;compounds&#34; : [] } gpython: | def merge(x,y): x[&#34;compounds&#34;] = list(set(y[&#34;compounds&#34;]+x[&#34;compounds&#34;])) return x - - - regexReplace - https://bmeg.github.io/sifter/docs/transforms/regexreplace/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/regexreplace/ - - - - split - https://bmeg.github.io/sifter/docs/transforms/split/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/split/ - split Split a field using string sep Parameters name Type Description field string Field to the split sep string String to use for splitting Example - split: field: methods sep: &#34;;&#34; - - - sqldump - https://bmeg.github.io/sifter/docs/inputs/sqldump/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/sqldump/ - sqlDump Scan file produced produced from sqldump. Parameters Name Type Description input string Path to the SQL dump file tables []string Names of tables to read out Example inputs: database: sqldumpLoad: input: &#34;{{config.sql}}&#34; tables: - cells - cell_tissues - dose_responses - drugs - drug_annots - experiments - profiles - - - sqliteLoad - https://bmeg.github.io/sifter/docs/inputs/sqliteload/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/sqliteload/ - sqliteLoad Extract data from an sqlite file Parameters Name Type Description input string Path to the SQLite file query string SQL select statement based input Example inputs: sqlQuery: sqliteLoad: input: &#34;{{config.sqlite}}&#34; query: &#34;select * from drug_mechanism as a LEFT JOIN MECHANISM_REFS as b on a.MEC_ID=b.MEC_ID LEFT JOIN TARGET_COMPONENTS as c on a.TID=c.TID LEFT JOIN COMPONENT_SEQUENCES as d on c.COMPONENT_ID=d.COMPONENT_ID LEFT JOIN MOLECULE_DICTIONARY as e on a.MOLREGNO=e.MOLREGNO&#34; - - - tableLoad - https://bmeg.github.io/sifter/docs/inputs/tableload/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/tableload/ - tableLoad Extract data from tabular file, includiong TSV and CSV files. Parameters Name Type Description input string File to be transformed rowSkip int Number of header rows to skip columns []string Manually set names of columns extraColumns string Columns beyond originally declared columns will be placed in this array sep string Separator \t for TSVs or , for CSVs Example config: gafFile: ../../source/go/goa_human.gaf.gz inputs: gafLoad: tableLoad: input: &#34;{{config.gafFile}}&#34; columns: - db - id - symbol - qualifier - goID - reference - evidenceCode - from - aspect - name - synonym - objectType - taxon - date - assignedBy - extension - geneProduct - - - tableWrite - https://bmeg.github.io/sifter/docs/transforms/tablewrite/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/tablewrite/ - - - - transform plugin - https://bmeg.github.io/sifter/docs/transforms/plugin/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/plugin/ - plugin Invoke external program for data processing Parameters name Description commandLine Command line program to be called The command line can be written in any language. Sifter and the plugin communicate via NDJSON. Sifter streams the input to the program via STDIN and the plugin returns results via STDOUT. Any loggin or additional data must be sent to STDERR, or it will interupt the stream of messages. The command line code is executed using the base directory of the sifter file as the working directory. - - - uuid - https://bmeg.github.io/sifter/docs/transforms/uuid/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/uuid/ - - - - xmlLoad - https://bmeg.github.io/sifter/docs/inputs/xmlload/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/xmlload/ - xmlLoad Load an XML file Parameters name Description input Path to input file Example inputs: loader: xmlLoad: input: &#34;{{config.xmlPath}}&#34; - - - diff --git a/website/content/docs/inputs.md b/docs/docs/inputs.md similarity index 100% rename from website/content/docs/inputs.md rename to docs/docs/inputs.md diff --git a/website/content/docs/inputs/avroLoad.md b/docs/docs/inputs/avroLoad.md similarity index 100% rename from website/content/docs/inputs/avroLoad.md rename to docs/docs/inputs/avroLoad.md diff --git a/docs/docs/inputs/avroload/index.html b/docs/docs/inputs/avroload/index.html deleted file mode 100644 index 976999e..0000000 --- a/docs/docs/inputs/avroload/index.html +++ /dev/null @@ -1,396 +0,0 @@ - - - - - - - - - - - avroLoad · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

avroLoad

-

Load an AvroFile

-

Parameters

- - - - - - - - - - - - - -
nameDescription
inputPath to input file
- -
- -
- - diff --git a/website/content/docs/inputs/embedded.md b/docs/docs/inputs/embedded.md similarity index 100% rename from website/content/docs/inputs/embedded.md rename to docs/docs/inputs/embedded.md diff --git a/docs/docs/inputs/embedded/index.html b/docs/docs/inputs/embedded/index.html deleted file mode 100644 index b1fd8ac..0000000 --- a/docs/docs/inputs/embedded/index.html +++ /dev/null @@ -1,387 +0,0 @@ - - - - - - - - - - - embedded · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

embedded

-

Load data from embedded structure

-

Example

-
inputs:
-  data:
-    embedded:
-      - { "name" : "Alice", "age": 28 }
-      - { "name" : "Bob", "age": 27 }
-
-
- -
- - diff --git a/website/content/docs/inputs/glob.md b/docs/docs/inputs/glob.md similarity index 100% rename from website/content/docs/inputs/glob.md rename to docs/docs/inputs/glob.md diff --git a/docs/docs/inputs/glob/index.html b/docs/docs/inputs/glob/index.html deleted file mode 100644 index 5039085..0000000 --- a/docs/docs/inputs/glob/index.html +++ /dev/null @@ -1,423 +0,0 @@ - - - - - - - - - - - glob · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

glob

-

Scan files using * based glob statement and open all files -as input.

-

Parameters

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameDescription
storeFilenameStore value of filename in parameter each row
inputPath of avro object file to transform
xmlLoadxmlLoad configutation
tableLoadRun transform pipeline on a TSV or CSV
jsonLoadRun a transform pipeline on a multi line json file
avroLoadLoad data from avro file
-

Example

-
inputs:
-  pubmedRead:
-    glob:
-      input: "{{config.baseline}}/*.xml.gz"
-      xmlLoad: {}
-
-
- -
- - diff --git a/docs/docs/inputs/gripperload/index.html b/docs/docs/inputs/gripperload/index.html deleted file mode 100644 index 2d720cb..0000000 --- a/docs/docs/inputs/gripperload/index.html +++ /dev/null @@ -1,350 +0,0 @@ - - - - - - - - - - - gripperLoad · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/docs/docs/inputs/index.html b/docs/docs/inputs/index.html deleted file mode 100644 index f689cfb..0000000 --- a/docs/docs/inputs/index.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - - - - - - - - Inputs · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

Every playbook consists of a series of inputs.

- -
- -
- - diff --git a/website/content/docs/inputs/jsonLoad.md b/docs/docs/inputs/jsonLoad.md similarity index 100% rename from website/content/docs/inputs/jsonLoad.md rename to docs/docs/inputs/jsonLoad.md diff --git a/docs/docs/inputs/jsonload/index.html b/docs/docs/inputs/jsonload/index.html deleted file mode 100644 index 012d9ba..0000000 --- a/docs/docs/inputs/jsonload/index.html +++ /dev/null @@ -1,405 +0,0 @@ - - - - - - - - - - - jsonLoad · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

jsonLoad

-

Load data from a JSON file. Default behavior expects a single dictionary per line. Each line is a seperate entry. The multiline parameter reads all of the lines of the files and returns a single object.

-

Parameters

- - - - - - - - - - - - - - - - - -
nameDescription
inputPath of JSON file to transform
multilineLoad file as a single multiline JSON object
-

Example

-
inputs:
-  caseData:
-    jsonLoad:
-      input: "{{config.casesJSON}}"
-
-
- -
- - diff --git a/website/content/docs/inputs/plugin.md b/docs/docs/inputs/plugin.md similarity index 100% rename from website/content/docs/inputs/plugin.md rename to docs/docs/inputs/plugin.md diff --git a/docs/docs/inputs/plugin/index.html b/docs/docs/inputs/plugin/index.html deleted file mode 100644 index 3c35ee2..0000000 --- a/docs/docs/inputs/plugin/index.html +++ /dev/null @@ -1,437 +0,0 @@ - - - - - - - - - - - input plugin · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

plugin

-

Run user program for customized data extraction.

-

Example

-
inputs:
-  oboData:
-    plugin:
-      commandLine: ../../util/obo_reader.py {{config.oboFile}}
-

The plugin program is expected to output JSON messages, one per line, to STDOUT that will then -be passed to the transform pipelines.

-

Example Plugin

-

The obo_reader.py plugin, it reads a OBO file, such as the kind the describe the GeneOntology, and emits the -records as single line JSON messages.

-
 #!/usr/bin/env python
-
-import re
-import sys
-import json
-
-re_section = re.compile(r'^\[(.*)\]')
-re_field = re.compile(r'^(\w+): (.*)$')
-
-def obo_parse(handle):
-    rec = None
-    for line in handle:
-        res = re_section.search(line)
-        if res:
-            if rec is not None:
-                yield rec
-            rec = None
-            if res.group(1) == "Term":
-                rec = {"type": res.group(1)}
-        else:
-            if rec is not None:
-                res = re_field.search(line)
-                if res:
-                    key = res.group(1)
-                    val = res.group(2)
-                    val = re.split(" ! | \(|\)", val)
-                    val = ":".join(val[0:3])
-                    if key in rec:
-                        rec[key].append(val)
-                    else:
-                        rec[key] = [val]
-
-    if rec is not None:
-        yield rec
-
-
-def unquote(s):
-    res = re.search(r'"(.*)"', s)
-    if res:
-        return res.group(1)
-    return s
-
-
-with open(sys.argv[1]) as handle:
-    for rec in obo_parse(handle):
-        print(json.dumps(rec))
-
-
- -
- - diff --git a/website/content/docs/inputs/sqldump.md b/docs/docs/inputs/sqldump.md similarity index 100% rename from website/content/docs/inputs/sqldump.md rename to docs/docs/inputs/sqldump.md diff --git a/docs/docs/inputs/sqldump/index.html b/docs/docs/inputs/sqldump/index.html deleted file mode 100644 index 44b2a7c..0000000 --- a/docs/docs/inputs/sqldump/index.html +++ /dev/null @@ -1,416 +0,0 @@ - - - - - - - - - - - sqldump · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

sqlDump

-

Scan file produced produced from sqldump.

-

Parameters

- - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
inputstringPath to the SQL dump file
tables[]stringNames of tables to read out
-

Example

-
inputs:
-  database:
-    sqldumpLoad:
-      input: "{{config.sql}}"
-      tables:
-        - cells
-        - cell_tissues
-        - dose_responses
-        - drugs
-        - drug_annots
-        - experiments
-        - profiles
-
-
- -
- - diff --git a/website/content/docs/inputs/sqliteLoad.md b/docs/docs/inputs/sqliteLoad.md similarity index 100% rename from website/content/docs/inputs/sqliteLoad.md rename to docs/docs/inputs/sqliteLoad.md diff --git a/docs/docs/inputs/sqliteload/index.html b/docs/docs/inputs/sqliteload/index.html deleted file mode 100644 index fd759fb..0000000 --- a/docs/docs/inputs/sqliteload/index.html +++ /dev/null @@ -1,410 +0,0 @@ - - - - - - - - - - - sqliteLoad · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

sqliteLoad

-

Extract data from an sqlite file

-

Parameters

- - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
inputstringPath to the SQLite file
querystringSQL select statement based input
-

Example

-

-inputs:
-  sqlQuery:
-    sqliteLoad:
-      input: "{{config.sqlite}}"
-      query: "select * from drug_mechanism as a LEFT JOIN MECHANISM_REFS as b on a.MEC_ID=b.MEC_ID LEFT JOIN TARGET_COMPONENTS as c on a.TID=c.TID LEFT JOIN COMPONENT_SEQUENCES as d on c.COMPONENT_ID=d.COMPONENT_ID LEFT JOIN MOLECULE_DICTIONARY as e on a.MOLREGNO=e.MOLREGNO"
-
-
- -
- - diff --git a/website/content/docs/inputs/tableLoad.md b/docs/docs/inputs/tableLoad.md similarity index 100% rename from website/content/docs/inputs/tableLoad.md rename to docs/docs/inputs/tableLoad.md diff --git a/docs/docs/inputs/tableload/index.html b/docs/docs/inputs/tableload/index.html deleted file mode 100644 index 4341148..0000000 --- a/docs/docs/inputs/tableload/index.html +++ /dev/null @@ -1,445 +0,0 @@ - - - - - - - - - - - tableLoad · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

tableLoad

-

Extract data from tabular file, includiong TSV and CSV files.

-

Parameters

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
inputstringFile to be transformed
rowSkipintNumber of header rows to skip
columns[]stringManually set names of columns
extraColumnsstringColumns beyond originally declared columns will be placed in this array
sepstringSeparator \t for TSVs or , for CSVs
-

Example

-

-config:
-  gafFile: ../../source/go/goa_human.gaf.gz
-
-inputs:
-  gafLoad:
-    tableLoad:
-      input: "{{config.gafFile}}"
-      columns:
-        - db
-        - id
-        - symbol
-        - qualifier
-        - goID
-        - reference
-        - evidenceCode
-        - from
-        - aspect
-        - name
-        - synonym
-        - objectType
-        - taxon
-        - date
-        - assignedBy
-        - extension
-        - geneProduct
-
-
- -
- - diff --git a/website/content/docs/inputs/xmlLoad.md b/docs/docs/inputs/xmlLoad.md similarity index 100% rename from website/content/docs/inputs/xmlLoad.md rename to docs/docs/inputs/xmlLoad.md diff --git a/docs/docs/inputs/xmlload/index.html b/docs/docs/inputs/xmlload/index.html deleted file mode 100644 index 0266f3f..0000000 --- a/docs/docs/inputs/xmlload/index.html +++ /dev/null @@ -1,401 +0,0 @@ - - - - - - - - - - - xmlLoad · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

xmlLoad

-

Load an XML file

-

Parameters

- - - - - - - - - - - - - -
nameDescription
inputPath to input file
-

Example

-
inputs:
-  loader:
-    xmlLoad:
-      input: "{{config.xmlPath}}"
-
-
- -
- - diff --git a/docs/docs/playbook/index.html b/docs/docs/playbook/index.html deleted file mode 100644 index 4fa4897..0000000 --- a/docs/docs/playbook/index.html +++ /dev/null @@ -1,382 +0,0 @@ - - - - - - - - - - - Sifter Pipeline File · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

Pipeline File

-

An sifter pipeline file is in YAML format and describes an entire processing pipelines. -If is composed of the following sections: config, inputs, pipelines, outputs. In addition, -for tracking, the file will also include name and class entries.

-

-class: sifter
-name: <script name>
-outdir: <where output files should go, relative to this file>
-
-config:
-  <config key>: <config value>
-  <config key>: <config value> 
-  # values that are referenced in pipeline parameters for 
-  # files will be treated like file paths and be 
-  # translated to full paths
-
-inputs:
-  <input name>:
-    <input driver>:
-      <driver config>
-
-pipelines:
-  <pipeline name>:
-    # all pipelines must start with a from step
-    - from: <name of input or pipeline> 
-    - <transform name>:
-       <transform parameters>
-
-outputs:
-  <output name>:
-    <output driver>:
-      <driver config>
-
-
- -
- - diff --git a/website/content/docs/transforms.md b/docs/docs/transforms.md similarity index 100% rename from website/content/docs/transforms.md rename to docs/docs/transforms.md diff --git a/website/content/docs/transforms/accumulate.md b/docs/docs/transforms/accumulate.md similarity index 100% rename from website/content/docs/transforms/accumulate.md rename to docs/docs/transforms/accumulate.md diff --git a/docs/docs/transforms/accumulate/index.html b/docs/docs/transforms/accumulate/index.html deleted file mode 100644 index 06bda8c..0000000 --- a/docs/docs/transforms/accumulate/index.html +++ /dev/null @@ -1,407 +0,0 @@ - - - - - - - - - - - accumulate · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

accumulate

-

Gather sequential rows into a single record, based on matching a field

-

Parameters

- - - - - - - - - - - - - - - - - - - - -
nameTypeDescription
fieldstring (field path)Field used to match rows
deststringfield to store accumulated records
-

Example

-
  - accumulate:
-      field: model_id
-      dest: rows   
-
-
- -
- - diff --git a/website/content/docs/transforms/clean.md b/docs/docs/transforms/clean.md similarity index 100% rename from website/content/docs/transforms/clean.md rename to docs/docs/transforms/clean.md diff --git a/docs/docs/transforms/clean/index.html b/docs/docs/transforms/clean/index.html deleted file mode 100644 index 69bd418..0000000 --- a/docs/docs/transforms/clean/index.html +++ /dev/null @@ -1,413 +0,0 @@ - - - - - - - - - - - clean · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

clean

-

Remove fields that don’t appear in the desingated list.

-

Parameters

- - - - - - - - - - - - - - - - - - - - - - - - - -
nameTypeDescription
fields[] stringFields to keep
removeEmptyboolFields with empty values will also be removed
storeExtrastringField name to store removed fields
-

Example

-
    - clean:
-        fields:
-          - id
-          - synonyms
-
-
- -
- - diff --git a/website/content/docs/transforms/debug.md b/docs/docs/transforms/debug.md similarity index 100% rename from website/content/docs/transforms/debug.md rename to docs/docs/transforms/debug.md diff --git a/docs/docs/transforms/debug/index.html b/docs/docs/transforms/debug/index.html deleted file mode 100644 index 58cd159..0000000 --- a/docs/docs/transforms/debug/index.html +++ /dev/null @@ -1,405 +0,0 @@ - - - - - - - - - - - debug · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

debug

-

Print out copy of stream to logging

-

Parameters

- - - - - - - - - - - - - - - - - - - - -
nameTypeDescription
labelstringLabel for log output
formatboolUse multiline spaced output
-

Example

-
    - debug: {}
-
-
- -
- - diff --git a/website/content/docs/transforms/distinct.md b/docs/docs/transforms/distinct.md similarity index 100% rename from website/content/docs/transforms/distinct.md rename to docs/docs/transforms/distinct.md diff --git a/docs/docs/transforms/distinct/index.html b/docs/docs/transforms/distinct/index.html deleted file mode 100644 index 092a2e0..0000000 --- a/docs/docs/transforms/distinct/index.html +++ /dev/null @@ -1,401 +0,0 @@ - - - - - - - - - - - distinct · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

distinct

-

Using templated value, allow only the first record for each distinct key

-

Parameters

- - - - - - - - - - - - - - - -
nameTypeDescription
valuestringKey used for distinct value
-

Example

-
    - distinct:
-        value: "{{row.key}}"
-
-
- -
- - diff --git a/website/content/docs/transforms/emit.md b/docs/docs/transforms/emit.md similarity index 100% rename from website/content/docs/transforms/emit.md rename to docs/docs/transforms/emit.md diff --git a/docs/docs/transforms/emit/index.html b/docs/docs/transforms/emit/index.html deleted file mode 100644 index a0db035..0000000 --- a/docs/docs/transforms/emit/index.html +++ /dev/null @@ -1,401 +0,0 @@ - - - - - - - - - - - emit · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

emit

-

Send data to output file. The naming of the file is outdir/script name.pipeline name.emit name.json.gz

-

Parameters

- - - - - - - - - - - - - - - -
nameTypeDescription
namestringName of emit value
-

example

-
    - emit:
-        name: protein_compound_association
-
-
- -
- - diff --git a/website/content/docs/transforms/fieldParse.md b/docs/docs/transforms/fieldParse.md similarity index 100% rename from website/content/docs/transforms/fieldParse.md rename to docs/docs/transforms/fieldParse.md diff --git a/website/content/docs/transforms/fieldProcess.md b/docs/docs/transforms/fieldProcess.md similarity index 100% rename from website/content/docs/transforms/fieldProcess.md rename to docs/docs/transforms/fieldProcess.md diff --git a/website/content/docs/transforms/fieldType.md b/docs/docs/transforms/fieldType.md similarity index 100% rename from website/content/docs/transforms/fieldType.md rename to docs/docs/transforms/fieldType.md diff --git a/docs/docs/transforms/fieldparse/index.html b/docs/docs/transforms/fieldparse/index.html deleted file mode 100644 index 122316b..0000000 --- a/docs/docs/transforms/fieldparse/index.html +++ /dev/null @@ -1,379 +0,0 @@ - - - - - - - - - - - fieldParse · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/docs/docs/transforms/fieldprocess/index.html b/docs/docs/transforms/fieldprocess/index.html deleted file mode 100644 index c2085b0..0000000 --- a/docs/docs/transforms/fieldprocess/index.html +++ /dev/null @@ -1,415 +0,0 @@ - - - - - - - - - - - fieldProcess · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

fieldProcess

-

Create stream of objects based on the contents of a field. If the selected field is an array -each of the items in the array will become an independent row.

-

Parameters

- - - - - - - - - - - - - - - - - - - - - - - - - -
nameTypeDescription
fieldstringName of field to be processed
mappingmap[string]stringProject templated values into child element
itemFieldstringIf processing an array of non-dict elements, create a dict as {itemField:element}
-

example

-
    - fieldProcess:
-        field: portions
-        mapping:
-          sample: "{{row.sample_id}}"
-          project_id: "{{row.project_id}}"
-
-
- -
- - diff --git a/docs/docs/transforms/fieldtype/index.html b/docs/docs/transforms/fieldtype/index.html deleted file mode 100644 index fb7cf6f..0000000 --- a/docs/docs/transforms/fieldtype/index.html +++ /dev/null @@ -1,391 +0,0 @@ - - - - - - - - - - - fieldType · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

fieldType

-

Set field to specific type, ie cast as float or integer

-

example

-

-    - fieldType:
-        t_depth: int
-        t_ref_count: int
-        t_alt_count: int
-        n_depth: int
-        n_ref_count: int
-        n_alt_count: int
-        start: int
-
-
- -
- - diff --git a/website/content/docs/transforms/filter.md b/docs/docs/transforms/filter.md similarity index 100% rename from website/content/docs/transforms/filter.md rename to docs/docs/transforms/filter.md diff --git a/docs/docs/transforms/filter/index.html b/docs/docs/transforms/filter/index.html deleted file mode 100644 index f47fe08..0000000 --- a/docs/docs/transforms/filter/index.html +++ /dev/null @@ -1,437 +0,0 @@ - - - - - - - - - - - filter · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

filter

-

Filter rows in stream using a number of different methods

-

Parameters

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
nameTypeDescription
fieldstring (field path)Field used to match rows
valuestring (template string)Template string to match against
matchstringString to match against
checkstringHow to check value, ’exists’ or ‘hasValue’
methodstringMethod name
pythonstringPython code string
gpythonstringPython code string run using (https://github.com/go-python/gpython)
-

Example

-

Field based match

-
    - filter:
-        field: table
-        match: source_statistics
-

Check based match

-
    - filter:
-        field: uniprot
-        check: hasValue
-
-
- -
- - diff --git a/website/content/docs/transforms/flatmap.md b/docs/docs/transforms/flatmap.md similarity index 100% rename from website/content/docs/transforms/flatmap.md rename to docs/docs/transforms/flatmap.md diff --git a/docs/docs/transforms/flatmap/index.html b/docs/docs/transforms/flatmap/index.html deleted file mode 100644 index 9e38b55..0000000 --- a/docs/docs/transforms/flatmap/index.html +++ /dev/null @@ -1,379 +0,0 @@ - - - - - - - - - - - flatMap · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/website/content/docs/transforms/from.md b/docs/docs/transforms/from.md similarity index 100% rename from website/content/docs/transforms/from.md rename to docs/docs/transforms/from.md diff --git a/docs/docs/transforms/from/index.html b/docs/docs/transforms/from/index.html deleted file mode 100644 index bad5bb8..0000000 --- a/docs/docs/transforms/from/index.html +++ /dev/null @@ -1,393 +0,0 @@ - - - - - - - - - - - from · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

from

-

Parmeters

-

Name of data source

-

Example

-

-
-inputs:
-  profileReader:
-    tableLoad:
-      input: "{{config.profiles}}"
-
-pipelines:
-  profileProcess:
-    - from: profileReader
-
-
- -
- - diff --git a/website/content/docs/transforms/graphBuild.md b/docs/docs/transforms/graphBuild.md similarity index 100% rename from website/content/docs/transforms/graphBuild.md rename to docs/docs/transforms/graphBuild.md diff --git a/docs/docs/transforms/graphbuild/index.html b/docs/docs/transforms/graphbuild/index.html deleted file mode 100644 index 0f02b57..0000000 --- a/docs/docs/transforms/graphbuild/index.html +++ /dev/null @@ -1,385 +0,0 @@ - - - - - - - - - - - graphBuild · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

graphBuild

-

Build graph elements from JSON objects using the JSON Schema graph extensions.

-

example

-
      - graphBuild:
-          schema: "{{config.allelesSchema}}"
-          title: Allele
-
-
- -
- - diff --git a/website/content/docs/transforms/hash.md b/docs/docs/transforms/hash.md similarity index 100% rename from website/content/docs/transforms/hash.md rename to docs/docs/transforms/hash.md diff --git a/docs/docs/transforms/hash/index.html b/docs/docs/transforms/hash/index.html deleted file mode 100644 index 546781c..0000000 --- a/docs/docs/transforms/hash/index.html +++ /dev/null @@ -1,412 +0,0 @@ - - - - - - - - - - - hash · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

hash

-

Parameters

- - - - - - - - - - - - - - - - - - - - - - - - - -
nameTypeDescription
fieldstringField to store hash value
valuestringTemplated string of value to be hashed
methodstringHashing method: sha1/sha256/md5
-

example

-
   - hash:
-      value: "{{row.contents}}"
-      field: contents-sha1
-      method: sha1
-
-
- -
- - diff --git a/docs/docs/transforms/index.html b/docs/docs/transforms/index.html deleted file mode 100644 index a514d38..0000000 --- a/docs/docs/transforms/index.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - - - - - - - - Pipeline Steps · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

Transforms alter the data

- -
- -
- - diff --git a/website/content/docs/transforms/lookup.md b/docs/docs/transforms/lookup.md similarity index 100% rename from website/content/docs/transforms/lookup.md rename to docs/docs/transforms/lookup.md diff --git a/docs/docs/transforms/lookup/index.html b/docs/docs/transforms/lookup/index.html deleted file mode 100644 index 77e6db6..0000000 --- a/docs/docs/transforms/lookup/index.html +++ /dev/null @@ -1,467 +0,0 @@ - - - - - - - - - - - lookup · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

lookup

-

Using key from current row, get values from a reference source

-

Parameters

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
nameTypeDescription
replacestring (field path)Field to replace
lookupstring (template string)Key to use for looking up data
copymap[string]stringCopy values from record that was found by lookup. The Key/Value record uses the Key as the destination field and copies the field from the retrieved records using the field named in Value
tsvTSVTableTSV translation table file
jsonJSONTableJSON data file
tableLookupTableInline lookup table
pipelinePipelineLookupUse output of a pipeline as a lookup table
-

Example

-

JSON file based lookup

-

The JSON file defined by config.doseResponseFile is opened and loaded into memory, using the experiment_id field as a primary key.

-
    - lookup:
-        json:
-          input: "{{config.doseResponseFile}}"
-          key: experiment_id
-        lookup: "{{row.experiment_id}}"
-        copy:
-          curve: curve
-

Pipeline output lookup

-

Prepare a table in the pipelines tableGen. Then in recordProcess use that table, indexed by the field primary_key and lookup the value {{row.table_id}} to copy in the contents of the other_data field from the table and add it to the row as my_data.

-

-pipelines:
-
-  tableGen:
-    - from: dataFile
-    #some set of transforms to prepair data
-    #records look like { "primary_key" : "bob", "other_data": "red" }
-
-  recordProcess:
-    - from: recordFile
-    - lookup:
-        pipeline:
-          from: tableGen
-          key: primary_key
-        lookup: "{{row.table_id}}"
-        copy:
-          my_data: other_data
-

Example data:

-

tableGen

-
{ "primary_key" : "bob", "other_data": "red" }
-{ "primary_key" : "alice", "other_data": "blue" }
-

recordProcess input

-
{"id" : "record_1", "table_id":"alice" }
-{"id" : "record_2", "table_id":"bob" }
-

recordProcess output

-
{"id" : "record_1", "table_id":"alice", "my_data" : "blue" }
-{"id" : "record_2", "table_id":"bob", "my_data" : "red" }
-
-
- -
- - diff --git a/website/content/docs/transforms/map.md b/docs/docs/transforms/map.md similarity index 100% rename from website/content/docs/transforms/map.md rename to docs/docs/transforms/map.md diff --git a/docs/docs/transforms/map/index.html b/docs/docs/transforms/map/index.html deleted file mode 100644 index b93585f..0000000 --- a/docs/docs/transforms/map/index.html +++ /dev/null @@ -1,421 +0,0 @@ - - - - - - - - - - - map · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

map

-

Run function on every row

-

Parameters

- - - - - - - - - - - - - - - - - - - - - -
nameDescription
methodName of function to call
pythonPython code to be run
gpythonPython code to be run using GPython
-

Example

-
    - map:
-        method: response
-        gpython: |
-          def response(x):
-            s = sorted(x["curve"].items(), key=lambda x:float(x[0]))
-            x['dose_um'] = []
-            x['response'] = []
-            for d, r in s:
-              try:
-                dn = float(d)
-                rn = float(r)
-                x['dose_um'].append(dn)
-                x['response'].append(rn)
-              except ValueError:
-                pass
-            return x          
-
-
- -
- - diff --git a/website/content/docs/transforms/objectValidate.md b/docs/docs/transforms/objectValidate.md similarity index 100% rename from website/content/docs/transforms/objectValidate.md rename to docs/docs/transforms/objectValidate.md diff --git a/docs/docs/transforms/objectvalidate/index.html b/docs/docs/transforms/objectvalidate/index.html deleted file mode 100644 index e2b6b43..0000000 --- a/docs/docs/transforms/objectvalidate/index.html +++ /dev/null @@ -1,407 +0,0 @@ - - - - - - - - - - - objectValidate · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

objectValidate

-

Use JSON schema to validate row contents

-

parameters

- - - - - - - - - - - - - - - - - - - - -
nameTypeDescription
titlestringTitle of object to use for validation
schemastringPath to JSON schema definition
-

example

-
    - objectValidate:
-        title: Aliquot
-        schema: "{{config.schema}}"
-
-
- -
- - diff --git a/website/content/docs/transforms/plugin.md b/docs/docs/transforms/plugin.md similarity index 100% rename from website/content/docs/transforms/plugin.md rename to docs/docs/transforms/plugin.md diff --git a/docs/docs/transforms/plugin/index.html b/docs/docs/transforms/plugin/index.html deleted file mode 100644 index d286eb5..0000000 --- a/docs/docs/transforms/plugin/index.html +++ /dev/null @@ -1,425 +0,0 @@ - - - - - - - - - - - transform plugin · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

plugin

-

Invoke external program for data processing

-

Parameters

- - - - - - - - - - - - - -
nameDescription
commandLineCommand line program to be called
-

The command line can be written in any language. Sifter and the -plugin communicate via NDJSON. Sifter streams the input to the program via -STDIN and the plugin returns results via STDOUT. Any loggin or additional -data must be sent to STDERR, or it will interupt the stream of messages. -The command line code is executed using the base directory of the -sifter file as the working directory.

-

Example

-
    - plugin:
-        commandLine: "../../util/calc_fingerprint.py"
-

In this case, the plugin code is

-
#!/usr/bin/env python
-
-import sys
-import json
-from rdkit import Chem
-from rdkit.Chem import AllChem
-
-for line in sys.stdin:
-    row = json.loads(line)
-    if "canonical_smiles" in row:
-        smiles = row["canonical_smiles"]
-        m = Chem.MolFromSmiles(smiles)
-        try:
-            fp = AllChem.GetMorganFingerprintAsBitVect(m, radius=2)
-            fingerprint = list(fp)
-            row["morgan_fingerprint_2"] = fingerprint
-        except:
-            pass
-    print(json.dumps(row))
-
-
- -
- - diff --git a/website/content/docs/transforms/project.md b/docs/docs/transforms/project.md similarity index 100% rename from website/content/docs/transforms/project.md rename to docs/docs/transforms/project.md diff --git a/docs/docs/transforms/project/index.html b/docs/docs/transforms/project/index.html deleted file mode 100644 index b87ac10..0000000 --- a/docs/docs/transforms/project/index.html +++ /dev/null @@ -1,408 +0,0 @@ - - - - - - - - - - - project · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

project

-

Populate row with templated values

-

parameters

- - - - - - - - - - - - - - - - - - - - -
nameTypeDescription
mappingmap[string]anyNew fields to be generated from template
renamemap[string]stringRename field (no template engine)
-

Example

-
    - project:
-        mapping:
-          type: sample
-          id: "{{row.sample_id}}"
-
-
- -
- - diff --git a/website/content/docs/transforms/reduce.md b/docs/docs/transforms/reduce.md similarity index 100% rename from website/content/docs/transforms/reduce.md rename to docs/docs/transforms/reduce.md diff --git a/docs/docs/transforms/reduce/index.html b/docs/docs/transforms/reduce/index.html deleted file mode 100644 index 5f9a11a..0000000 --- a/docs/docs/transforms/reduce/index.html +++ /dev/null @@ -1,428 +0,0 @@ - - - - - - - - - - - reduce · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

reduce

-

Using key from rows, reduce matched records into a single entry

-

Parameters

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
nameTypeDescription
fieldstring (field path)Field used to match rows
methodstringMethod name
pythonstringPython code string
gpythonstringPython code string run using (https://github.com/go-python/gpython)
initmap[string]anyData to use for first reduce
-

Example

-
    - reduce:
-        field: dataset_name
-        method: merge
-        init: { "compounds" : [] }
-        gpython: |
-
-          def merge(x,y):
-            x["compounds"] = list(set(y["compounds"]+x["compounds"]))
-            return x
-
-
- -
- - diff --git a/website/content/docs/transforms/regexReplace.md b/docs/docs/transforms/regexReplace.md similarity index 100% rename from website/content/docs/transforms/regexReplace.md rename to docs/docs/transforms/regexReplace.md diff --git a/docs/docs/transforms/regexreplace/index.html b/docs/docs/transforms/regexreplace/index.html deleted file mode 100644 index 1d20965..0000000 --- a/docs/docs/transforms/regexreplace/index.html +++ /dev/null @@ -1,379 +0,0 @@ - - - - - - - - - - - regexReplace · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/website/content/docs/transforms/split.md b/docs/docs/transforms/split.md similarity index 100% rename from website/content/docs/transforms/split.md rename to docs/docs/transforms/split.md diff --git a/docs/docs/transforms/split/index.html b/docs/docs/transforms/split/index.html deleted file mode 100644 index 597538a..0000000 --- a/docs/docs/transforms/split/index.html +++ /dev/null @@ -1,407 +0,0 @@ - - - - - - - - - - - split · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

split

-

Split a field using string sep

-

Parameters

- - - - - - - - - - - - - - - - - - - - -
nameTypeDescription
fieldstringField to the split
sepstringString to use for splitting
-

Example

-
    - split:
-        field: methods
-        sep: ";"
-
-
- -
- - diff --git a/website/content/docs/transforms/tableWrite.md b/docs/docs/transforms/tableWrite.md similarity index 100% rename from website/content/docs/transforms/tableWrite.md rename to docs/docs/transforms/tableWrite.md diff --git a/docs/docs/transforms/tablewrite/index.html b/docs/docs/transforms/tablewrite/index.html deleted file mode 100644 index 6158a1a..0000000 --- a/docs/docs/transforms/tablewrite/index.html +++ /dev/null @@ -1,379 +0,0 @@ - - - - - - - - - - - tableWrite · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/website/content/docs/transforms/uuid.md b/docs/docs/transforms/uuid.md similarity index 100% rename from website/content/docs/transforms/uuid.md rename to docs/docs/transforms/uuid.md diff --git a/docs/docs/transforms/uuid/index.html b/docs/docs/transforms/uuid/index.html deleted file mode 100644 index 28de436..0000000 --- a/docs/docs/transforms/uuid/index.html +++ /dev/null @@ -1,379 +0,0 @@ - - - - - - - - - - - uuid · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 120ebf1..0000000 --- a/docs/index.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - Sifter - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
-
-

SIFTER

-

Sifter is a Extract Tranform Load (ETL) engine. It can be used to -Extract from a number of different data resources, including TSV files, SQLDump -files and external databases. It includes a pipeline description language to -define a set of Transform steps to create object messages that can be -validated using a JSON schema data.

-

Example of sifter code

- -
-
-
- - - - diff --git a/docs/index.xml b/docs/index.xml deleted file mode 100644 index d3388c1..0000000 --- a/docs/index.xml +++ /dev/null @@ -1,263 +0,0 @@ - - - - Sifter - https://bmeg.github.io/sifter/ - Recent content on Sifter - Hugo -- gohugo.io - en-us - - - accumulate - https://bmeg.github.io/sifter/docs/transforms/accumulate/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/accumulate/ - accumulate Gather sequential rows into a single record, based on matching a field Parameters name Type Description field string (field path) Field used to match rows dest string field to store accumulated records Example - accumulate: field: model_id dest: rows - - - avroLoad - https://bmeg.github.io/sifter/docs/inputs/avroload/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/avroload/ - avroLoad Load an AvroFile Parameters name Description input Path to input file - - - clean - https://bmeg.github.io/sifter/docs/transforms/clean/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/clean/ - clean Remove fields that don&rsquo;t appear in the desingated list. Parameters name Type Description fields [] string Fields to keep removeEmpty bool Fields with empty values will also be removed storeExtra string Field name to store removed fields Example - clean: fields: - id - synonyms - - - debug - https://bmeg.github.io/sifter/docs/transforms/debug/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/debug/ - debug Print out copy of stream to logging Parameters name Type Description label string Label for log output format bool Use multiline spaced output Example - debug: {} - - - distinct - https://bmeg.github.io/sifter/docs/transforms/distinct/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/distinct/ - distinct Using templated value, allow only the first record for each distinct key Parameters name Type Description value string Key used for distinct value Example - distinct: value: &#34;{{row.key}}&#34; - - - embedded - https://bmeg.github.io/sifter/docs/inputs/embedded/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/embedded/ - embedded Load data from embedded structure Example inputs: data: embedded: - { &#34;name&#34; : &#34;Alice&#34;, &#34;age&#34;: 28 } - { &#34;name&#34; : &#34;Bob&#34;, &#34;age&#34;: 27 } - - - emit - https://bmeg.github.io/sifter/docs/transforms/emit/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/emit/ - emit Send data to output file. The naming of the file is outdir/script name.pipeline name.emit name.json.gz Parameters name Type Description name string Name of emit value example - emit: name: protein_compound_association - - - Example - https://bmeg.github.io/sifter/docs/example/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/example/ - Example Pipeline Our first task will be to convert a ZIP code TSV into a set of county level entries. The input file looks like: ZIP,COUNTYNAME,STATE,STCOUNTYFP,CLASSFP 36003,Autauga County,AL,01001,H1 36006,Autauga County,AL,01001,H1 36067,Autauga County,AL,01001,H1 36066,Autauga County,AL,01001,H1 36703,Autauga County,AL,01001,H1 36701,Autauga County,AL,01001,H1 36091,Autauga County,AL,01001,H1 First is the header of the pipeline. This declares the unique name of the pipeline and it&rsquo;s output directory. name: zipcode_map outdir: ./ docs: Converts zipcode TSV into graph elements Next the configuration is declared. - - - fieldParse - https://bmeg.github.io/sifter/docs/transforms/fieldparse/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/fieldparse/ - - - - fieldProcess - https://bmeg.github.io/sifter/docs/transforms/fieldprocess/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/fieldprocess/ - fieldProcess Create stream of objects based on the contents of a field. If the selected field is an array each of the items in the array will become an independent row. Parameters name Type Description field string Name of field to be processed mapping map[string]string Project templated values into child element itemField string If processing an array of non-dict elements, create a dict as {itemField:element} example - fieldProcess: field: portions mapping: sample: &#34;{{row. - - - fieldType - https://bmeg.github.io/sifter/docs/transforms/fieldtype/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/fieldtype/ - fieldType Set field to specific type, ie cast as float or integer example - fieldType: t_depth: int t_ref_count: int t_alt_count: int n_depth: int n_ref_count: int n_alt_count: int start: int - - - filter - https://bmeg.github.io/sifter/docs/transforms/filter/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/filter/ - filter Filter rows in stream using a number of different methods Parameters name Type Description field string (field path) Field used to match rows value string (template string) Template string to match against match string String to match against check string How to check value, &rsquo;exists&rsquo; or &lsquo;hasValue&rsquo; method string Method name python string Python code string gpython string Python code string run using (https://github.com/go-python/gpython) Example Field based match - filter: field: table match: source_statistics Check based match - - - flatMap - https://bmeg.github.io/sifter/docs/transforms/flatmap/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/flatmap/ - - - - from - https://bmeg.github.io/sifter/docs/transforms/from/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/from/ - from Parmeters Name of data source Example inputs: profileReader: tableLoad: input: &#34;{{config.profiles}}&#34; pipelines: profileProcess: - from: profileReader - - - glob - https://bmeg.github.io/sifter/docs/inputs/glob/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/glob/ - glob Scan files using * based glob statement and open all files as input. Parameters Name Description storeFilename Store value of filename in parameter each row input Path of avro object file to transform xmlLoad xmlLoad configutation tableLoad Run transform pipeline on a TSV or CSV jsonLoad Run a transform pipeline on a multi line json file avroLoad Load data from avro file Example inputs: pubmedRead: glob: input: &#34;{{config.baseline}}/*.xml.gz&#34; xmlLoad: {} - - - graphBuild - https://bmeg.github.io/sifter/docs/transforms/graphbuild/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/graphbuild/ - graphBuild Build graph elements from JSON objects using the JSON Schema graph extensions. example - graphBuild: schema: &#34;{{config.allelesSchema}}&#34; title: Allele - - - hash - https://bmeg.github.io/sifter/docs/transforms/hash/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/hash/ - hash Parameters name Type Description field string Field to store hash value value string Templated string of value to be hashed method string Hashing method: sha1/sha256/md5 example - hash: value: &#34;{{row.contents}}&#34; field: contents-sha1 method: sha1 - - - input plugin - https://bmeg.github.io/sifter/docs/inputs/plugin/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/plugin/ - plugin Run user program for customized data extraction. Example inputs: oboData: plugin: commandLine: ../../util/obo_reader.py {{config.oboFile}} The plugin program is expected to output JSON messages, one per line, to STDOUT that will then be passed to the transform pipelines. Example Plugin The obo_reader.py plugin, it reads a OBO file, such as the kind the describe the GeneOntology, and emits the records as single line JSON messages. #!/usr/bin/env python import re import sys import json re_section = re. - - - Inputs - https://bmeg.github.io/sifter/docs/inputs/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/ - Every playbook consists of a series of inputs. - - - jsonLoad - https://bmeg.github.io/sifter/docs/inputs/jsonload/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/jsonload/ - jsonLoad Load data from a JSON file. Default behavior expects a single dictionary per line. Each line is a seperate entry. The multiline parameter reads all of the lines of the files and returns a single object. Parameters name Description input Path of JSON file to transform multiline Load file as a single multiline JSON object Example inputs: caseData: jsonLoad: input: &#34;{{config.casesJSON}}&#34; - - - lookup - https://bmeg.github.io/sifter/docs/transforms/lookup/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/lookup/ - lookup Using key from current row, get values from a reference source Parameters name Type Description replace string (field path) Field to replace lookup string (template string) Key to use for looking up data copy map[string]string Copy values from record that was found by lookup. The Key/Value record uses the Key as the destination field and copies the field from the retrieved records using the field named in Value tsv TSVTable TSV translation table file json JSONTable JSON data file table LookupTable Inline lookup table pipeline PipelineLookup Use output of a pipeline as a lookup table Example JSON file based lookup The JSON file defined by config. - - - map - https://bmeg.github.io/sifter/docs/transforms/map/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/map/ - map Run function on every row Parameters name Description method Name of function to call python Python code to be run gpython Python code to be run using GPython Example - map: method: response gpython: | def response(x): s = sorted(x[&#34;curve&#34;].items(), key=lambda x:float(x[0])) x[&#39;dose_um&#39;] = [] x[&#39;response&#39;] = [] for d, r in s: try: dn = float(d) rn = float(r) x[&#39;dose_um&#39;].append(dn) x[&#39;response&#39;].append(rn) except ValueError: pass return x - - - objectValidate - https://bmeg.github.io/sifter/docs/transforms/objectvalidate/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/objectvalidate/ - objectValidate Use JSON schema to validate row contents parameters name Type Description title string Title of object to use for validation schema string Path to JSON schema definition example - objectValidate: title: Aliquot schema: &#34;{{config.schema}}&#34; - - - Overview - https://bmeg.github.io/sifter/docs/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/ - Sifter pipelines Sifter pipelines process steams of nested JSON messages. Sifter comes with a number of file extractors that operate as inputs to these pipelines. The pipeline engine connects togeather arrays of transform steps into directed acylic graph that is processed in parallel. Example Message: { &#34;firstName&#34; : &#34;bob&#34;, &#34;age&#34; : &#34;25&#34; &#34;friends&#34; : [ &#34;Max&#34;, &#34;Alex&#34;] } Once a stream of messages are produced, that can be run through a transform pipeline. - - - Pipeline Steps - https://bmeg.github.io/sifter/docs/transforms/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/ - Transforms alter the data - - - project - https://bmeg.github.io/sifter/docs/transforms/project/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/project/ - project Populate row with templated values parameters name Type Description mapping map[string]any New fields to be generated from template rename map[string]string Rename field (no template engine) Example - project: mapping: type: sample id: &#34;{{row.sample_id}}&#34; - - - reduce - https://bmeg.github.io/sifter/docs/transforms/reduce/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/reduce/ - reduce Using key from rows, reduce matched records into a single entry Parameters name Type Description field string (field path) Field used to match rows method string Method name python string Python code string gpython string Python code string run using (https://github.com/go-python/gpython) init map[string]any Data to use for first reduce Example - reduce: field: dataset_name method: merge init: { &#34;compounds&#34; : [] } gpython: | def merge(x,y): x[&#34;compounds&#34;] = list(set(y[&#34;compounds&#34;]+x[&#34;compounds&#34;])) return x - - - regexReplace - https://bmeg.github.io/sifter/docs/transforms/regexreplace/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/regexreplace/ - - - - split - https://bmeg.github.io/sifter/docs/transforms/split/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/split/ - split Split a field using string sep Parameters name Type Description field string Field to the split sep string String to use for splitting Example - split: field: methods sep: &#34;;&#34; - - - sqldump - https://bmeg.github.io/sifter/docs/inputs/sqldump/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/sqldump/ - sqlDump Scan file produced produced from sqldump. Parameters Name Type Description input string Path to the SQL dump file tables []string Names of tables to read out Example inputs: database: sqldumpLoad: input: &#34;{{config.sql}}&#34; tables: - cells - cell_tissues - dose_responses - drugs - drug_annots - experiments - profiles - - - sqliteLoad - https://bmeg.github.io/sifter/docs/inputs/sqliteload/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/sqliteload/ - sqliteLoad Extract data from an sqlite file Parameters Name Type Description input string Path to the SQLite file query string SQL select statement based input Example inputs: sqlQuery: sqliteLoad: input: &#34;{{config.sqlite}}&#34; query: &#34;select * from drug_mechanism as a LEFT JOIN MECHANISM_REFS as b on a.MEC_ID=b.MEC_ID LEFT JOIN TARGET_COMPONENTS as c on a.TID=c.TID LEFT JOIN COMPONENT_SEQUENCES as d on c.COMPONENT_ID=d.COMPONENT_ID LEFT JOIN MOLECULE_DICTIONARY as e on a.MOLREGNO=e.MOLREGNO&#34; - - - tableLoad - https://bmeg.github.io/sifter/docs/inputs/tableload/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/tableload/ - tableLoad Extract data from tabular file, includiong TSV and CSV files. Parameters Name Type Description input string File to be transformed rowSkip int Number of header rows to skip columns []string Manually set names of columns extraColumns string Columns beyond originally declared columns will be placed in this array sep string Separator \t for TSVs or , for CSVs Example config: gafFile: ../../source/go/goa_human.gaf.gz inputs: gafLoad: tableLoad: input: &#34;{{config.gafFile}}&#34; columns: - db - id - symbol - qualifier - goID - reference - evidenceCode - from - aspect - name - synonym - objectType - taxon - date - assignedBy - extension - geneProduct - - - tableWrite - https://bmeg.github.io/sifter/docs/transforms/tablewrite/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/tablewrite/ - - - - transform plugin - https://bmeg.github.io/sifter/docs/transforms/plugin/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/plugin/ - plugin Invoke external program for data processing Parameters name Description commandLine Command line program to be called The command line can be written in any language. Sifter and the plugin communicate via NDJSON. Sifter streams the input to the program via STDIN and the plugin returns results via STDOUT. Any loggin or additional data must be sent to STDERR, or it will interupt the stream of messages. The command line code is executed using the base directory of the sifter file as the working directory. - - - uuid - https://bmeg.github.io/sifter/docs/transforms/uuid/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/uuid/ - - - - xmlLoad - https://bmeg.github.io/sifter/docs/inputs/xmlload/ - Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/xmlload/ - xmlLoad Load an XML file Parameters name Description input Path to input file Example inputs: loader: xmlLoad: input: &#34;{{config.xmlPath}}&#34; - - - diff --git a/docs/sitemap.xml b/docs/sitemap.xml deleted file mode 100644 index 1295605..0000000 --- a/docs/sitemap.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - https://bmeg.github.io/sifter/ - - https://bmeg.github.io/sifter/docs/transforms/accumulate/ - - https://bmeg.github.io/sifter/docs/inputs/avroload/ - - https://bmeg.github.io/sifter/categories/ - - https://bmeg.github.io/sifter/docs/transforms/clean/ - - https://bmeg.github.io/sifter/docs/transforms/debug/ - - https://bmeg.github.io/sifter/docs/transforms/distinct/ - - https://bmeg.github.io/sifter/docs/ - - https://bmeg.github.io/sifter/docs/inputs/embedded/ - - https://bmeg.github.io/sifter/docs/transforms/emit/ - - https://bmeg.github.io/sifter/docs/example/ - - https://bmeg.github.io/sifter/docs/transforms/fieldparse/ - - https://bmeg.github.io/sifter/docs/transforms/fieldprocess/ - - https://bmeg.github.io/sifter/docs/transforms/fieldtype/ - - https://bmeg.github.io/sifter/docs/transforms/filter/ - - https://bmeg.github.io/sifter/docs/transforms/flatmap/ - - https://bmeg.github.io/sifter/docs/transforms/from/ - - https://bmeg.github.io/sifter/docs/inputs/glob/ - - https://bmeg.github.io/sifter/docs/transforms/graphbuild/ - - https://bmeg.github.io/sifter/docs/transforms/hash/ - - https://bmeg.github.io/sifter/docs/inputs/plugin/ - - https://bmeg.github.io/sifter/docs/inputs/ - - https://bmeg.github.io/sifter/docs/inputs/jsonload/ - - https://bmeg.github.io/sifter/docs/transforms/lookup/ - - https://bmeg.github.io/sifter/docs/transforms/map/ - - https://bmeg.github.io/sifter/docs/transforms/objectvalidate/ - - https://bmeg.github.io/sifter/docs/ - - https://bmeg.github.io/sifter/docs/transforms/ - - https://bmeg.github.io/sifter/docs/transforms/project/ - - https://bmeg.github.io/sifter/docs/transforms/reduce/ - - https://bmeg.github.io/sifter/docs/transforms/regexreplace/ - - https://bmeg.github.io/sifter/docs/transforms/split/ - - https://bmeg.github.io/sifter/docs/inputs/sqldump/ - - https://bmeg.github.io/sifter/docs/inputs/sqliteload/ - - https://bmeg.github.io/sifter/docs/inputs/tableload/ - - https://bmeg.github.io/sifter/docs/transforms/tablewrite/ - - https://bmeg.github.io/sifter/tags/ - - https://bmeg.github.io/sifter/docs/transforms/plugin/ - - https://bmeg.github.io/sifter/docs/transforms/uuid/ - - https://bmeg.github.io/sifter/docs/inputs/xmlload/ - - diff --git a/docs/tags/index.xml b/docs/tags/index.xml deleted file mode 100644 index 41241ef..0000000 --- a/docs/tags/index.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - Tags on Sifter - https://bmeg.github.io/sifter/tags/ - Recent content in Tags on Sifter - Hugo -- gohugo.io - en-us - - - diff --git a/examples/cbio.yaml b/examples/cbio.yaml index 79e52f6..8ce5e5c 100644 --- a/examples/cbio.yaml +++ b/examples/cbio.yaml @@ -1,127 +1,144 @@ -class: PlayBook +class: sifter name: CBioPortal -inputs: - tar: - type: File - geneTable: - type: File - -schema: bmeg-dictionary/gdcdictionary/schemas +config: + tar: "" + geneTable: "" + schema: bmeg-dictionary/gdcdictionary/schemas -steps: - - desc: Untar - untar: - input: "{{inputs.tar}}" - - desc: Loading Patient List +inputs: + untar: + plugin: + commandLine: tar -xzf {{config.tar}} + + patientReader: tableLoad: input: data_clinical_patient.txt - transform: - - debug: {} - - project: - mapping: - id : "{{row.PATIENT_ID}}" - submitter_id : "{{row.PATIENT_ID}}" - type: "case" - experiments: - submitter_id: "TCGA" - - objectCreate: - class: case - - desc: Loading Sample List + + sampleReader: tableLoad: input: data_clinical_sample.txt - transform: - - fork: - transform: - - - - project: - mapping: - id : "{{row.SAMPLE_ID}}" - submitter_id : "{{row.SAMPLE_ID}}" - cases: - submitter_id: "{{row.PATIENT_ID}}" - type: "sample" - - objectCreate: - class: sample - - - - project: - mapping: - id : "{{row.SAMPLE_ID}}-0000" - submitter_id : "{{row.SAMPLE_ID}}-0000" - samples: - submitter_id: "{{row.SAMPLE_ID}}" - type: "aliquot" - - objectCreate: - class: aliquot - - fileGlob: - files: [ data_RNA_Seq_expression_median.txt, data_RNA_Seq_V2_expression_median.txt ] - limit: 1 - inputName: rnaFile - steps: - - desc: Transpose RNA file - transposeFile: - input: "{{inputs.rnaFile}}" - output: data_RNA_Seq_expression_median_transpose.txt - - - desc: Loading RNA File - tableLoad: - input: data_RNA_Seq_expression_median_transpose.txt - rowSkip: 1 - transform: - - project: - mapping: - id: "gexp:{{row.Entrez_Gene_Id}}" #after the transpose, the index column header is `Entrez_Gene_Id` - aliquot_id: "{{row.Entrez_Gene_Id}}-0000" - - tableProject: - input: "{{inputs.geneTable}}" - - map: - method: nodeMap - python: > - def nodeMap(x): - values = {} - for k, v in x.items(): - if k != "id" and k != "aliquot_id" and k != "Entrez_Gene_Id": - values[k] = v - return { - "id" : x["id"], - "aliquot_id" : x["aliquot_id"], - "metric" : "OTHER", - "values": values - } - - objectCreate: - class: gene_expression - - desc: Loading Mutations + + rnaReader: + transposeLoad: + input: data_RNA_Seq_expression_median.txt + rowSkip: 1 + + mutationReader: tableLoad: input: data_mutations_extended.txt - transform: - - alleleID: - dst: allele_id - prefix: "Allele:" - genome: GRCh37 - chromosome: "{{row.Chromosome}}" - start: "{{row.Start_Position}}" - end: "{{row.End_Position}}" - reference_bases: "{{row.Reference_Allele}}" - alternate_bases: "{{row.Tumor_Seq_Allele1}}" - - project: - mapping: - aliquot: "{{row.Tumor_Sample_Barcode}}-0000" - ref: "{{row.Reference_Allele}}" - alt: "{{row.Tumor_Seq_Allele1}}" - ensembl_transcript: "{{row.Transcript_ID}}" - - objectCreate: - class: somatic_variant - - project: - mapping: - genome: "{{row.NCBI_Build}}" - chromosome: "{{row.Chromosome}}" - start: "{{row.Start_Position}}" - end: "{{row.End_Position}}" - strand: "{{row.Strand}}" - reference_bases: "{{row.Reference_Allele}}" - alternate_bases: "{{row.Tumor_Seq_Allele1}}" - hugo_symbol: "{{row.Hugo_Symbol}}" - effect: "{{row.Variant_Classification}}" - - objectCreate: - class: allele + +pipelines: + cases: + - from: patientReader + - project: + mapping: + id: "{{row.PATIENT_ID}}" + submitter_id: "{{row.PATIENT_ID}}" + type: "case" + experiments: + submitter_id: "TCGA" + - objectValidate: + title: Case + schema: "{{config.schema}}" + - emit: + name: case + + samples: + - from: sampleReader + - project: + mapping: + id: "{{row.SAMPLE_ID}}" + submitter_id: "{{row.SAMPLE_ID}}" + cases: + submitter_id: "{{row.PATIENT_ID}}" + type: "sample" + - objectValidate: + title: Sample + schema: "{{config.schema}}" + - emit: + name: sample + + aliquots: + - from: sampleReader + - project: + mapping: + id: "{{row.SAMPLE_ID}}-0000" + submitter_id: "{{row.SAMPLE_ID}}-0000" + samples: + submitter_id: "{{row.SAMPLE_ID}}" + type: "aliquot" + - objectValidate: + title: Aliquot + schema: "{{config.schema}}" + - emit: + name: aliquot + + gene_expression: + - from: rnaReader + - project: + mapping: + id: "gexp:{{row.Entrez_Gene_Id}}" + aliquot_id: "{{row.Entrez_Gene_Id}}-0000" + - lookup: + tsv: + input: "{{config.geneTable}}" + lookup: "{{row.Entrez_Gene_Id}}" + - map: + method: nodeMap + python: | + def nodeMap(x): + values = {} + for k, v in x.items(): + if k not in ["id", "aliquot_id", "Entrez_Gene_Id"]: + values[k] = v + return { + "id": x["id"], + "aliquot_id": x["aliquot_id"], + "metric": "OTHER", + "values": values + } + - emit: + name: gene_expression + + mutations: + - from: mutationReader + - map: + method: alleleID + python: | + import hashlib + def alleleID(row): + s = "GRCh37" + row["Chromosome"] + str(row["Start_Position"]) + str(row["End_Position"]) + row["Reference_Allele"] + row["Tumor_Seq_Allele1"] + row["allele_id"] = "Allele:" + hashlib.sha1(s.encode()).hexdigest() + return row + - project: + mapping: + aliquot: "{{row.Tumor_Sample_Barcode}}-0000" + ref: "{{row.Reference_Allele}}" + alt: "{{row.Tumor_Seq_Allele1}}" + ensembl_transcript: "{{row.Transcript_ID}}" + - objectValidate: + title: SomaticVariant + schema: "{{config.schema}}" + - emit: + name: somatic_variant + + alleles: + - from: mutationReader + - project: + mapping: + genome: "{{row.NCBI_Build}}" + chromosome: "{{row.Chromosome}}" + start: "{{row.Start_Position}}" + end: "{{row.End_Position}}" + strand: "{{row.Strand}}" + reference_bases: "{{row.Reference_Allele}}" + alternate_bases: "{{row.Tumor_Seq_Allele1}}" + hugo_symbol: "{{row.Hugo_Symbol}}" + effect: "{{row.Variant_Classification}}" + - objectValidate: + title: Allele + schema: "{{config.schema}}" + - emit: + name: allele diff --git a/examples/gdc-convert.yaml b/examples/gdc-convert.yaml index 1488240..861b83c 100644 --- a/examples/gdc-convert.yaml +++ b/examples/gdc-convert.yaml @@ -1,85 +1,96 @@ -class: Playbook -name: GDCCpnvert +class: sifter +name: GDCConvert -inputs: {} +config: + schema: bmeg-dictionary/gdcdictionary/schemas -schema: bmeg-dictionary/gdcdictionary/schemas - -steps: - - desc: Scrape GDC Projects - script: - dockerImage: bmeg/sifter-gdc-scan - command: [/opt/gdc-scan.py, projects] - - desc: Scrape GDC Cases - script: - dockerImage: bmeg/sifter-gdc-scan - command: [/opt/gdc-scan.py, cases] - - desc: Loading ProjectData +inputs: + projects_scrape: + plugin: + commandLine: docker run --rm bmeg/sifter-gdc-scan /opt/gdc-scan.py projects + cases_scrape: + plugin: + commandLine: docker run --rm bmeg/sifter-gdc-scan /opt/gdc-scan.py cases + projects_data: jsonLoad: input: out.projects.json - transform: - - fork: - transform: - - - - project: - mapping: - code: "{{row.project_id}}" - programs: "{{row.program.name}}" - - objectCreate: - class: project - - - - project: - mapping: - code: "{{row.project_id}}" - programs: "{{row.program.name}}" - submitter_id: "{{row.program.name}}" - projects: "{{row.project_id}}" - type: experiment - - objectCreate: - class: experiment - - desc: Loading CaseData + cases_data: jsonLoad: input: out.case.json - transform: - - project: - mapping: - studies: "{{row.project.project_id}}" - experiments: "exp:{{row.project.project_id}}" - type: case - - objectCreate: - class: case - - fieldProcess: - field: samples - mapping: - cases: "{{row.id}}" - steps: - #- debug: {} - - project: - mapping: - type: sample - id: "{{row.sample_id}}" - - objectCreate: - class: sample - - fieldProcess: - field: portions - mapping: - samples: "{{row.id}}" - steps: - - fieldProcess: - field: analytes - mapping: - samples: "{{row.samples}}" - steps: - - fieldProcess: - field: aliquots - mapping: - samples: "{{row.samples}}" - steps: - - project: - mapping: - type: aliquot - id: "{{row.aliquot_id}}" - - objectCreate: - class: aliquot + +pipelines: + projects: + - from: projects_data + - project: + mapping: + code: "{{row.project_id}}" + programs: "{{row.program.name}}" + - objectValidate: + title: project + schema: "{{config.schema}}" + - emit: + name: project + + experiments: + - from: projects_data + - project: + mapping: + code: "{{row.project_id}}" + programs: "{{row.program.name}}" + submitter_id: "{{row.program.name}}" + projects: "{{row.project_id}}" + type: experiment + - objectValidate: + title: experiment + schema: "{{config.schema}}" + - emit: + name: experiment + + cases: + - from: cases_data + - project: + mapping: + studies: "{{row.project.project_id}}" + experiments: "exp:{{row.project.project_id}}" + type: case + - objectValidate: + title: case + schema: "{{config.schema}}" + - emit: + name: case + + samples: + - from: cases_data + - fieldProcess: + field: samples + - project: + mapping: + type: sample + id: "{{row.sample_id}}" + - objectValidate: + title: sample + schema: "{{config.schema}}" + - emit: + name: sample + + aliquots: + - from: cases_data + - fieldProcess: + field: samples + - fieldProcess: + field: portions + - fieldProcess: + field: analytes + - fieldProcess: + field: aliquots + - project: + mapping: + type: aliquot + id: "{{row.aliquot_id}}" + - objectValidate: + title: aliquot + schema: "{{config.schema}}" + - emit: + name: aliquot diff --git a/examples/gene-table.yaml b/examples/gene-table.yaml index 973a2e5..65d2956 100644 --- a/examples/gene-table.yaml +++ b/examples/gene-table.yaml @@ -1,19 +1,14 @@ -class: Playbook +class: sifter +name: gene-table - -desc: > - This takes a Gene TSV, filters rows, selects columns and outputs a 2 - column TSV into the working directory +config: + geneTSV: ftp://ftp.ncbi.nih.gov/gene/DATA/gene2ensembl.gz inputs: - geneTSV: - type: File - default: ftp://ftp.ncbi.nih.gov/gene/DATA/gene2ensembl.gz - -steps: - - tableLoad: - input: "{{inputs.geneTSV}}" + geneReader: + tableLoad: + input: "{{config.geneTSV}}" columns: - tax_id - GeneID @@ -22,13 +17,15 @@ steps: - Ensembl_rna_identifier - protein_accession.version - Ensembl_protein_identifier - transform: - - filter: - field: row.tax_id - match: "9606" - steps: - - tableWrite: - output: "gene.table" - columns: - - GeneID - - Ensembl_gene_identifier + +pipelines: + transform: + - from: geneReader + - filter: + field: tax_id + match: "9606" + - tableWrite: + output: gene.table + columns: + - GeneID + - Ensembl_gene_identifier diff --git a/examples/genome.yaml b/examples/genome.yaml index 564a7c7..67ec9ea 100644 --- a/examples/genome.yaml +++ b/examples/genome.yaml @@ -1,22 +1,15 @@ -class: Playbook +class: sifter name: RefGenome -inputs: - gtf: - type: File - default: ftp://ftp.ensembl.org/pub/grch37/release-96/gff3/homo_sapiens/Homo_sapiens.GRCh37.87.gff3.gz - - schema: - type: Directory - default: ./bmeg-dictionary/gdcdictionary/schemas +config: + gtfPath: ftp://ftp.ensembl.org/pub/grch37/release-96/gff3/homo_sapiens/Homo_sapiens.GRCh37.87.gff3.gz + schema: ./bmeg-dictionary/gdcdictionary/schemas -schema: "{{inputs.schema}}" - -steps: - - desc: GTF Seq +inputs: + gtfReader: tableLoad: - input: "{{inputs.gtf}}" + input: "{{config.gtfPath}}" columns: - seqid - source @@ -27,60 +20,73 @@ steps: - strand - phase - attributes - transform: - - fieldMap: - col: attributes - sep: ";" - - fieldType: - start: int - end: int - - filter: - field: row.type - match: exon - steps: - - regexReplace: - col: "{{row.attributes.Parent}}" - regex: "^transcript:" - replace: "" - dst: transcript_id - - project: - mapping: - exon_id : "{{row.attributes.exon_id}}" - - map: - method: mapList - python: > - def mapList(x): - x['transcript_id'] = [x['transcript_id']] - return x - - reduce: - field: "{{row.exon_id}}" - method: merge - python: > - def merge(x,y): - x['transcript_id'] = x['transcript_id'] + y['transcript_id'] - return x - - objectCreate: - class: exon - - filter: - field: row.type - match: gene - steps: - - project: - mapping: - gene_id : "{{row.attributes.gene_id}}" - - objectCreate: - class: gene - - filter: - field: row.type - match: mRNA - steps: - - regexReplace: - col: "{{row.attributes.Parent}}" - regex: "^gene:" - replace: "" - dst: gene_id - - project: - mapping: - transcript_id : "{{row.attributes.transcript_id}}" - - objectCreate: - class: transcript + +pipelines: + transform: + - from: gtfReader + - fieldParse: + field: attributes + sep: ";" + - fieldType: + start: integer + end: integer + + exons: + - from: transform + - filter: + field: type + match: exon + - regexReplace: + field: Parent + regex: "^transcript:" + replace: "" + dst: transcript_id + - project: + mapping: + exon_id: "{{row.exon_id}}" + transcript_id: ["{{row.transcript_id}}"] + - reduce: + field: exon_id + method: merge + python: | + def merge(x, y): + x['transcript_id'] = x['transcript_id'] + y['transcript_id'] + return x + - objectValidate: + title: exon + schema: "{{config.schema}}" + - emit: + name: exon + + genes: + - from: transform + - filter: + field: type + match: gene + - project: + mapping: + gene_id: "{{row.gene_id}}" + - objectValidate: + title: gene + schema: "{{config.schema}}" + - emit: + name: gene + + transcripts: + - from: transform + - filter: + field: type + match: mRNA + - regexReplace: + field: Parent + regex: "^gene:" + replace: "" + dst: gene_id + - project: + mapping: + transcript_id: "{{row.transcript_id}}" + - objectValidate: + title: transcript + schema: "{{config.schema}}" + - emit: + name: transcript diff --git a/examples/hugo-ensembl.yaml b/examples/hugo-ensembl.yaml index 8f75ae9..08783cd 100644 --- a/examples/hugo-ensembl.yaml +++ b/examples/hugo-ensembl.yaml @@ -1,29 +1,25 @@ -class: Playbook +class: sifter +name: hugo-ensembl - -desc: > - This takes a Gene TSV, filters rows, selects columns and outputs a 2 - column TSV into the working directory +config: + hugoJSON: ftp://ftp.ebi.ac.uk/pub/databases/genenames/hgnc/json/locus_types/gene_with_protein_product.json inputs: - hugoJSON: - type: File - default: ftp://ftp.ebi.ac.uk/pub/databases/genenames/hgnc/json/locus_types/gene_with_protein_product.json + hugoReader: + jsonLoad: + input: "{{config.hugoJSON}}" -steps: - - jsonLoad: - input: "{{inputs.hugoJSON}}" - transform: - - fieldProcess: - field: response.docs - steps: - - filter: - field: "ensembl_gene_id" - exists: True - steps: - - tableWrite: - output: "hugo-ensembl.table" - columns: - - symbol - - ensembl_gene_id +pipelines: + transform: + - from: hugoReader + - fieldProcess: + field: response.docs + - filter: + field: ensembl_gene_id + check: exists + - tableWrite: + output: hugo-ensembl.table + columns: + - symbol + - ensembl_gene_id diff --git a/examples/vcfload.yaml b/examples/vcfload.yaml deleted file mode 100644 index 3901cc2..0000000 --- a/examples/vcfload.yaml +++ /dev/null @@ -1,27 +0,0 @@ - -class: Playbook -name: VCFLoad - -inputs: - clinvar_vcf: - type: File - default: ftp://ftp.ncbi.nlm.nih.gov/pub/clinvar/vcf_GRCh37/clinvar_20190422.vcf.gz - dbsnp_vcf: - type: File - default: ftp://ftp.ncbi.nih.gov/snp/organisms/human_9606/VCF/ - -steps: - - desc: Loading VCF - vcfLoad: - # VCF from ftp://ftp.ncbi.nlm.nih.gov/pub/clinvar/vcf_GRCh37/clinvar_20190422.vcf.gz - input: "{{inputs.clinvar_vcf}}" - label: Annotation - edgeLabel: inAllele - idTemplate: "Clinvar:{{row.ID}}" - infoMap: - CLNDN: disease - - desc: DB SNP VCF - # VCF from ftp://ftp.ncbi.nih.gov/snp/organisms/human_9606/VCF/ - vcfLoad: - emitAllele: true - input: "{{inputs.dbsnp_vcf}}" diff --git a/website/archetypes/default.md b/website/archetypes/default.md deleted file mode 100644 index 00e77bd..0000000 --- a/website/archetypes/default.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: "{{ replace .Name "-" " " | title }}" -date: {{ .Date }} -draft: true ---- - diff --git a/website/config.yaml b/website/config.yaml deleted file mode 100644 index 98a395b..0000000 --- a/website/config.yaml +++ /dev/null @@ -1,4 +0,0 @@ -baseURL: https://bmeg.github.io/sifter -languageCode: en-us -title: Sifter -publishDir: ../docs/ \ No newline at end of file diff --git a/website/layouts/_default/single.html b/website/layouts/_default/single.html deleted file mode 100644 index ee52f69..0000000 --- a/website/layouts/_default/single.html +++ /dev/null @@ -1,40 +0,0 @@ -{{ partial "head.html" . }} - -
- - - -
- {{ .Content }} -
- -
- - diff --git a/website/layouts/index.html b/website/layouts/index.html deleted file mode 100644 index bc335ba..0000000 --- a/website/layouts/index.html +++ /dev/null @@ -1,18 +0,0 @@ - -{{ partial "head.html" . }} - - - -
-
-
- - -
-
- {{ .Content }} -
-
-
- -{{ partial "tail.html" . }} \ No newline at end of file diff --git a/website/layouts/partials/head.html b/website/layouts/partials/head.html deleted file mode 100644 index 5564aca..0000000 --- a/website/layouts/partials/head.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - {{ if .IsHome }} - {{ .Site.Title }} - {{ else }} - {{ .Title }} · {{ .Site.Title }} - {{ end }} - - - - - - - - - - - - - - - - - - - - diff --git a/website/layouts/partials/tail.html b/website/layouts/partials/tail.html deleted file mode 100644 index 11a09ca..0000000 --- a/website/layouts/partials/tail.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/website/public/categories/index.xml b/website/public/categories/index.xml deleted file mode 100644 index 1de8250..0000000 --- a/website/public/categories/index.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - Categories on Sifter - http://example.org/categories/ - Recent content in Categories on Sifter - Hugo -- gohugo.io - en-us - - diff --git a/website/public/css/darcula.css b/website/public/css/darcula.css deleted file mode 100644 index be182d0..0000000 --- a/website/public/css/darcula.css +++ /dev/null @@ -1,77 +0,0 @@ -/* - -Darcula color scheme from the JetBrains family of IDEs - -*/ - - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #2b2b2b; -} - -.hljs { - color: #bababa; -} - -.hljs-strong, -.hljs-emphasis { - color: #a8a8a2; -} - -.hljs-bullet, -.hljs-quote, -.hljs-link, -.hljs-number, -.hljs-regexp, -.hljs-literal { - color: #6896ba; -} - -.hljs-code, -.hljs-selector-class { - color: #a6e22e; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-section, -.hljs-attribute, -.hljs-name, -.hljs-variable { - color: #cb7832; -} - -.hljs-params { - color: #b9b9b9; -} - -.hljs-string { - color: #6a8759; -} - -.hljs-subst, -.hljs-type, -.hljs-built_in, -.hljs-builtin-name, -.hljs-symbol, -.hljs-selector-id, -.hljs-selector-attr, -.hljs-selector-pseudo, -.hljs-template-tag, -.hljs-template-variable, -.hljs-addition { - color: #e0c46c; -} - -.hljs-comment, -.hljs-deletion, -.hljs-meta { - color: #7f7f7f; -} diff --git a/website/public/css/dark.css b/website/public/css/dark.css deleted file mode 100644 index b4724f5..0000000 --- a/website/public/css/dark.css +++ /dev/null @@ -1,63 +0,0 @@ -/* - -Dark style from softwaremaniacs.org (c) Ivan Sagalaev - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #444; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal, -.hljs-section, -.hljs-link { - color: white; -} - -.hljs, -.hljs-subst { - color: #ddd; -} - -.hljs-string, -.hljs-title, -.hljs-name, -.hljs-type, -.hljs-attribute, -.hljs-symbol, -.hljs-bullet, -.hljs-built_in, -.hljs-addition, -.hljs-variable, -.hljs-template-tag, -.hljs-template-variable { - color: #d88; -} - -.hljs-comment, -.hljs-quote, -.hljs-deletion, -.hljs-meta { - color: #777; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal, -.hljs-title, -.hljs-section, -.hljs-doctag, -.hljs-type, -.hljs-name, -.hljs-strong { - font-weight: bold; -} - -.hljs-emphasis { - font-style: italic; -} diff --git a/website/public/css/flexboxgrid.css b/website/public/css/flexboxgrid.css deleted file mode 100644 index 603506f..0000000 --- a/website/public/css/flexboxgrid.css +++ /dev/null @@ -1,960 +0,0 @@ -.container-fluid, -.container { - margin-right: auto; - margin-left: auto; -} - -.container-fluid { - padding-right: 2rem; - padding-left: 2rem; -} - -.row { - box-sizing: border-box; - display: -webkit-box; - display: -ms-flexbox; - display: flex; - -webkit-box-flex: 0; - -ms-flex: 0 1 auto; - flex: 0 1 auto; - -webkit-box-orient: horizontal; - -webkit-box-direction: normal; - -ms-flex-direction: row; - flex-direction: row; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - margin-right: -0.5rem; - margin-left: -0.5rem; -} - -.row.reverse { - -webkit-box-orient: horizontal; - -webkit-box-direction: reverse; - -ms-flex-direction: row-reverse; - flex-direction: row-reverse; -} - -.col.reverse { - -webkit-box-orient: vertical; - -webkit-box-direction: reverse; - -ms-flex-direction: column-reverse; - flex-direction: column-reverse; -} - -.col-xs, -.col-xs-1, -.col-xs-2, -.col-xs-3, -.col-xs-4, -.col-xs-5, -.col-xs-6, -.col-xs-7, -.col-xs-8, -.col-xs-9, -.col-xs-10, -.col-xs-11, -.col-xs-12, -.col-xs-offset-0, -.col-xs-offset-1, -.col-xs-offset-2, -.col-xs-offset-3, -.col-xs-offset-4, -.col-xs-offset-5, -.col-xs-offset-6, -.col-xs-offset-7, -.col-xs-offset-8, -.col-xs-offset-9, -.col-xs-offset-10, -.col-xs-offset-11, -.col-xs-offset-12 { - box-sizing: border-box; - -webkit-box-flex: 0; - -ms-flex: 0 0 auto; - flex: 0 0 auto; - padding-right: 0.5rem; - padding-left: 0.5rem; -} - -.col-xs { - -webkit-box-flex: 1; - -ms-flex-positive: 1; - flex-grow: 1; - -ms-flex-preferred-size: 0; - flex-basis: 0; - max-width: 100%; -} - -.col-xs-1 { - -ms-flex-preferred-size: 8.33333333%; - flex-basis: 8.33333333%; - max-width: 8.33333333%; -} - -.col-xs-2 { - -ms-flex-preferred-size: 16.66666667%; - flex-basis: 16.66666667%; - max-width: 16.66666667%; -} - -.col-xs-3 { - -ms-flex-preferred-size: 25%; - flex-basis: 25%; - max-width: 25%; -} - -.col-xs-4 { - -ms-flex-preferred-size: 33.33333333%; - flex-basis: 33.33333333%; - max-width: 33.33333333%; -} - -.col-xs-5 { - -ms-flex-preferred-size: 41.66666667%; - flex-basis: 41.66666667%; - max-width: 41.66666667%; -} - -.col-xs-6 { - -ms-flex-preferred-size: 50%; - flex-basis: 50%; - max-width: 50%; -} - -.col-xs-7 { - -ms-flex-preferred-size: 58.33333333%; - flex-basis: 58.33333333%; - max-width: 58.33333333%; -} - -.col-xs-8 { - -ms-flex-preferred-size: 66.66666667%; - flex-basis: 66.66666667%; - max-width: 66.66666667%; -} - -.col-xs-9 { - -ms-flex-preferred-size: 75%; - flex-basis: 75%; - max-width: 75%; -} - -.col-xs-10 { - -ms-flex-preferred-size: 83.33333333%; - flex-basis: 83.33333333%; - max-width: 83.33333333%; -} - -.col-xs-11 { - -ms-flex-preferred-size: 91.66666667%; - flex-basis: 91.66666667%; - max-width: 91.66666667%; -} - -.col-xs-12 { - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - max-width: 100%; -} - -.col-xs-offset-0 { - margin-left: 0; -} - -.col-xs-offset-1 { - margin-left: 8.33333333%; -} - -.col-xs-offset-2 { - margin-left: 16.66666667%; -} - -.col-xs-offset-3 { - margin-left: 25%; -} - -.col-xs-offset-4 { - margin-left: 33.33333333%; -} - -.col-xs-offset-5 { - margin-left: 41.66666667%; -} - -.col-xs-offset-6 { - margin-left: 50%; -} - -.col-xs-offset-7 { - margin-left: 58.33333333%; -} - -.col-xs-offset-8 { - margin-left: 66.66666667%; -} - -.col-xs-offset-9 { - margin-left: 75%; -} - -.col-xs-offset-10 { - margin-left: 83.33333333%; -} - -.col-xs-offset-11 { - margin-left: 91.66666667%; -} - -.start-xs { - -webkit-box-pack: start; - -ms-flex-pack: start; - justify-content: flex-start; - text-align: start; -} - -.center-xs { - -webkit-box-pack: center; - -ms-flex-pack: center; - justify-content: center; - text-align: center; -} - -.end-xs { - -webkit-box-pack: end; - -ms-flex-pack: end; - justify-content: flex-end; - text-align: end; -} - -.top-xs { - -webkit-box-align: start; - -ms-flex-align: start; - align-items: flex-start; -} - -.middle-xs { - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; -} - -.bottom-xs { - -webkit-box-align: end; - -ms-flex-align: end; - align-items: flex-end; -} - -.around-xs { - -ms-flex-pack: distribute; - justify-content: space-around; -} - -.between-xs { - -webkit-box-pack: justify; - -ms-flex-pack: justify; - justify-content: space-between; -} - -.first-xs { - -webkit-box-ordinal-group: 0; - -ms-flex-order: -1; - order: -1; -} - -.last-xs { - -webkit-box-ordinal-group: 2; - -ms-flex-order: 1; - order: 1; -} - -@media only screen and (min-width: 48em) { - .container { - width: 49rem; - } - - .col-sm, - .col-sm-1, - .col-sm-2, - .col-sm-3, - .col-sm-4, - .col-sm-5, - .col-sm-6, - .col-sm-7, - .col-sm-8, - .col-sm-9, - .col-sm-10, - .col-sm-11, - .col-sm-12, - .col-sm-offset-0, - .col-sm-offset-1, - .col-sm-offset-2, - .col-sm-offset-3, - .col-sm-offset-4, - .col-sm-offset-5, - .col-sm-offset-6, - .col-sm-offset-7, - .col-sm-offset-8, - .col-sm-offset-9, - .col-sm-offset-10, - .col-sm-offset-11, - .col-sm-offset-12 { - box-sizing: border-box; - -webkit-box-flex: 0; - -ms-flex: 0 0 auto; - flex: 0 0 auto; - padding-right: 0.5rem; - padding-left: 0.5rem; - } - - .col-sm { - -webkit-box-flex: 1; - -ms-flex-positive: 1; - flex-grow: 1; - -ms-flex-preferred-size: 0; - flex-basis: 0; - max-width: 100%; - } - - .col-sm-1 { - -ms-flex-preferred-size: 8.33333333%; - flex-basis: 8.33333333%; - max-width: 8.33333333%; - } - - .col-sm-2 { - -ms-flex-preferred-size: 16.66666667%; - flex-basis: 16.66666667%; - max-width: 16.66666667%; - } - - .col-sm-3 { - -ms-flex-preferred-size: 25%; - flex-basis: 25%; - max-width: 25%; - } - - .col-sm-4 { - -ms-flex-preferred-size: 33.33333333%; - flex-basis: 33.33333333%; - max-width: 33.33333333%; - } - - .col-sm-5 { - -ms-flex-preferred-size: 41.66666667%; - flex-basis: 41.66666667%; - max-width: 41.66666667%; - } - - .col-sm-6 { - -ms-flex-preferred-size: 50%; - flex-basis: 50%; - max-width: 50%; - } - - .col-sm-7 { - -ms-flex-preferred-size: 58.33333333%; - flex-basis: 58.33333333%; - max-width: 58.33333333%; - } - - .col-sm-8 { - -ms-flex-preferred-size: 66.66666667%; - flex-basis: 66.66666667%; - max-width: 66.66666667%; - } - - .col-sm-9 { - -ms-flex-preferred-size: 75%; - flex-basis: 75%; - max-width: 75%; - } - - .col-sm-10 { - -ms-flex-preferred-size: 83.33333333%; - flex-basis: 83.33333333%; - max-width: 83.33333333%; - } - - .col-sm-11 { - -ms-flex-preferred-size: 91.66666667%; - flex-basis: 91.66666667%; - max-width: 91.66666667%; - } - - .col-sm-12 { - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - max-width: 100%; - } - - .col-sm-offset-0 { - margin-left: 0; - } - - .col-sm-offset-1 { - margin-left: 8.33333333%; - } - - .col-sm-offset-2 { - margin-left: 16.66666667%; - } - - .col-sm-offset-3 { - margin-left: 25%; - } - - .col-sm-offset-4 { - margin-left: 33.33333333%; - } - - .col-sm-offset-5 { - margin-left: 41.66666667%; - } - - .col-sm-offset-6 { - margin-left: 50%; - } - - .col-sm-offset-7 { - margin-left: 58.33333333%; - } - - .col-sm-offset-8 { - margin-left: 66.66666667%; - } - - .col-sm-offset-9 { - margin-left: 75%; - } - - .col-sm-offset-10 { - margin-left: 83.33333333%; - } - - .col-sm-offset-11 { - margin-left: 91.66666667%; - } - - .start-sm { - -webkit-box-pack: start; - -ms-flex-pack: start; - justify-content: flex-start; - text-align: start; - } - - .center-sm { - -webkit-box-pack: center; - -ms-flex-pack: center; - justify-content: center; - text-align: center; - } - - .end-sm { - -webkit-box-pack: end; - -ms-flex-pack: end; - justify-content: flex-end; - text-align: end; - } - - .top-sm { - -webkit-box-align: start; - -ms-flex-align: start; - align-items: flex-start; - } - - .middle-sm { - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - } - - .bottom-sm { - -webkit-box-align: end; - -ms-flex-align: end; - align-items: flex-end; - } - - .around-sm { - -ms-flex-pack: distribute; - justify-content: space-around; - } - - .between-sm { - -webkit-box-pack: justify; - -ms-flex-pack: justify; - justify-content: space-between; - } - - .first-sm { - -webkit-box-ordinal-group: 0; - -ms-flex-order: -1; - order: -1; - } - - .last-sm { - -webkit-box-ordinal-group: 2; - -ms-flex-order: 1; - order: 1; - } -} - -@media only screen and (min-width: 64em) { - .container { - width: 65rem; - } - - .col-md, - .col-md-1, - .col-md-2, - .col-md-3, - .col-md-4, - .col-md-5, - .col-md-6, - .col-md-7, - .col-md-8, - .col-md-9, - .col-md-10, - .col-md-11, - .col-md-12, - .col-md-offset-0, - .col-md-offset-1, - .col-md-offset-2, - .col-md-offset-3, - .col-md-offset-4, - .col-md-offset-5, - .col-md-offset-6, - .col-md-offset-7, - .col-md-offset-8, - .col-md-offset-9, - .col-md-offset-10, - .col-md-offset-11, - .col-md-offset-12 { - box-sizing: border-box; - -webkit-box-flex: 0; - -ms-flex: 0 0 auto; - flex: 0 0 auto; - padding-right: 0.5rem; - padding-left: 0.5rem; - } - - .col-md { - -webkit-box-flex: 1; - -ms-flex-positive: 1; - flex-grow: 1; - -ms-flex-preferred-size: 0; - flex-basis: 0; - max-width: 100%; - } - - .col-md-1 { - -ms-flex-preferred-size: 8.33333333%; - flex-basis: 8.33333333%; - max-width: 8.33333333%; - } - - .col-md-2 { - -ms-flex-preferred-size: 16.66666667%; - flex-basis: 16.66666667%; - max-width: 16.66666667%; - } - - .col-md-3 { - -ms-flex-preferred-size: 25%; - flex-basis: 25%; - max-width: 25%; - } - - .col-md-4 { - -ms-flex-preferred-size: 33.33333333%; - flex-basis: 33.33333333%; - max-width: 33.33333333%; - } - - .col-md-5 { - -ms-flex-preferred-size: 41.66666667%; - flex-basis: 41.66666667%; - max-width: 41.66666667%; - } - - .col-md-6 { - -ms-flex-preferred-size: 50%; - flex-basis: 50%; - max-width: 50%; - } - - .col-md-7 { - -ms-flex-preferred-size: 58.33333333%; - flex-basis: 58.33333333%; - max-width: 58.33333333%; - } - - .col-md-8 { - -ms-flex-preferred-size: 66.66666667%; - flex-basis: 66.66666667%; - max-width: 66.66666667%; - } - - .col-md-9 { - -ms-flex-preferred-size: 75%; - flex-basis: 75%; - max-width: 75%; - } - - .col-md-10 { - -ms-flex-preferred-size: 83.33333333%; - flex-basis: 83.33333333%; - max-width: 83.33333333%; - } - - .col-md-11 { - -ms-flex-preferred-size: 91.66666667%; - flex-basis: 91.66666667%; - max-width: 91.66666667%; - } - - .col-md-12 { - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - max-width: 100%; - } - - .col-md-offset-0 { - margin-left: 0; - } - - .col-md-offset-1 { - margin-left: 8.33333333%; - } - - .col-md-offset-2 { - margin-left: 16.66666667%; - } - - .col-md-offset-3 { - margin-left: 25%; - } - - .col-md-offset-4 { - margin-left: 33.33333333%; - } - - .col-md-offset-5 { - margin-left: 41.66666667%; - } - - .col-md-offset-6 { - margin-left: 50%; - } - - .col-md-offset-7 { - margin-left: 58.33333333%; - } - - .col-md-offset-8 { - margin-left: 66.66666667%; - } - - .col-md-offset-9 { - margin-left: 75%; - } - - .col-md-offset-10 { - margin-left: 83.33333333%; - } - - .col-md-offset-11 { - margin-left: 91.66666667%; - } - - .start-md { - -webkit-box-pack: start; - -ms-flex-pack: start; - justify-content: flex-start; - text-align: start; - } - - .center-md { - -webkit-box-pack: center; - -ms-flex-pack: center; - justify-content: center; - text-align: center; - } - - .end-md { - -webkit-box-pack: end; - -ms-flex-pack: end; - justify-content: flex-end; - text-align: end; - } - - .top-md { - -webkit-box-align: start; - -ms-flex-align: start; - align-items: flex-start; - } - - .middle-md { - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - } - - .bottom-md { - -webkit-box-align: end; - -ms-flex-align: end; - align-items: flex-end; - } - - .around-md { - -ms-flex-pack: distribute; - justify-content: space-around; - } - - .between-md { - -webkit-box-pack: justify; - -ms-flex-pack: justify; - justify-content: space-between; - } - - .first-md { - -webkit-box-ordinal-group: 0; - -ms-flex-order: -1; - order: -1; - } - - .last-md { - -webkit-box-ordinal-group: 2; - -ms-flex-order: 1; - order: 1; - } -} - -@media only screen and (min-width: 75em) { - .container { - width: 76rem; - } - - .col-lg, - .col-lg-1, - .col-lg-2, - .col-lg-3, - .col-lg-4, - .col-lg-5, - .col-lg-6, - .col-lg-7, - .col-lg-8, - .col-lg-9, - .col-lg-10, - .col-lg-11, - .col-lg-12, - .col-lg-offset-0, - .col-lg-offset-1, - .col-lg-offset-2, - .col-lg-offset-3, - .col-lg-offset-4, - .col-lg-offset-5, - .col-lg-offset-6, - .col-lg-offset-7, - .col-lg-offset-8, - .col-lg-offset-9, - .col-lg-offset-10, - .col-lg-offset-11, - .col-lg-offset-12 { - box-sizing: border-box; - -webkit-box-flex: 0; - -ms-flex: 0 0 auto; - flex: 0 0 auto; - padding-right: 0.5rem; - padding-left: 0.5rem; - } - - .col-lg { - -webkit-box-flex: 1; - -ms-flex-positive: 1; - flex-grow: 1; - -ms-flex-preferred-size: 0; - flex-basis: 0; - max-width: 100%; - } - - .col-lg-1 { - -ms-flex-preferred-size: 8.33333333%; - flex-basis: 8.33333333%; - max-width: 8.33333333%; - } - - .col-lg-2 { - -ms-flex-preferred-size: 16.66666667%; - flex-basis: 16.66666667%; - max-width: 16.66666667%; - } - - .col-lg-3 { - -ms-flex-preferred-size: 25%; - flex-basis: 25%; - max-width: 25%; - } - - .col-lg-4 { - -ms-flex-preferred-size: 33.33333333%; - flex-basis: 33.33333333%; - max-width: 33.33333333%; - } - - .col-lg-5 { - -ms-flex-preferred-size: 41.66666667%; - flex-basis: 41.66666667%; - max-width: 41.66666667%; - } - - .col-lg-6 { - -ms-flex-preferred-size: 50%; - flex-basis: 50%; - max-width: 50%; - } - - .col-lg-7 { - -ms-flex-preferred-size: 58.33333333%; - flex-basis: 58.33333333%; - max-width: 58.33333333%; - } - - .col-lg-8 { - -ms-flex-preferred-size: 66.66666667%; - flex-basis: 66.66666667%; - max-width: 66.66666667%; - } - - .col-lg-9 { - -ms-flex-preferred-size: 75%; - flex-basis: 75%; - max-width: 75%; - } - - .col-lg-10 { - -ms-flex-preferred-size: 83.33333333%; - flex-basis: 83.33333333%; - max-width: 83.33333333%; - } - - .col-lg-11 { - -ms-flex-preferred-size: 91.66666667%; - flex-basis: 91.66666667%; - max-width: 91.66666667%; - } - - .col-lg-12 { - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - max-width: 100%; - } - - .col-lg-offset-0 { - margin-left: 0; - } - - .col-lg-offset-1 { - margin-left: 8.33333333%; - } - - .col-lg-offset-2 { - margin-left: 16.66666667%; - } - - .col-lg-offset-3 { - margin-left: 25%; - } - - .col-lg-offset-4 { - margin-left: 33.33333333%; - } - - .col-lg-offset-5 { - margin-left: 41.66666667%; - } - - .col-lg-offset-6 { - margin-left: 50%; - } - - .col-lg-offset-7 { - margin-left: 58.33333333%; - } - - .col-lg-offset-8 { - margin-left: 66.66666667%; - } - - .col-lg-offset-9 { - margin-left: 75%; - } - - .col-lg-offset-10 { - margin-left: 83.33333333%; - } - - .col-lg-offset-11 { - margin-left: 91.66666667%; - } - - .start-lg { - -webkit-box-pack: start; - -ms-flex-pack: start; - justify-content: flex-start; - text-align: start; - } - - .center-lg { - -webkit-box-pack: center; - -ms-flex-pack: center; - justify-content: center; - text-align: center; - } - - .end-lg { - -webkit-box-pack: end; - -ms-flex-pack: end; - justify-content: flex-end; - text-align: end; - } - - .top-lg { - -webkit-box-align: start; - -ms-flex-align: start; - align-items: flex-start; - } - - .middle-lg { - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - } - - .bottom-lg { - -webkit-box-align: end; - -ms-flex-align: end; - align-items: flex-end; - } - - .around-lg { - -ms-flex-pack: distribute; - justify-content: space-around; - } - - .between-lg { - -webkit-box-pack: justify; - -ms-flex-pack: justify; - justify-content: space-between; - } - - .first-lg { - -webkit-box-ordinal-group: 0; - -ms-flex-order: -1; - order: -1; - } - - .last-lg { - -webkit-box-ordinal-group: 2; - -ms-flex-order: 1; - order: 1; - } -} diff --git a/website/public/css/funnel.css b/website/public/css/funnel.css deleted file mode 100644 index 825d6af..0000000 --- a/website/public/css/funnel.css +++ /dev/null @@ -1,245 +0,0 @@ -.global-header { - background-color: #23241f; - padding: .3rem .5rem; -} - -.global-header-container { - display: flex; - align-items: center; -} - -.global-header-container h1, -.global-header-container h2 { - margin: 0; - padding: 0; - color: white; -} -.global-header-container h1 { - font-size: 1.2rem; -} -.global-header-container h2 { - font-size: .9rem; -} - -.global-header-container, -.homepage { - max-width: 50rem; - margin: 0 auto; -} - -.homepage-intro .col { - padding: 0 1rem; -} -.homepage-intro li { - font-size: 0.8rem; -} - -.global-header-nav { - list-style-type: none; - padding: 0; - margin: 0; - margin-left: 3rem; - flex-grow: 1; - display: flex; - align-items: center; - justify-content: center; -} - -@media only screen and (max-width: 600px) { - .global-header-container, - .global-header-nav { - flex-direction: column; - } - .global-header-home, - .global-header-ohsucb { - margin: .5rem 0; - } - .global-header-nav { - margin: 0; - } - .global-header-nav li { - margin: .5rem 0; - } - - .homepage-demo .col h1, - .homepage-demo .col p { - margin-left: .3rem; - } - - .content { - padding: 0 .7rem; - } - - .sidebar { - padding: 1rem; - padding-bottom: 0; - } - - .sidebar-nav li { - margin: .3rem 0; - } -} - -.sidebar-nav { - font-size: .9rem; -} -.sidebar-nav span.intermediate { - color: #23241f; -} - -.global-header-nav li { - display: inline-block; - padding: 0 0.5rem; - font-size: .9rem; -} - -.global-header-nav li a { - color: white; -} - -.global-header a:hover, -.global-header a:hover h1, -.global-header a:hover h2, -.global-header-nav li a:hover { - color: #9ed9ff; - text-decoration: none; -} - -.lead { - font-size: .8rem; -} - -.lead a { - color: #b8d4e0; -} - -.homepage h2 { - text-align: center; - font-size: 1.5rem; - margin-bottom: 1rem; -} - -.homepage-lead { - background-color: #f1f1f1; - padding: 2rem 2rem 1rem 2rem; - border-radius: 10px; - margin-bottom: 1rem; - text-align: center; -} - -.homepage-lead-container { - max-width: 42rem; - margin: 0 auto; -} - -.homepage-lead h1 { - margin: 0; -} - -.homepage-footer { - height: 100px; -} - -.homepage-notice { - background-color: #fffcbf; - padding: 1rem 3rem; - border-radius: 10px; - margin-top: 0; - margin-bottom: 1rem; - text-align: center; -} - -.homepage-notice h4 { - font-size: 1rem; -} - -.homepage-notice h3, -.homepage-notice p { - margin: 0; -} - -.homepage-lead .download-button, -.homepage-lead .docs-button { - padding: 10px 30px; - border-radius: 5px; - border: 0; - color: white; - font-size: .7rem; - display: inline-block; - margin: 0.1rem 0.2rem; -} -.docs-button { - border-radius: 5px; - border: 0; - color: white; - font-size: .7rem; - background-color: #4ca0ea; - padding: 10px 30px; -} - -.homepage-lead .download-button { - background-color: #29b429; -} - -.homepage .row { - width: 100%; - margin-bottom: 20px; -} - -.homepage-demo { - margin-top: 3rem; - margin-bottom: 3rem; -} - -.homepage-demo h1.demo-header { - font-size: 1.5rem; - text-align: center; - margin-bottom: 2rem; -} - -.homepage-demo h1 { - font-size: 1rem; - margin: 0; -} - -.homepage-demo p { - margin: .7rem 0; - padding-right: .7rem; - font-size: .8rem; -} - -.homepage h3 { - font-size: 1rem; -} - -.homepage-more { - text-align: center; -} - -.homepage p { - font-size: .8rem; -} - -pre { - padding: 0; -} - -.homepage-demo .section { - margin-bottom: 2rem; -} - -.homepage-demo pre { - margin: 0; -} -.homepage-demo code { - width: 100%; - display: block; - font-size: .8rem; - border-radius: 0; -} - -.optional { - font-size: 1rem; - color: #aaa; - font-style: normal; -} diff --git a/website/public/css/highlight.min.css b/website/public/css/highlight.min.css deleted file mode 100644 index 7d8be18..0000000 --- a/website/public/css/highlight.min.css +++ /dev/null @@ -1 +0,0 @@ -.hljs{display:block;overflow-x:auto;padding:0.5em;background:#F0F0F0}.hljs,.hljs-subst{color:#444}.hljs-comment{color:#888888}.hljs-keyword,.hljs-attribute,.hljs-selector-tag,.hljs-meta-keyword,.hljs-doctag,.hljs-name{font-weight:bold}.hljs-type,.hljs-string,.hljs-number,.hljs-selector-id,.hljs-selector-class,.hljs-quote,.hljs-template-tag,.hljs-deletion{color:#880000}.hljs-title,.hljs-section{color:#880000;font-weight:bold}.hljs-regexp,.hljs-symbol,.hljs-variable,.hljs-template-variable,.hljs-link,.hljs-selector-attr,.hljs-selector-pseudo{color:#BC6060}.hljs-literal{color:#78A960}.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-addition{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold} \ No newline at end of file diff --git a/website/public/css/html5reset.css b/website/public/css/html5reset.css deleted file mode 100755 index 3bfbb3d..0000000 --- a/website/public/css/html5reset.css +++ /dev/null @@ -1,96 +0,0 @@ -/* html5reset.css - 01/11/2011 */ - -html, body, div, span, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -abbr, address, cite, code, -del, dfn, em, img, ins, kbd, q, samp, -small, strong, sub, sup, var, -b, i, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, figcaption, figure, -footer, header, hgroup, menu, nav, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - outline: 0; - font-size: 100%; - vertical-align: baseline; - background: transparent; -} - -body { - line-height: 1; -} - -article,aside,details,figcaption,figure, -footer,header,hgroup,menu,nav,section { - display: block; -} - -nav ul { - list-style: none; -} - -blockquote, q { - quotes: none; -} - -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; -} - -a { - margin: 0; - padding: 0; - font-size: 100%; - vertical-align: baseline; - background: transparent; -} - -/* change colours to suit your needs */ -ins { - background-color: #ff9; - color: #000; - text-decoration: none; -} - -/* change colours to suit your needs */ -mark { - background-color: #ff9; - color: #000; - font-style: italic; - font-weight: bold; -} - -del { - text-decoration: line-through; -} - -abbr[title], dfn[title] { - border-bottom: 1px dotted; - cursor: help; -} - -table { - border-collapse: collapse; - border-spacing: 0; -} - -/* change border colour to suit your needs */ -hr { - display: block; - height: 1px; - border: 0; - border-top: 1px solid #cccccc; - margin: 1em 0; - padding: 0; -} - -input, select { - vertical-align: middle; -} \ No newline at end of file diff --git a/website/public/css/hybrid.css b/website/public/css/hybrid.css deleted file mode 100644 index 29735a1..0000000 --- a/website/public/css/hybrid.css +++ /dev/null @@ -1,102 +0,0 @@ -/* - -vim-hybrid theme by w0ng (https://github.com/w0ng/vim-hybrid) - -*/ - -/*background color*/ -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #1d1f21; -} - -/*selection color*/ -.hljs::selection, -.hljs span::selection { - background: #373b41; -} - -.hljs::-moz-selection, -.hljs span::-moz-selection { - background: #373b41; -} - -/*foreground color*/ -.hljs { - color: #c5c8c6; -} - -/*color: fg_yellow*/ -.hljs-title, -.hljs-name { - color: #f0c674; -} - -/*color: fg_comment*/ -.hljs-comment, -.hljs-meta, -.hljs-meta .hljs-keyword { - color: #707880; -} - -/*color: fg_red*/ -.hljs-number, -.hljs-symbol, -.hljs-literal, -.hljs-deletion, -.hljs-link { - color: #cc6666 -} - -/*color: fg_green*/ -.hljs-string, -.hljs-doctag, -.hljs-addition, -.hljs-regexp, -.hljs-selector-attr, -.hljs-selector-pseudo { - color: #b5bd68; -} - -/*color: fg_purple*/ -.hljs-attribute, -.hljs-code, -.hljs-selector-id { - color: #b294bb; -} - -/*color: fg_blue*/ -.hljs-keyword, -.hljs-selector-tag, -.hljs-bullet, -.hljs-tag { - color: #81a2be; -} - -/*color: fg_aqua*/ -.hljs-subst, -.hljs-variable, -.hljs-template-tag, -.hljs-template-variable { - color: #8abeb7; -} - -/*color: fg_orange*/ -.hljs-type, -.hljs-built_in, -.hljs-builtin-name, -.hljs-quote, -.hljs-section, -.hljs-selector-class { - color: #de935f; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/website/public/css/monokai-sublime.css b/website/public/css/monokai-sublime.css deleted file mode 100644 index 2864170..0000000 --- a/website/public/css/monokai-sublime.css +++ /dev/null @@ -1,83 +0,0 @@ -/* - -Monokai Sublime style. Derived from Monokai by noformnocontent http://nn.mit-license.org/ - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #23241f; -} - -.hljs, -.hljs-tag, -.hljs-subst { - color: #f8f8f2; -} - -.hljs-strong, -.hljs-emphasis { - color: #a8a8a2; -} - -.hljs-bullet, -.hljs-quote, -.hljs-number, -.hljs-regexp, -.hljs-literal, -.hljs-link { - color: #ae81ff; -} - -.hljs-code, -.hljs-title, -.hljs-section, -.hljs-selector-class { - color: #a6e22e; -} - -.hljs-strong { - font-weight: bold; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-name, -.hljs-attr { - color: #f92672; -} - -.hljs-symbol, -.hljs-attribute { - color: #66d9ef; -} - -.hljs-params, -.hljs-class .hljs-title { - color: #f8f8f2; -} - -.hljs-string, -.hljs-type, -.hljs-built_in, -.hljs-builtin-name, -.hljs-selector-id, -.hljs-selector-attr, -.hljs-selector-pseudo, -.hljs-addition, -.hljs-variable, -.hljs-template-variable { - color: #e6db74; -} - -.hljs-comment, -.hljs-deletion, -.hljs-meta { - color: #75715e; -} diff --git a/website/public/css/poole.css b/website/public/css/poole.css deleted file mode 100644 index 03f9338..0000000 --- a/website/public/css/poole.css +++ /dev/null @@ -1,283 +0,0 @@ -/* - * ___ - * /\_ \ - * _____ ___ ___\//\ \ __ - * /\ '__`\ / __`\ / __`\\ \ \ /'__`\ - * \ \ \_\ \/\ \_\ \/\ \_\ \\_\ \_/\ __/ - * \ \ ,__/\ \____/\ \____//\____\ \____\ - * \ \ \/ \/___/ \/___/ \/____/\/____/ - * \ \_\ - * \/_/ - * - * Designed, built, and released under MIT license by @mdo. Learn more at - * https://github.com/poole/poole. - */ - - -/* - * Contents - * - * Body resets - * Custom type - * Messages - * Container - * Masthead - * Posts and pages - * Pagination - * Reverse layout - * Themes - */ - - -/* - * Body resets - * - * Update the foundational and global aspects of the page. - */ - -* { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -html, -body { - margin: 0; - padding: 0; -} - -html { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 16px; - line-height: 1.5; -} - -body { - color: #515151; - background-color: #fff; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; -} - -/* No `:visited` state is required by default (browsers will use `a`) */ -a { - color: #268bd2; - text-decoration: none; -} -/* `:focus` is linked to `:hover` for basic accessibility */ -a:hover, -a:focus { - text-decoration: underline; -} - -/* Headings */ -h1, h2, h3, h4, h5, h6 { - margin-bottom: .5rem; - font-weight: bold; - line-height: 1.25; - color: #313131; - text-rendering: optimizeLegibility; -} -h1 { - font-size: 2rem; -} -h2 { - margin-top: 1rem; - font-size: 1.5rem; -} -h3 { - margin-top: 1.5rem; - font-size: 1.25rem; -} -h4, h5, h6 { - margin-top: 1rem; - font-size: 1rem; -} - -/* Body text */ -p { - margin-top: 0; - margin-bottom: 1rem; -} - -strong { - color: #303030; -} - - -/* Lists */ -ul, ol, dl { - margin-top: 0; - margin-bottom: 1rem; -} - -dt { - font-weight: bold; -} -dd { - margin-bottom: .5rem; -} - -/* Misc */ -hr { - position: relative; - margin: 1.5rem 0; - border: 0; - border-top: 1px solid #eee; - border-bottom: 1px solid #fff; -} - -abbr { - font-size: 85%; - font-weight: bold; - color: #555; - text-transform: uppercase; -} -abbr[title] { - cursor: help; - border-bottom: 1px dotted #e5e5e5; -} - -/* Code */ -code, -pre { - font-family: Menlo, Monaco, "Courier New", monospace; -} -code { - padding: .25em .5em; - font-size: 85%; - color: #bf616a; - background-color: #f9f9f9; - border-radius: 3px; -} -pre { - display: block; - margin-top: 0; - margin-bottom: 1rem; - padding: 1rem; - font-size: .8rem; - line-height: 1.4; - white-space: pre; - white-space: pre-wrap; - word-break: break-all; - word-wrap: break-word; - background-color: #f9f9f9; -} -pre code { - padding: 0; - font-size: 100%; - color: inherit; - background-color: transparent; -} -.highlight { - margin-bottom: 1rem; - border-radius: 4px; -} -.highlight pre { - margin-bottom: 0; -} - -/* Quotes */ -blockquote { - padding: .5rem 1rem; - margin: .8rem 0; - color: #7a7a7a; - border-left: .25rem solid #e5e5e5; -} -blockquote p:last-child { - margin-bottom: 0; -} -@media (min-width: 30em) { - blockquote { - padding-right: 5rem; - padding-left: 1.25rem; - } -} - -img { - display: block; - margin: 0 0 1rem; - border-radius: 5px; - max-width: 100%; -} - -/* Tables */ -table { - margin-bottom: 1rem; - width: 100%; - border: 1px solid #e5e5e5; - border-collapse: collapse; -} -td, -th { - padding: .25rem .5rem; - border: 1px solid #e5e5e5; -} -tbody tr:nth-child(odd) td, -tbody tr:nth-child(odd) th { - background-color: #f9f9f9; -} - - -/* - * Custom type - * - * Extend paragraphs with `.lead` for larger introductory text. - */ - -.lead { - font-size: 1.25rem; - font-weight: 300; -} - - -/* - * Messages - * - * Show alert messages to users. You may add it to single elements like a `

`, - * or to a parent if there are multiple elements to show. - */ - -.message { - margin-bottom: 1rem; - padding: 1rem; - color: #717171; - background-color: #f9f9f9; -} - - -/* - * Masthead - * - * Super small header above the content for site name and short description. - */ - -.masthead { - padding-top: 1rem; - padding-bottom: 1rem; - margin-bottom: 3rem; -} -.masthead-title { - margin-top: 0; - margin-bottom: 0; - color: #505050; -} -.masthead-title a { - color: #505050; -} -.masthead-title small { - font-size: 75%; - font-weight: 400; - color: #c0c0c0; - letter-spacing: 0; -} - - -/* Meta data line below post title */ -.post-date { - display: block; - margin-top: -.5rem; - margin-bottom: 1rem; - color: #9a9a9a; -} diff --git a/website/public/css/syntax.css b/website/public/css/syntax.css deleted file mode 100644 index 1264b87..0000000 --- a/website/public/css/syntax.css +++ /dev/null @@ -1,66 +0,0 @@ -.hll { background-color: #ffffcc } - /*{ background: #f0f3f3; }*/ -.c { color: #999; } /* Comment */ -.err { color: #AA0000; background-color: #FFAAAA } /* Error */ -.k { color: #006699; } /* Keyword */ -.o { color: #555555 } /* Operator */ -.cm { color: #0099FF; font-style: italic } /* Comment.Multiline */ -.cp { color: #009999 } /* Comment.Preproc */ -.c1 { color: #999; } /* Comment.Single */ -.cs { color: #999; } /* Comment.Special */ -.gd { background-color: #FFCCCC; border: 1px solid #CC0000 } /* Generic.Deleted */ -.ge { font-style: italic } /* Generic.Emph */ -.gr { color: #FF0000 } /* Generic.Error */ -.gh { color: #003300; } /* Generic.Heading */ -.gi { background-color: #CCFFCC; border: 1px solid #00CC00 } /* Generic.Inserted */ -.go { color: #AAAAAA } /* Generic.Output */ -.gp { color: #000099; } /* Generic.Prompt */ -.gs { } /* Generic.Strong */ -.gu { color: #003300; } /* Generic.Subheading */ -.gt { color: #99CC66 } /* Generic.Traceback */ -.kc { color: #006699; } /* Keyword.Constant */ -.kd { color: #006699; } /* Keyword.Declaration */ -.kn { color: #006699; } /* Keyword.Namespace */ -.kp { color: #006699 } /* Keyword.Pseudo */ -.kr { color: #006699; } /* Keyword.Reserved */ -.kt { color: #007788; } /* Keyword.Type */ -.m { color: #FF6600 } /* Literal.Number */ -.s { color: #d44950 } /* Literal.String */ -.na { color: #4f9fcf } /* Name.Attribute */ -.nb { color: #336666 } /* Name.Builtin */ -.nc { color: #00AA88; } /* Name.Class */ -.no { color: #336600 } /* Name.Constant */ -.nd { color: #9999FF } /* Name.Decorator */ -.ni { color: #999999; } /* Name.Entity */ -.ne { color: #CC0000; } /* Name.Exception */ -.nf { color: #CC00FF } /* Name.Function */ -.nl { color: #9999FF } /* Name.Label */ -.nn { color: #00CCFF; } /* Name.Namespace */ -.nt { color: #2f6f9f; } /* Name.Tag */ -.nv { color: #003333 } /* Name.Variable */ -.ow { color: #000000; } /* Operator.Word */ -.w { color: #bbbbbb } /* Text.Whitespace */ -.mf { color: #FF6600 } /* Literal.Number.Float */ -.mh { color: #FF6600 } /* Literal.Number.Hex */ -.mi { color: #FF6600 } /* Literal.Number.Integer */ -.mo { color: #FF6600 } /* Literal.Number.Oct */ -.sb { color: #CC3300 } /* Literal.String.Backtick */ -.sc { color: #CC3300 } /* Literal.String.Char */ -.sd { color: #CC3300; font-style: italic } /* Literal.String.Doc */ -.s2 { color: #CC3300 } /* Literal.String.Double */ -.se { color: #CC3300; } /* Literal.String.Escape */ -.sh { color: #CC3300 } /* Literal.String.Heredoc */ -.si { color: #AA0000 } /* Literal.String.Interpol */ -.sx { color: #CC3300 } /* Literal.String.Other */ -.sr { color: #33AAAA } /* Literal.String.Regex */ -.s1 { color: #CC3300 } /* Literal.String.Single */ -.ss { color: #FFCC33 } /* Literal.String.Symbol */ -.bp { color: #336666 } /* Name.Builtin.Pseudo */ -.vc { color: #003333 } /* Name.Variable.Class */ -.vg { color: #003333 } /* Name.Variable.Global */ -.vi { color: #003333 } /* Name.Variable.Instance */ -.il { color: #FF6600 } /* Literal.Number.Integer.Long */ - -.css .o, -.css .o + .nt, -.css .nt + .nt { color: #999; } diff --git a/website/public/css/theme.css b/website/public/css/theme.css deleted file mode 100644 index af44672..0000000 --- a/website/public/css/theme.css +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Originally based on the Hyde theme, but heavily modified - # and who knows what original code remains. - * - * Designed, built, and released under MIT license by @mdo. Learn more at - * https://github.com/poole/hyde. - */ - - -/* - * Global resets - * - * Update the foundational and global aspects of the page. - */ - -html { - font-family: "PT Sans", Helvetica, Arial, sans-serif; - font-size: 20px; -} - -@media (max-width: 48em) { - .main { - font-size: 16px; - } -} - -/* SECTIONS ============================================================================= */ - -.section { - clear: both; - padding: 0px; - margin: 0px; -} - -/* GROUPING ============================================================================= */ - - -.group:before, -.group:after { - content:""; - display:table; -} -.group:after { - clear:both; -} -.group { - zoom:1; /* For IE 6/7 (trigger hasLayout) */ -} - -/* GRID COLUMN SETUP ==================================================================== */ - -.col { - display: block; - float:left; - margin: 1% 0 1% 1.6%; -} - -.col:first-child { margin-left: 0; } /* all browsers except IE6 and lower */ - - -/* REMOVE MARGINS AS ALL GO FULL WIDTH AT 600 PIXELS */ - -@media only screen and (max-width: 600px) { - .col { - margin: 1% 0 1% 0%; - } -} - -/* GRID OF THREE ============================================================================= */ - - -.span_3_of_3 { - width: 100%; -} - -.span_2_of_3 { - width: 66.13%; -} - -.span_1_of_3 { - width: 32.26%; -} - - -/* GO FULL WIDTH AT LESS THAN 600 PIXELS */ - -@media only screen and (max-width: 600px) { - .span_3_of_3 { - width: 100%; - } - .span_2_of_3 { - width: 100%; - } - .span_1_of_3 { - width: 100%; - } -} - -/* GRID OF TWELVE ============================================================================= */ - -.span_12_of_12 { - width: 100%; -} - -.span_11_of_12 { - width: 91.53%; -} - -.span_10_of_12 { - width: 83.06%; -} - -.span_9_of_12 { - width: 74.6%; -} - -.span_8_of_12 { - width: 66.13%; -} - -.span_7_of_12 { - width: 57.66%; -} - -.span_6_of_12 { - width: 49.2%; -} - -.span_5_of_12 { - width: 40.73%; -} - -.span_4_of_12 { - width: 32.26%; -} - -.span_3_of_12 { - width: 23.8%; -} - -.span_2_of_12 { - width: 15.33%; -} - -.span_1_of_12 { - width: 6.86%; -} - - -/* GO FULL WIDTH AT LESS THAN 600 PIXELS */ - -@media only screen and (max-width: 600px) { - .span_12_of_12 { - width: 100%; - } - .span_11_of_12 { - width: 100%; - } - .span_10_of_12 { - width: 100%; - } - .span_9_of_12 { - width: 100%; - } - .span_8_of_12 { - width: 100%; - } - .span_7_of_12 { - width: 100%; - } - .span_6_of_12 { - width: 100%; - } - .span_5_of_12 { - width: 100%; - } - .span_4_of_12 { - width: 100%; - } - .span_3_of_12 { - width: 100%; - } - .span_2_of_12 { - width: 100%; - } - .span_1_of_12 { - width: 100%; - } -} - - -/* - * Sidebar - * - * Flexible banner for housing site name, intro, and "footer" content. Starts - * out above content in mobile and later moves to the side with wider viewports. - */ - -.sidebar { - padding: 2rem; - padding-right: 0; - color: rgba(255,255,255,.5); - font-size: 1rem; -} - -.sidebar-nav { - padding-left: 0; - list-style: none; -} -.sidebar-nav-item { - display: block; -} -a.sidebar-nav-item:hover, -a.sidebar-nav-item:focus { - text-decoration: underline; -} -.sidebar-nav-item.active { - font-weight: bold; -} -.sidebar-nav-nested { - padding-left: 1rem; - margin-bottom: 0; -} diff --git a/website/public/docs/example/index.html b/website/public/docs/example/index.html deleted file mode 100644 index f05c281..0000000 --- a/website/public/docs/example/index.html +++ /dev/null @@ -1,469 +0,0 @@ - - - - - - - - - - - Example · Sifter - - - - - - - - - - - - - - - - - - - - -

- - -
- - - -
-

Example Pipeline

-

Our first task will be to convert a ZIP code TSV into a set of county level -entries.

-

The input file looks like:

-
ZIP,COUNTYNAME,STATE,STCOUNTYFP,CLASSFP
-36003,Autauga County,AL,01001,H1
-36006,Autauga County,AL,01001,H1
-36067,Autauga County,AL,01001,H1
-36066,Autauga County,AL,01001,H1
-36703,Autauga County,AL,01001,H1
-36701,Autauga County,AL,01001,H1
-36091,Autauga County,AL,01001,H1
-

First is the header of the pipeline. This declares the -unique name of the pipeline and it’s output directory.

-
name: zipcode_map
-outdir: ./
-docs: Converts zipcode TSV into graph elements
-

Next the configuration is declared. In this case the only input is the zipcode TSV. -There is a default value, so the pipeline can be invoked without passing in -any parameters. However, to apply this pipeline to a new input file, the -input parameter zipcode could be used to define the source file.

-
config:
-  schema: ../covid19_datadictionary/gdcdictionary/schemas/
-  zipcode: ../data/ZIP-COUNTY-FIPS_2017-06.csv
-

The inputs section declares data input sources. In this pipeline, there is -only one input, which is to run the table loader.

-
inputs:
-  tableLoad:
-    input: "{{config.zipcode}}"
-    sep: ","
-

Tableload operaters of the input file that was originally passed in using the -inputs stanza. SIFTER string parsing is based on mustache template system. -To access the string passed in the template is {{config.zipcode}}. -The seperator in the file input file is a , so that is also passed in as a -parameter to the extractor.

-

The tableLoad extractor opens up the TSV and generates a one message for -every row in the file. It uses the header of the file to map the column values -into a dictionary. The first row would produce the message:

-
{
-    "ZIP" : "36003",
-    "COUNTYNAME" : "Autauga County",
-    "STATE" : "AL",
-    "STCOUNTYFP" : "01001",
-    "CLASSFP" : "H1"
-}
-

The stream of messages are then passed into the steps listed in the transform -section of the tableLoad extractor.

-

For the current tranform, we want to produce a single entry per STCOUNTYFP, -however, the file has a line per ZIP. We need to run a reduce transform, -that collects rows togeather using a field key, which in this case is "{{row.STCOUNTYFP}}", -and then runs a function merge that takes two messages, merges them togeather -and produces a single output message.

-

The two messages:

-
{ "ZIP" : "36003", "COUNTYNAME" : "Autauga County", "STATE" : "AL", "STCOUNTYFP" : "01001", "CLASSFP" : "H1"}
-{ "ZIP" : "36006", "COUNTYNAME" : "Autauga County", "STATE" : "AL", "STCOUNTYFP" : "01001", "CLASSFP" : "H1"}
-

Would be merged into the message:

-
{ "ZIP" : ["36003", "36006"], "COUNTYNAME" : "Autauga County", "STATE" : "AL", "STCOUNTYFP" : "01001", "CLASSFP" : "H1"}
-

The reduce transform step uses a block of python code to describe the function. -The method field names the function, in this case merge that will be used -as the reduce function.

-
  zipReduce:
-    - from: zipcode
-    - reduce:
-        field: STCOUNTYFP
-        method: merge
-        python: >
-          def merge(x,y):
-            a = x.get('zipcodes', []) + [x['ZIP']]
-            b = y.get('zipcodes', []) + [y['ZIP']]
-            x['zipcodes'] = a + b
-            return x
-

The original messages produced by the loader have all of the information required -by the summary_location object type as described by the JSON schema that was linked -to in the header stanza. However, the data is all under the wrong field names. -To remap the data, we use a project tranformation that uses the template engine -to project data into new files in the message. The template engine has the current -message data in the value row. So the value -FIPS:{{row.STCOUNTYFP}} is mapped into the field id.

-
  - project:
-      mapping:
-        id: "FIPS:{{row.STCOUNTYFP}}"
-        province_state: "{{row.STATE}}"
-        summary_locations: "{{row.STCOUNTYFP}}"
-        county: "{{row.COUNTYNAME}}"
-        submitter_id: "{{row.STCOUNTYFP}}"
-        type: summary_location
-        projects: []
-

Using this projection, the message:

-
{
-  "ZIP" : ["36003", "36006"],
-  "COUNTYNAME" : "Autauga County",
-  "STATE" : "AL",
-  "STCOUNTYFP" : "01001",
-  "CLASSFP" : "H1"
-}
-

would become

-
{
-  "id" : "FIPS:01001",
-  "province_state" : "AL",
-  "summary_locations" : "01001",
-  "county" : "Autauga County",
-  "submitter_id" : "01001",
-  "type" : "summary_location"
-  "projects" : [],
-  "ZIP" : ["36003", "36006"],
-  "COUNTYNAME" : "Autauga County",
-  "STATE" : "AL",
-  "STCOUNTYFP" : "01001",
-  "CLASSFP" : "H1"
-}
-

Now that the data has been remapped, we pass the data into the ‘objectCreate’ -transformation, which will read in the schema for summary_location, check the -message to make sure it matches and then output it.

-
  - objectCreate:
-        class: summary_location
-

Outputs

-

To create an output table, with two columns connecting -ZIP values to STCOUNTYFP values. The STCOUNTYFP is a county level FIPS -code, used by the census office. A single FIPS code my contain many ZIP codes, -and we can use this table later for mapping ids when loading the data into a database.

-
outputs:
-  zip2fips:
-    tableWrite:
-      from: 
-      output: zip2fips
-      columns:
-        - ZIP
-        - STCOUNTYFP
-
-
- -
- - diff --git a/website/public/docs/index.html b/website/public/docs/index.html deleted file mode 100644 index 47922a2..0000000 --- a/website/public/docs/index.html +++ /dev/null @@ -1,365 +0,0 @@ - - - - - - - - - - - Overview · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

Sifter pipelines

-

Sifter pipelines process steams of nested JSON messages. Sifter comes with a number of -file extractors that operate as inputs to these pipelines. The pipeline engine -connects togeather arrays of transform steps into direct acylic graph that is processed -in parallel.

-

Example Message:

-
{
-  "firstName" : "bob",
-  "age" : "25"
-  "friends" : [ "Max", "Alex"]
-}
-

Once a stream of messages are produced, that can be run through a transform -pipeline. A transform pipeline is an array of transform steps, each transform -step can represent a different way to alter the data. The array of transforms link -togeather into a pipe that makes multiple alterations to messages as they are -passed along. There are a number of different transform steps types that can -be done in a transform pipeline these include:

-
    -
  • Projection: creating new fields using a templating engine driven by existing values
  • -
  • Filtering: removing messages
  • -
  • Programmatic transformation: alter messages using an embedded python interpreter
  • -
  • Table based field translation
  • -
  • Outputing the message as a JSON Schema checked object
  • -
- -
- -
- - diff --git a/website/public/docs/index.xml b/website/public/docs/index.xml deleted file mode 100644 index 464694e..0000000 --- a/website/public/docs/index.xml +++ /dev/null @@ -1,304 +0,0 @@ - - - - Docs on Sifter - http://example.org/docs/ - Recent content in Docs on Sifter - Hugo -- gohugo.io - en-us - - accumulate - http://example.org/docs/transforms/accumulate/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/accumulate/ - - - - - avroLoad - http://example.org/docs/inputs/avroload/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/avroload/ - avroLoad Load an AvroFile -Parameters name Description input Path to input file - - - - clean - http://example.org/docs/transforms/clean/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/clean/ - - - - - debug - http://example.org/docs/transforms/debug/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/debug/ - - - - - distinct - http://example.org/docs/transforms/distinct/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/distinct/ - - - - - embedded - http://example.org/docs/inputs/embedded/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/embedded/ - embedded Load data from embedded structure -Example inputs: data: embedded: - { &#34;name&#34; : &#34;Alice&#34;, &#34;age&#34;: 28 } - { &#34;name&#34; : &#34;Bob&#34;, &#34;age&#34;: 27 } - - - - emit - http://example.org/docs/transforms/emit/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/emit/ - - - - - Example - http://example.org/docs/example/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/example/ - Example Pipeline Our first task will be to convert a ZIP code TSV into a set of county level entries. -The input file looks like: -ZIP,COUNTYNAME,STATE,STCOUNTYFP,CLASSFP 36003,Autauga County,AL,01001,H1 36006,Autauga County,AL,01001,H1 36067,Autauga County,AL,01001,H1 36066,Autauga County,AL,01001,H1 36703,Autauga County,AL,01001,H1 36701,Autauga County,AL,01001,H1 36091,Autauga County,AL,01001,H1 First is the header of the pipeline. This declares the unique name of the pipeline and it&rsquo;s output directory. -name: zipcode_map outdir: ./ docs: Converts zipcode TSV into graph elements Next the configuration is declared. - - - - fieldParse - http://example.org/docs/transforms/fieldparse/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/fieldparse/ - - - - - fieldProcess - http://example.org/docs/transforms/fieldprocess/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/fieldprocess/ - - - - - fieldType - http://example.org/docs/transforms/fieldtype/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/fieldtype/ - - - - - filter - http://example.org/docs/transforms/filter/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/filter/ - - - - - from - http://example.org/docs/transforms/from/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/from/ - from Parmeters Name of data source -Example inputs: profileReader: tableLoad: input: &#34;{{config.profiles}}&#34; pipelines: profileProcess: - from: profileReader - - - - glob - http://example.org/docs/inputs/glob/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/glob/ - glob Scan files using * based glob statement and open all files as input. -Parameters Name Description storeFilename Store value of filename in parameter each row input Path of avro object file to transform xmlLoad xmlLoad configutation tableLoad Run transform pipeline on a TSV or CSV jsonLoad Run a transform pipeline on a multi line json file avroLoad Load data from avro file Example inputs: pubmedRead: glob: input: &#34;{{config.baseline}}/*.xml.gz&#34; xmlLoad: {} - - - - graphBuild - http://example.org/docs/transforms/graphbuild/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/graphbuild/ - - - - - gripperLoad - http://example.org/docs/inputs/gripperload/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/gripperload/ - - - - - hash - http://example.org/docs/transforms/hash/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/hash/ - - - - - Inputs - http://example.org/docs/inputs/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/ - Every playbook consists of a series of inputs. - - - - jsonLoad - http://example.org/docs/inputs/jsonload/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/jsonload/ - jsonLoad Load data from a JSON file. Default behavior expects a single dictionary per line. Each line is a seperate entry. The multiline parameter reads all of the lines of the files and returns a single object. -Parameters name Description input Path of JSON file to transform multiline Load file as a single multiline JSON object Example inputs: caseData: jsonLoad: input: &#34;{{config.casesJSON}}&#34; - - - - lookup - http://example.org/docs/transforms/lookup/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/lookup/ - lookup Using key from current row, get values from a reference source -Parameters name Type Description replace string (field path) Field to replace lookup string (template string) Key to use for looking up data copy map[string]string Given lookup of structure, copy values (key) to row (value) tsv TSVTable TSV translation table file json JSONTable JSON data file table LookupTable Inline lookup table Example - lookup: json: input: &#34;{{config.doseResponseFile}}&#34; key: experiment_id lookup: &#34;{{row. - - - - map - http://example.org/docs/transforms/map/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/map/ - map Run function on every row -Parameters name Description method Name of function to call python Python code to be run gpython Python code to be run using GPython Example - map: method: response gpython: | def response(x): s = sorted(x[&#34;curve&#34;].items(), key=lambda x:float(x[0])) x[&#39;dose_um&#39;] = [] x[&#39;response&#39;] = [] for d, r in s: try: dn = float(d) rn = float(r) x[&#39;dose_um&#39;].append(dn) x[&#39;response&#39;].append(rn) except ValueError: pass return x - - - - objectCreate - http://example.org/docs/transforms/objectcreate/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/objectcreate/ - - - - - Pipeline Steps - http://example.org/docs/transforms/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/ - Transforms alter the data - - - - project - http://example.org/docs/transforms/project/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/project/ - - - - - reduce - http://example.org/docs/transforms/reduce/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/reduce/ - reduce Using key from rows, reduce matched records into a single entry -Parameters name Type Description field string (field path) Field used to match rows method string Method name python string Python code string gpython string Python code string run using (https://github.com/go-python/gpython) init map[string]any Data to use for first reduce Example - reduce: field: dataset_name method: merge init: { &#34;compounds&#34; : [] } gpython: | def merge(x,y): x[&#34;compounds&#34;] = list(set(y[&#34;compounds&#34;]+x[&#34;compounds&#34;])) return x - - - - regexReplace - http://example.org/docs/transforms/regexreplace/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/regexreplace/ - - - - - Sifter Pipeline File - http://example.org/docs/playbook/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/playbook/ - Pipeline File An sifter pipeline file is in YAML format and describes an entire processing pipelines. If is composed of the following sections: config, inputs, pipelines, outputs. In addition, for tracking, the file will also include name and class entries. -class: sifter name: &lt;script name&gt; outdir: &lt;where output files should go, relative to this file&gt; config: &lt;config key&gt;: &lt;config value&gt; &lt;config key&gt;: &lt;config value&gt; # values that are referenced in pipeline parameters for # files will be treated like file paths and be # translated to full paths inputs: &lt;input name&gt;: &lt;input driver&gt;: &lt;driver config&gt; pipelines: &lt;pipeline name&gt;: # all pipelines must start with a from step - from: &lt;name of input or pipeline&gt; - &lt;transform name&gt;: &lt;transform parameters&gt; outputs: &lt;output name&gt;: &lt;output driver&gt;: &lt;driver config&gt; - - - - sqldump - http://example.org/docs/inputs/sqldump/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/sqldump/ - sqlDump Scan file produced produced from sqldump. -Parameters Name Type Description input string Path to the SQL dump file tables []string Names of tables to read out Example inputs: database: sqldumpLoad: input: &#34;{{config.sql}}&#34; tables: - cells - cell_tissues - dose_responses - drugs - drug_annots - experiments - profiles - - - - sqliteLoad - http://example.org/docs/inputs/sqliteload/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/sqliteload/ - sqliteLoad Extract data from an sqlite file -Parameters Name Type Description input string Path to the SQLite file query string SQL select statement based input Example inputs: sqlQuery: sqliteLoad: input: &#34;{{config.sqlite}}&#34; query: &#34;select * from drug_mechanism as a LEFT JOIN MECHANISM_REFS as b on a.MEC_ID=b.MEC_ID LEFT JOIN TARGET_COMPONENTS as c on a.TID=c.TID LEFT JOIN COMPONENT_SEQUENCES as d on c.COMPONENT_ID=d.COMPONENT_ID LEFT JOIN MOLECULE_DICTIONARY as e on a.MOLREGNO=e.MOLREGNO&#34; - - - - tableLoad - http://example.org/docs/inputs/tableload/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/tableload/ - - - - - xmlLoad - http://example.org/docs/inputs/xmlload/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/xmlload/ - xmlLoad Load an XML file -Parameters name Description input Path to input file Example inputs: loader: xmlLoad: input: &#34;{{config.xmlPath}}&#34; - - - - diff --git a/website/public/docs/inputs/avroload/index.html b/website/public/docs/inputs/avroload/index.html deleted file mode 100644 index f7faca2..0000000 --- a/website/public/docs/inputs/avroload/index.html +++ /dev/null @@ -1,358 +0,0 @@ - - - - - - - - - - - avroLoad · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

avroLoad

-

Load an AvroFile

-

Parameters

- - - - - - - - - - - - - -
nameDescription
inputPath to input file
- -
- -
- - diff --git a/website/public/docs/inputs/embedded/index.html b/website/public/docs/inputs/embedded/index.html deleted file mode 100644 index 8d2c1ad..0000000 --- a/website/public/docs/inputs/embedded/index.html +++ /dev/null @@ -1,349 +0,0 @@ - - - - - - - - - - - embedded · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

embedded

-

Load data from embedded structure

-

Example

-
inputs:
-  data:
-    embedded:
-      - { "name" : "Alice", "age": 28 }
-      - { "name" : "Bob", "age": 27 }
-
-
- -
- - diff --git a/website/public/docs/inputs/glob/index.html b/website/public/docs/inputs/glob/index.html deleted file mode 100644 index fe2e3e3..0000000 --- a/website/public/docs/inputs/glob/index.html +++ /dev/null @@ -1,385 +0,0 @@ - - - - - - - - - - - glob · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

glob

-

Scan files using * based glob statement and open all files -as input.

-

Parameters

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameDescription
storeFilenameStore value of filename in parameter each row
inputPath of avro object file to transform
xmlLoadxmlLoad configutation
tableLoadRun transform pipeline on a TSV or CSV
jsonLoadRun a transform pipeline on a multi line json file
avroLoadLoad data from avro file
-

Example

-
inputs:
-  pubmedRead:
-    glob:
-      input: "{{config.baseline}}/*.xml.gz"
-      xmlLoad: {}
-
-
- -
- - diff --git a/website/public/docs/inputs/gripperload/index.html b/website/public/docs/inputs/gripperload/index.html deleted file mode 100644 index b6bc4ae..0000000 --- a/website/public/docs/inputs/gripperload/index.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - - - - - - gripperLoad · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/website/public/docs/inputs/index.html b/website/public/docs/inputs/index.html deleted file mode 100644 index 2a4a0e6..0000000 --- a/website/public/docs/inputs/index.html +++ /dev/null @@ -1,342 +0,0 @@ - - - - - - - - - - - Inputs · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

Every playbook consists of a series of inputs.

- -
- -
- - diff --git a/website/public/docs/inputs/jsonload/index.html b/website/public/docs/inputs/jsonload/index.html deleted file mode 100644 index faaea61..0000000 --- a/website/public/docs/inputs/jsonload/index.html +++ /dev/null @@ -1,367 +0,0 @@ - - - - - - - - - - - jsonLoad · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

jsonLoad

-

Load data from a JSON file. Default behavior expects a single dictionary per line. Each line is a seperate entry. The multiline parameter reads all of the lines of the files and returns a single object.

-

Parameters

- - - - - - - - - - - - - - - - - -
nameDescription
inputPath of JSON file to transform
multilineLoad file as a single multiline JSON object
-

Example

-
inputs:
-  caseData:
-    jsonLoad:
-      input: "{{config.casesJSON}}"
-
-
- -
- - diff --git a/website/public/docs/inputs/sqldump/index.html b/website/public/docs/inputs/sqldump/index.html deleted file mode 100644 index ddeaf7f..0000000 --- a/website/public/docs/inputs/sqldump/index.html +++ /dev/null @@ -1,378 +0,0 @@ - - - - - - - - - - - sqldump · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

sqlDump

-

Scan file produced produced from sqldump.

-

Parameters

- - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
inputstringPath to the SQL dump file
tables[]stringNames of tables to read out
-

Example

-
inputs:
-  database:
-    sqldumpLoad:
-      input: "{{config.sql}}"
-      tables:
-        - cells
-        - cell_tissues
-        - dose_responses
-        - drugs
-        - drug_annots
-        - experiments
-        - profiles
-
-
- -
- - diff --git a/website/public/docs/inputs/sqliteload/index.html b/website/public/docs/inputs/sqliteload/index.html deleted file mode 100644 index c30f2c4..0000000 --- a/website/public/docs/inputs/sqliteload/index.html +++ /dev/null @@ -1,372 +0,0 @@ - - - - - - - - - - - sqliteLoad · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

sqliteLoad

-

Extract data from an sqlite file

-

Parameters

- - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
inputstringPath to the SQLite file
querystringSQL select statement based input
-

Example

-

-inputs:
-  sqlQuery:
-    sqliteLoad:
-      input: "{{config.sqlite}}"
-      query: "select * from drug_mechanism as a LEFT JOIN MECHANISM_REFS as b on a.MEC_ID=b.MEC_ID LEFT JOIN TARGET_COMPONENTS as c on a.TID=c.TID LEFT JOIN COMPONENT_SEQUENCES as d on c.COMPONENT_ID=d.COMPONENT_ID LEFT JOIN MOLECULE_DICTIONARY as e on a.MOLREGNO=e.MOLREGNO"
-
-
- -
- - diff --git a/website/public/docs/inputs/tableload/index.html b/website/public/docs/inputs/tableload/index.html deleted file mode 100644 index 9f10d36..0000000 --- a/website/public/docs/inputs/tableload/index.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - - - - - - tableLoad · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/website/public/docs/inputs/xmlload/index.html b/website/public/docs/inputs/xmlload/index.html deleted file mode 100644 index 833fd28..0000000 --- a/website/public/docs/inputs/xmlload/index.html +++ /dev/null @@ -1,363 +0,0 @@ - - - - - - - - - - - xmlLoad · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

xmlLoad

-

Load an XML file

-

Parameters

- - - - - - - - - - - - - -
nameDescription
inputPath to input file
-

Example

-
inputs:
-  loader:
-    xmlLoad:
-      input: "{{config.xmlPath}}"
-
-
- -
- - diff --git a/website/public/docs/playbook/index.html b/website/public/docs/playbook/index.html deleted file mode 100644 index ec46a3d..0000000 --- a/website/public/docs/playbook/index.html +++ /dev/null @@ -1,373 +0,0 @@ - - - - - - - - - - - Sifter Pipeline File · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

Pipeline File

-

An sifter pipeline file is in YAML format and describes an entire processing pipelines. -If is composed of the following sections: config, inputs, pipelines, outputs. In addition, -for tracking, the file will also include name and class entries.

-

-class: sifter
-name: <script name>
-outdir: <where output files should go, relative to this file>
-
-config:
-  <config key>: <config value>
-  <config key>: <config value> 
-  # values that are referenced in pipeline parameters for 
-  # files will be treated like file paths and be 
-  # translated to full paths
-
-inputs:
-  <input name>:
-    <input driver>:
-      <driver config>
-
-pipelines:
-  <pipeline name>:
-    # all pipelines must start with a from step
-    - from: <name of input or pipeline> 
-    - <transform name>:
-       <transform parameters>
-
-outputs:
-  <output name>:
-    <output driver>:
-      <driver config>
-
-
- -
- - diff --git a/website/public/docs/transforms/accumulate/index.html b/website/public/docs/transforms/accumulate/index.html deleted file mode 100644 index 38102d4..0000000 --- a/website/public/docs/transforms/accumulate/index.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - - - - - - accumulate · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/website/public/docs/transforms/clean/index.html b/website/public/docs/transforms/clean/index.html deleted file mode 100644 index 8ccfff1..0000000 --- a/website/public/docs/transforms/clean/index.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - - - - - - clean · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/website/public/docs/transforms/debug/index.html b/website/public/docs/transforms/debug/index.html deleted file mode 100644 index 1fd5f0f..0000000 --- a/website/public/docs/transforms/debug/index.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - - - - - - debug · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/website/public/docs/transforms/distinct/index.html b/website/public/docs/transforms/distinct/index.html deleted file mode 100644 index b486fe6..0000000 --- a/website/public/docs/transforms/distinct/index.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - - - - - - distinct · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/website/public/docs/transforms/emit/index.html b/website/public/docs/transforms/emit/index.html deleted file mode 100644 index 4d41462..0000000 --- a/website/public/docs/transforms/emit/index.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - - - - - - emit · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/website/public/docs/transforms/fieldparse/index.html b/website/public/docs/transforms/fieldparse/index.html deleted file mode 100644 index d474476..0000000 --- a/website/public/docs/transforms/fieldparse/index.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - - - - - - fieldParse · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/website/public/docs/transforms/fieldprocess/index.html b/website/public/docs/transforms/fieldprocess/index.html deleted file mode 100644 index d4bc610..0000000 --- a/website/public/docs/transforms/fieldprocess/index.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - - - - - - fieldProcess · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/website/public/docs/transforms/fieldtype/index.html b/website/public/docs/transforms/fieldtype/index.html deleted file mode 100644 index 0ddd76f..0000000 --- a/website/public/docs/transforms/fieldtype/index.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - - - - - - fieldType · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/website/public/docs/transforms/filter/index.html b/website/public/docs/transforms/filter/index.html deleted file mode 100644 index 5c1f9e0..0000000 --- a/website/public/docs/transforms/filter/index.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - - - - - - filter · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/website/public/docs/transforms/from/index.html b/website/public/docs/transforms/from/index.html deleted file mode 100644 index 24f3715..0000000 --- a/website/public/docs/transforms/from/index.html +++ /dev/null @@ -1,355 +0,0 @@ - - - - - - - - - - - from · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

from

-

Parmeters

-

Name of data source

-

Example

-

-
-inputs:
-  profileReader:
-    tableLoad:
-      input: "{{config.profiles}}"
-
-pipelines:
-  profileProcess:
-    - from: profileReader
-
-
- -
- - diff --git a/website/public/docs/transforms/graphbuild/index.html b/website/public/docs/transforms/graphbuild/index.html deleted file mode 100644 index 211e52f..0000000 --- a/website/public/docs/transforms/graphbuild/index.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - - - - - - graphBuild · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/website/public/docs/transforms/hash/index.html b/website/public/docs/transforms/hash/index.html deleted file mode 100644 index d4cd0fb..0000000 --- a/website/public/docs/transforms/hash/index.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - - - - - - hash · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/website/public/docs/transforms/index.html b/website/public/docs/transforms/index.html deleted file mode 100644 index 3c468c2..0000000 --- a/website/public/docs/transforms/index.html +++ /dev/null @@ -1,342 +0,0 @@ - - - - - - - - - - - Pipeline Steps · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

Transforms alter the data

- -
- -
- - diff --git a/website/public/docs/transforms/lookup/index.html b/website/public/docs/transforms/lookup/index.html deleted file mode 100644 index 9bdf56c..0000000 --- a/website/public/docs/transforms/lookup/index.html +++ /dev/null @@ -1,393 +0,0 @@ - - - - - - - - - - - lookup · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

lookup

-

Using key from current row, get values from a reference source

-

Parameters

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
nameTypeDescription
replacestring (field path)Field to replace
lookupstring (template string)Key to use for looking up data
copymap[string]stringGiven lookup of structure, copy values (key) to row (value)
tsvTSVTableTSV translation table file
jsonJSONTableJSON data file
tableLookupTableInline lookup table
-

Example

-
    - lookup:
-        json:
-          input: "{{config.doseResponseFile}}"
-          key: experiment_id
-        lookup: "{{row.experiment_id}}"
-        copy:
-          curve: curve
-
-
- -
- - diff --git a/website/public/docs/transforms/map/index.html b/website/public/docs/transforms/map/index.html deleted file mode 100644 index 77d82e7..0000000 --- a/website/public/docs/transforms/map/index.html +++ /dev/null @@ -1,383 +0,0 @@ - - - - - - - - - - - map · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

map

-

Run function on every row

-

Parameters

- - - - - - - - - - - - - - - - - - - - - -
nameDescription
methodName of function to call
pythonPython code to be run
gpythonPython code to be run using GPython
-

Example

-
    - map:
-        method: response
-        gpython: |
-          def response(x):
-            s = sorted(x["curve"].items(), key=lambda x:float(x[0]))
-            x['dose_um'] = []
-            x['response'] = []
-            for d, r in s:
-              try:
-                dn = float(d)
-                rn = float(r)
-                x['dose_um'].append(dn)
-                x['response'].append(rn)
-              except ValueError:
-                pass
-            return x          
-
-
- -
- - diff --git a/website/public/docs/transforms/objectcreate/index.html b/website/public/docs/transforms/objectcreate/index.html deleted file mode 100644 index bb0bf68..0000000 --- a/website/public/docs/transforms/objectcreate/index.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - - - - - - objectCreate · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/website/public/docs/transforms/project/index.html b/website/public/docs/transforms/project/index.html deleted file mode 100644 index cab2a30..0000000 --- a/website/public/docs/transforms/project/index.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - - - - - - project · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/website/public/docs/transforms/reduce/index.html b/website/public/docs/transforms/reduce/index.html deleted file mode 100644 index b42cfd7..0000000 --- a/website/public/docs/transforms/reduce/index.html +++ /dev/null @@ -1,390 +0,0 @@ - - - - - - - - - - - reduce · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-

reduce

-

Using key from rows, reduce matched records into a single entry

-

Parameters

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
nameTypeDescription
fieldstring (field path)Field used to match rows
methodstringMethod name
pythonstringPython code string
gpythonstringPython code string run using (https://github.com/go-python/gpython)
initmap[string]anyData to use for first reduce
-

Example

-
    - reduce:
-        field: dataset_name
-        method: merge
-        init: { "compounds" : [] }
-        gpython: |
-
-          def merge(x,y):
-            x["compounds"] = list(set(y["compounds"]+x["compounds"]))
-            return x
-
-
- -
- - diff --git a/website/public/docs/transforms/regexreplace/index.html b/website/public/docs/transforms/regexreplace/index.html deleted file mode 100644 index def1c1f..0000000 --- a/website/public/docs/transforms/regexreplace/index.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - - - - - - regexReplace · Sifter - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- -
- - diff --git a/website/public/index.html b/website/public/index.html deleted file mode 100644 index 063cb9e..0000000 --- a/website/public/index.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - Sifter - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-

SIFTER

-

Sifter is a Extract Tranform Load (ETL) engine. It can be used to -Extract from a number of different data resources, including TSV files, SQLDump -files and external databases. It includes a pipeline description language to -define a set of Transform steps to create object messages that can be -validated using a JSON schema data.

- -
-
- - - - - diff --git a/website/public/index.xml b/website/public/index.xml deleted file mode 100644 index 3acd47d..0000000 --- a/website/public/index.xml +++ /dev/null @@ -1,315 +0,0 @@ - - - - Sifter - http://example.org/ - Recent content on Sifter - Hugo -- gohugo.io - en-us - - accumulate - http://example.org/docs/transforms/accumulate/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/accumulate/ - - - - - avroLoad - http://example.org/docs/inputs/avroload/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/avroload/ - avroLoad Load an AvroFile -Parameters name Description input Path to input file - - - - clean - http://example.org/docs/transforms/clean/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/clean/ - - - - - debug - http://example.org/docs/transforms/debug/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/debug/ - - - - - distinct - http://example.org/docs/transforms/distinct/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/distinct/ - - - - - embedded - http://example.org/docs/inputs/embedded/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/embedded/ - embedded Load data from embedded structure -Example inputs: data: embedded: - { &#34;name&#34; : &#34;Alice&#34;, &#34;age&#34;: 28 } - { &#34;name&#34; : &#34;Bob&#34;, &#34;age&#34;: 27 } - - - - emit - http://example.org/docs/transforms/emit/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/emit/ - - - - - Example - http://example.org/docs/example/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/example/ - Example Pipeline Our first task will be to convert a ZIP code TSV into a set of county level entries. -The input file looks like: -ZIP,COUNTYNAME,STATE,STCOUNTYFP,CLASSFP 36003,Autauga County,AL,01001,H1 36006,Autauga County,AL,01001,H1 36067,Autauga County,AL,01001,H1 36066,Autauga County,AL,01001,H1 36703,Autauga County,AL,01001,H1 36701,Autauga County,AL,01001,H1 36091,Autauga County,AL,01001,H1 First is the header of the pipeline. This declares the unique name of the pipeline and it&rsquo;s output directory. -name: zipcode_map outdir: ./ docs: Converts zipcode TSV into graph elements Next the configuration is declared. - - - - fieldParse - http://example.org/docs/transforms/fieldparse/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/fieldparse/ - - - - - fieldProcess - http://example.org/docs/transforms/fieldprocess/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/fieldprocess/ - - - - - fieldType - http://example.org/docs/transforms/fieldtype/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/fieldtype/ - - - - - filter - http://example.org/docs/transforms/filter/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/filter/ - - - - - from - http://example.org/docs/transforms/from/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/from/ - from Parmeters Name of data source -Example inputs: profileReader: tableLoad: input: &#34;{{config.profiles}}&#34; pipelines: profileProcess: - from: profileReader - - - - glob - http://example.org/docs/inputs/glob/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/glob/ - glob Scan files using * based glob statement and open all files as input. -Parameters Name Description storeFilename Store value of filename in parameter each row input Path of avro object file to transform xmlLoad xmlLoad configutation tableLoad Run transform pipeline on a TSV or CSV jsonLoad Run a transform pipeline on a multi line json file avroLoad Load data from avro file Example inputs: pubmedRead: glob: input: &#34;{{config.baseline}}/*.xml.gz&#34; xmlLoad: {} - - - - graphBuild - http://example.org/docs/transforms/graphbuild/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/graphbuild/ - - - - - gripperLoad - http://example.org/docs/inputs/gripperload/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/gripperload/ - - - - - hash - http://example.org/docs/transforms/hash/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/hash/ - - - - - Inputs - http://example.org/docs/inputs/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/ - Every playbook consists of a series of inputs. - - - - jsonLoad - http://example.org/docs/inputs/jsonload/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/jsonload/ - jsonLoad Load data from a JSON file. Default behavior expects a single dictionary per line. Each line is a seperate entry. The multiline parameter reads all of the lines of the files and returns a single object. -Parameters name Description input Path of JSON file to transform multiline Load file as a single multiline JSON object Example inputs: caseData: jsonLoad: input: &#34;{{config.casesJSON}}&#34; - - - - lookup - http://example.org/docs/transforms/lookup/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/lookup/ - lookup Using key from current row, get values from a reference source -Parameters name Type Description replace string (field path) Field to replace lookup string (template string) Key to use for looking up data copy map[string]string Given lookup of structure, copy values (key) to row (value) tsv TSVTable TSV translation table file json JSONTable JSON data file table LookupTable Inline lookup table Example - lookup: json: input: &#34;{{config.doseResponseFile}}&#34; key: experiment_id lookup: &#34;{{row. - - - - map - http://example.org/docs/transforms/map/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/map/ - map Run function on every row -Parameters name Description method Name of function to call python Python code to be run gpython Python code to be run using GPython Example - map: method: response gpython: | def response(x): s = sorted(x[&#34;curve&#34;].items(), key=lambda x:float(x[0])) x[&#39;dose_um&#39;] = [] x[&#39;response&#39;] = [] for d, r in s: try: dn = float(d) rn = float(r) x[&#39;dose_um&#39;].append(dn) x[&#39;response&#39;].append(rn) except ValueError: pass return x - - - - objectCreate - http://example.org/docs/transforms/objectcreate/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/objectcreate/ - - - - - Overview - http://example.org/docs/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/ - Sifter pipelines Sifter pipelines process steams of nested JSON messages. Sifter comes with a number of file extractors that operate as inputs to these pipelines. The pipeline engine connects togeather arrays of transform steps into direct acylic graph that is processed in parallel. -Example Message: -{ &#34;firstName&#34; : &#34;bob&#34;, &#34;age&#34; : &#34;25&#34; &#34;friends&#34; : [ &#34;Max&#34;, &#34;Alex&#34;] } Once a stream of messages are produced, that can be run through a transform pipeline. - - - - Pipeline Steps - http://example.org/docs/transforms/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/ - Transforms alter the data - - - - project - http://example.org/docs/transforms/project/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/project/ - - - - - reduce - http://example.org/docs/transforms/reduce/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/reduce/ - reduce Using key from rows, reduce matched records into a single entry -Parameters name Type Description field string (field path) Field used to match rows method string Method name python string Python code string gpython string Python code string run using (https://github.com/go-python/gpython) init map[string]any Data to use for first reduce Example - reduce: field: dataset_name method: merge init: { &#34;compounds&#34; : [] } gpython: | def merge(x,y): x[&#34;compounds&#34;] = list(set(y[&#34;compounds&#34;]+x[&#34;compounds&#34;])) return x - - - - regexReplace - http://example.org/docs/transforms/regexreplace/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/transforms/regexreplace/ - - - - - Sifter Pipeline File - http://example.org/docs/playbook/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/playbook/ - Pipeline File An sifter pipeline file is in YAML format and describes an entire processing pipelines. If is composed of the following sections: config, inputs, pipelines, outputs. In addition, for tracking, the file will also include name and class entries. -class: sifter name: &lt;script name&gt; outdir: &lt;where output files should go, relative to this file&gt; config: &lt;config key&gt;: &lt;config value&gt; &lt;config key&gt;: &lt;config value&gt; # values that are referenced in pipeline parameters for # files will be treated like file paths and be # translated to full paths inputs: &lt;input name&gt;: &lt;input driver&gt;: &lt;driver config&gt; pipelines: &lt;pipeline name&gt;: # all pipelines must start with a from step - from: &lt;name of input or pipeline&gt; - &lt;transform name&gt;: &lt;transform parameters&gt; outputs: &lt;output name&gt;: &lt;output driver&gt;: &lt;driver config&gt; - - - - sqldump - http://example.org/docs/inputs/sqldump/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/sqldump/ - sqlDump Scan file produced produced from sqldump. -Parameters Name Type Description input string Path to the SQL dump file tables []string Names of tables to read out Example inputs: database: sqldumpLoad: input: &#34;{{config.sql}}&#34; tables: - cells - cell_tissues - dose_responses - drugs - drug_annots - experiments - profiles - - - - sqliteLoad - http://example.org/docs/inputs/sqliteload/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/sqliteload/ - sqliteLoad Extract data from an sqlite file -Parameters Name Type Description input string Path to the SQLite file query string SQL select statement based input Example inputs: sqlQuery: sqliteLoad: input: &#34;{{config.sqlite}}&#34; query: &#34;select * from drug_mechanism as a LEFT JOIN MECHANISM_REFS as b on a.MEC_ID=b.MEC_ID LEFT JOIN TARGET_COMPONENTS as c on a.TID=c.TID LEFT JOIN COMPONENT_SEQUENCES as d on c.COMPONENT_ID=d.COMPONENT_ID LEFT JOIN MOLECULE_DICTIONARY as e on a.MOLREGNO=e.MOLREGNO&#34; - - - - tableLoad - http://example.org/docs/inputs/tableload/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/tableload/ - - - - - xmlLoad - http://example.org/docs/inputs/xmlload/ - Mon, 01 Jan 0001 00:00:00 +0000 - - http://example.org/docs/inputs/xmlload/ - xmlLoad Load an XML file -Parameters name Description input Path to input file Example inputs: loader: xmlLoad: input: &#34;{{config.xmlPath}}&#34; - - - - diff --git a/website/public/sitemap.xml b/website/public/sitemap.xml deleted file mode 100644 index 3c9fad6..0000000 --- a/website/public/sitemap.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - - http://example.org/ - 0 - - http://example.org/docs/transforms/accumulate/ - - http://example.org/docs/inputs/avroload/ - - http://example.org/categories/ - - http://example.org/docs/transforms/clean/ - - http://example.org/docs/transforms/debug/ - - http://example.org/docs/transforms/distinct/ - - http://example.org/docs/ - - http://example.org/docs/inputs/embedded/ - - http://example.org/docs/transforms/emit/ - - http://example.org/docs/example/ - - http://example.org/docs/transforms/fieldparse/ - - http://example.org/docs/transforms/fieldprocess/ - - http://example.org/docs/transforms/fieldtype/ - - http://example.org/docs/transforms/filter/ - - http://example.org/docs/transforms/from/ - - http://example.org/docs/inputs/glob/ - - http://example.org/docs/transforms/graphbuild/ - - http://example.org/docs/inputs/gripperload/ - - http://example.org/docs/transforms/hash/ - - http://example.org/docs/inputs/ - - http://example.org/docs/inputs/jsonload/ - - http://example.org/docs/transforms/lookup/ - - http://example.org/docs/transforms/map/ - - http://example.org/docs/transforms/objectcreate/ - - http://example.org/docs/ - - http://example.org/docs/transforms/ - - http://example.org/docs/transforms/project/ - - http://example.org/docs/transforms/reduce/ - - http://example.org/docs/transforms/regexreplace/ - - http://example.org/docs/playbook/ - - http://example.org/docs/inputs/sqldump/ - - http://example.org/docs/inputs/sqliteload/ - - http://example.org/docs/inputs/tableload/ - - http://example.org/tags/ - - http://example.org/docs/inputs/xmlload/ - - diff --git a/website/public/tags/index.xml b/website/public/tags/index.xml deleted file mode 100644 index 78658a6..0000000 --- a/website/public/tags/index.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - Tags on Sifter - http://example.org/tags/ - Recent content in Tags on Sifter - Hugo -- gohugo.io - en-us - - diff --git a/website/static/css/darcula.css b/website/static/css/darcula.css deleted file mode 100644 index be182d0..0000000 --- a/website/static/css/darcula.css +++ /dev/null @@ -1,77 +0,0 @@ -/* - -Darcula color scheme from the JetBrains family of IDEs - -*/ - - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #2b2b2b; -} - -.hljs { - color: #bababa; -} - -.hljs-strong, -.hljs-emphasis { - color: #a8a8a2; -} - -.hljs-bullet, -.hljs-quote, -.hljs-link, -.hljs-number, -.hljs-regexp, -.hljs-literal { - color: #6896ba; -} - -.hljs-code, -.hljs-selector-class { - color: #a6e22e; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-section, -.hljs-attribute, -.hljs-name, -.hljs-variable { - color: #cb7832; -} - -.hljs-params { - color: #b9b9b9; -} - -.hljs-string { - color: #6a8759; -} - -.hljs-subst, -.hljs-type, -.hljs-built_in, -.hljs-builtin-name, -.hljs-symbol, -.hljs-selector-id, -.hljs-selector-attr, -.hljs-selector-pseudo, -.hljs-template-tag, -.hljs-template-variable, -.hljs-addition { - color: #e0c46c; -} - -.hljs-comment, -.hljs-deletion, -.hljs-meta { - color: #7f7f7f; -} diff --git a/website/static/css/dark.css b/website/static/css/dark.css deleted file mode 100644 index b4724f5..0000000 --- a/website/static/css/dark.css +++ /dev/null @@ -1,63 +0,0 @@ -/* - -Dark style from softwaremaniacs.org (c) Ivan Sagalaev - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #444; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal, -.hljs-section, -.hljs-link { - color: white; -} - -.hljs, -.hljs-subst { - color: #ddd; -} - -.hljs-string, -.hljs-title, -.hljs-name, -.hljs-type, -.hljs-attribute, -.hljs-symbol, -.hljs-bullet, -.hljs-built_in, -.hljs-addition, -.hljs-variable, -.hljs-template-tag, -.hljs-template-variable { - color: #d88; -} - -.hljs-comment, -.hljs-quote, -.hljs-deletion, -.hljs-meta { - color: #777; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal, -.hljs-title, -.hljs-section, -.hljs-doctag, -.hljs-type, -.hljs-name, -.hljs-strong { - font-weight: bold; -} - -.hljs-emphasis { - font-style: italic; -} diff --git a/website/static/css/flexboxgrid.css b/website/static/css/flexboxgrid.css deleted file mode 100644 index 603506f..0000000 --- a/website/static/css/flexboxgrid.css +++ /dev/null @@ -1,960 +0,0 @@ -.container-fluid, -.container { - margin-right: auto; - margin-left: auto; -} - -.container-fluid { - padding-right: 2rem; - padding-left: 2rem; -} - -.row { - box-sizing: border-box; - display: -webkit-box; - display: -ms-flexbox; - display: flex; - -webkit-box-flex: 0; - -ms-flex: 0 1 auto; - flex: 0 1 auto; - -webkit-box-orient: horizontal; - -webkit-box-direction: normal; - -ms-flex-direction: row; - flex-direction: row; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - margin-right: -0.5rem; - margin-left: -0.5rem; -} - -.row.reverse { - -webkit-box-orient: horizontal; - -webkit-box-direction: reverse; - -ms-flex-direction: row-reverse; - flex-direction: row-reverse; -} - -.col.reverse { - -webkit-box-orient: vertical; - -webkit-box-direction: reverse; - -ms-flex-direction: column-reverse; - flex-direction: column-reverse; -} - -.col-xs, -.col-xs-1, -.col-xs-2, -.col-xs-3, -.col-xs-4, -.col-xs-5, -.col-xs-6, -.col-xs-7, -.col-xs-8, -.col-xs-9, -.col-xs-10, -.col-xs-11, -.col-xs-12, -.col-xs-offset-0, -.col-xs-offset-1, -.col-xs-offset-2, -.col-xs-offset-3, -.col-xs-offset-4, -.col-xs-offset-5, -.col-xs-offset-6, -.col-xs-offset-7, -.col-xs-offset-8, -.col-xs-offset-9, -.col-xs-offset-10, -.col-xs-offset-11, -.col-xs-offset-12 { - box-sizing: border-box; - -webkit-box-flex: 0; - -ms-flex: 0 0 auto; - flex: 0 0 auto; - padding-right: 0.5rem; - padding-left: 0.5rem; -} - -.col-xs { - -webkit-box-flex: 1; - -ms-flex-positive: 1; - flex-grow: 1; - -ms-flex-preferred-size: 0; - flex-basis: 0; - max-width: 100%; -} - -.col-xs-1 { - -ms-flex-preferred-size: 8.33333333%; - flex-basis: 8.33333333%; - max-width: 8.33333333%; -} - -.col-xs-2 { - -ms-flex-preferred-size: 16.66666667%; - flex-basis: 16.66666667%; - max-width: 16.66666667%; -} - -.col-xs-3 { - -ms-flex-preferred-size: 25%; - flex-basis: 25%; - max-width: 25%; -} - -.col-xs-4 { - -ms-flex-preferred-size: 33.33333333%; - flex-basis: 33.33333333%; - max-width: 33.33333333%; -} - -.col-xs-5 { - -ms-flex-preferred-size: 41.66666667%; - flex-basis: 41.66666667%; - max-width: 41.66666667%; -} - -.col-xs-6 { - -ms-flex-preferred-size: 50%; - flex-basis: 50%; - max-width: 50%; -} - -.col-xs-7 { - -ms-flex-preferred-size: 58.33333333%; - flex-basis: 58.33333333%; - max-width: 58.33333333%; -} - -.col-xs-8 { - -ms-flex-preferred-size: 66.66666667%; - flex-basis: 66.66666667%; - max-width: 66.66666667%; -} - -.col-xs-9 { - -ms-flex-preferred-size: 75%; - flex-basis: 75%; - max-width: 75%; -} - -.col-xs-10 { - -ms-flex-preferred-size: 83.33333333%; - flex-basis: 83.33333333%; - max-width: 83.33333333%; -} - -.col-xs-11 { - -ms-flex-preferred-size: 91.66666667%; - flex-basis: 91.66666667%; - max-width: 91.66666667%; -} - -.col-xs-12 { - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - max-width: 100%; -} - -.col-xs-offset-0 { - margin-left: 0; -} - -.col-xs-offset-1 { - margin-left: 8.33333333%; -} - -.col-xs-offset-2 { - margin-left: 16.66666667%; -} - -.col-xs-offset-3 { - margin-left: 25%; -} - -.col-xs-offset-4 { - margin-left: 33.33333333%; -} - -.col-xs-offset-5 { - margin-left: 41.66666667%; -} - -.col-xs-offset-6 { - margin-left: 50%; -} - -.col-xs-offset-7 { - margin-left: 58.33333333%; -} - -.col-xs-offset-8 { - margin-left: 66.66666667%; -} - -.col-xs-offset-9 { - margin-left: 75%; -} - -.col-xs-offset-10 { - margin-left: 83.33333333%; -} - -.col-xs-offset-11 { - margin-left: 91.66666667%; -} - -.start-xs { - -webkit-box-pack: start; - -ms-flex-pack: start; - justify-content: flex-start; - text-align: start; -} - -.center-xs { - -webkit-box-pack: center; - -ms-flex-pack: center; - justify-content: center; - text-align: center; -} - -.end-xs { - -webkit-box-pack: end; - -ms-flex-pack: end; - justify-content: flex-end; - text-align: end; -} - -.top-xs { - -webkit-box-align: start; - -ms-flex-align: start; - align-items: flex-start; -} - -.middle-xs { - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; -} - -.bottom-xs { - -webkit-box-align: end; - -ms-flex-align: end; - align-items: flex-end; -} - -.around-xs { - -ms-flex-pack: distribute; - justify-content: space-around; -} - -.between-xs { - -webkit-box-pack: justify; - -ms-flex-pack: justify; - justify-content: space-between; -} - -.first-xs { - -webkit-box-ordinal-group: 0; - -ms-flex-order: -1; - order: -1; -} - -.last-xs { - -webkit-box-ordinal-group: 2; - -ms-flex-order: 1; - order: 1; -} - -@media only screen and (min-width: 48em) { - .container { - width: 49rem; - } - - .col-sm, - .col-sm-1, - .col-sm-2, - .col-sm-3, - .col-sm-4, - .col-sm-5, - .col-sm-6, - .col-sm-7, - .col-sm-8, - .col-sm-9, - .col-sm-10, - .col-sm-11, - .col-sm-12, - .col-sm-offset-0, - .col-sm-offset-1, - .col-sm-offset-2, - .col-sm-offset-3, - .col-sm-offset-4, - .col-sm-offset-5, - .col-sm-offset-6, - .col-sm-offset-7, - .col-sm-offset-8, - .col-sm-offset-9, - .col-sm-offset-10, - .col-sm-offset-11, - .col-sm-offset-12 { - box-sizing: border-box; - -webkit-box-flex: 0; - -ms-flex: 0 0 auto; - flex: 0 0 auto; - padding-right: 0.5rem; - padding-left: 0.5rem; - } - - .col-sm { - -webkit-box-flex: 1; - -ms-flex-positive: 1; - flex-grow: 1; - -ms-flex-preferred-size: 0; - flex-basis: 0; - max-width: 100%; - } - - .col-sm-1 { - -ms-flex-preferred-size: 8.33333333%; - flex-basis: 8.33333333%; - max-width: 8.33333333%; - } - - .col-sm-2 { - -ms-flex-preferred-size: 16.66666667%; - flex-basis: 16.66666667%; - max-width: 16.66666667%; - } - - .col-sm-3 { - -ms-flex-preferred-size: 25%; - flex-basis: 25%; - max-width: 25%; - } - - .col-sm-4 { - -ms-flex-preferred-size: 33.33333333%; - flex-basis: 33.33333333%; - max-width: 33.33333333%; - } - - .col-sm-5 { - -ms-flex-preferred-size: 41.66666667%; - flex-basis: 41.66666667%; - max-width: 41.66666667%; - } - - .col-sm-6 { - -ms-flex-preferred-size: 50%; - flex-basis: 50%; - max-width: 50%; - } - - .col-sm-7 { - -ms-flex-preferred-size: 58.33333333%; - flex-basis: 58.33333333%; - max-width: 58.33333333%; - } - - .col-sm-8 { - -ms-flex-preferred-size: 66.66666667%; - flex-basis: 66.66666667%; - max-width: 66.66666667%; - } - - .col-sm-9 { - -ms-flex-preferred-size: 75%; - flex-basis: 75%; - max-width: 75%; - } - - .col-sm-10 { - -ms-flex-preferred-size: 83.33333333%; - flex-basis: 83.33333333%; - max-width: 83.33333333%; - } - - .col-sm-11 { - -ms-flex-preferred-size: 91.66666667%; - flex-basis: 91.66666667%; - max-width: 91.66666667%; - } - - .col-sm-12 { - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - max-width: 100%; - } - - .col-sm-offset-0 { - margin-left: 0; - } - - .col-sm-offset-1 { - margin-left: 8.33333333%; - } - - .col-sm-offset-2 { - margin-left: 16.66666667%; - } - - .col-sm-offset-3 { - margin-left: 25%; - } - - .col-sm-offset-4 { - margin-left: 33.33333333%; - } - - .col-sm-offset-5 { - margin-left: 41.66666667%; - } - - .col-sm-offset-6 { - margin-left: 50%; - } - - .col-sm-offset-7 { - margin-left: 58.33333333%; - } - - .col-sm-offset-8 { - margin-left: 66.66666667%; - } - - .col-sm-offset-9 { - margin-left: 75%; - } - - .col-sm-offset-10 { - margin-left: 83.33333333%; - } - - .col-sm-offset-11 { - margin-left: 91.66666667%; - } - - .start-sm { - -webkit-box-pack: start; - -ms-flex-pack: start; - justify-content: flex-start; - text-align: start; - } - - .center-sm { - -webkit-box-pack: center; - -ms-flex-pack: center; - justify-content: center; - text-align: center; - } - - .end-sm { - -webkit-box-pack: end; - -ms-flex-pack: end; - justify-content: flex-end; - text-align: end; - } - - .top-sm { - -webkit-box-align: start; - -ms-flex-align: start; - align-items: flex-start; - } - - .middle-sm { - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - } - - .bottom-sm { - -webkit-box-align: end; - -ms-flex-align: end; - align-items: flex-end; - } - - .around-sm { - -ms-flex-pack: distribute; - justify-content: space-around; - } - - .between-sm { - -webkit-box-pack: justify; - -ms-flex-pack: justify; - justify-content: space-between; - } - - .first-sm { - -webkit-box-ordinal-group: 0; - -ms-flex-order: -1; - order: -1; - } - - .last-sm { - -webkit-box-ordinal-group: 2; - -ms-flex-order: 1; - order: 1; - } -} - -@media only screen and (min-width: 64em) { - .container { - width: 65rem; - } - - .col-md, - .col-md-1, - .col-md-2, - .col-md-3, - .col-md-4, - .col-md-5, - .col-md-6, - .col-md-7, - .col-md-8, - .col-md-9, - .col-md-10, - .col-md-11, - .col-md-12, - .col-md-offset-0, - .col-md-offset-1, - .col-md-offset-2, - .col-md-offset-3, - .col-md-offset-4, - .col-md-offset-5, - .col-md-offset-6, - .col-md-offset-7, - .col-md-offset-8, - .col-md-offset-9, - .col-md-offset-10, - .col-md-offset-11, - .col-md-offset-12 { - box-sizing: border-box; - -webkit-box-flex: 0; - -ms-flex: 0 0 auto; - flex: 0 0 auto; - padding-right: 0.5rem; - padding-left: 0.5rem; - } - - .col-md { - -webkit-box-flex: 1; - -ms-flex-positive: 1; - flex-grow: 1; - -ms-flex-preferred-size: 0; - flex-basis: 0; - max-width: 100%; - } - - .col-md-1 { - -ms-flex-preferred-size: 8.33333333%; - flex-basis: 8.33333333%; - max-width: 8.33333333%; - } - - .col-md-2 { - -ms-flex-preferred-size: 16.66666667%; - flex-basis: 16.66666667%; - max-width: 16.66666667%; - } - - .col-md-3 { - -ms-flex-preferred-size: 25%; - flex-basis: 25%; - max-width: 25%; - } - - .col-md-4 { - -ms-flex-preferred-size: 33.33333333%; - flex-basis: 33.33333333%; - max-width: 33.33333333%; - } - - .col-md-5 { - -ms-flex-preferred-size: 41.66666667%; - flex-basis: 41.66666667%; - max-width: 41.66666667%; - } - - .col-md-6 { - -ms-flex-preferred-size: 50%; - flex-basis: 50%; - max-width: 50%; - } - - .col-md-7 { - -ms-flex-preferred-size: 58.33333333%; - flex-basis: 58.33333333%; - max-width: 58.33333333%; - } - - .col-md-8 { - -ms-flex-preferred-size: 66.66666667%; - flex-basis: 66.66666667%; - max-width: 66.66666667%; - } - - .col-md-9 { - -ms-flex-preferred-size: 75%; - flex-basis: 75%; - max-width: 75%; - } - - .col-md-10 { - -ms-flex-preferred-size: 83.33333333%; - flex-basis: 83.33333333%; - max-width: 83.33333333%; - } - - .col-md-11 { - -ms-flex-preferred-size: 91.66666667%; - flex-basis: 91.66666667%; - max-width: 91.66666667%; - } - - .col-md-12 { - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - max-width: 100%; - } - - .col-md-offset-0 { - margin-left: 0; - } - - .col-md-offset-1 { - margin-left: 8.33333333%; - } - - .col-md-offset-2 { - margin-left: 16.66666667%; - } - - .col-md-offset-3 { - margin-left: 25%; - } - - .col-md-offset-4 { - margin-left: 33.33333333%; - } - - .col-md-offset-5 { - margin-left: 41.66666667%; - } - - .col-md-offset-6 { - margin-left: 50%; - } - - .col-md-offset-7 { - margin-left: 58.33333333%; - } - - .col-md-offset-8 { - margin-left: 66.66666667%; - } - - .col-md-offset-9 { - margin-left: 75%; - } - - .col-md-offset-10 { - margin-left: 83.33333333%; - } - - .col-md-offset-11 { - margin-left: 91.66666667%; - } - - .start-md { - -webkit-box-pack: start; - -ms-flex-pack: start; - justify-content: flex-start; - text-align: start; - } - - .center-md { - -webkit-box-pack: center; - -ms-flex-pack: center; - justify-content: center; - text-align: center; - } - - .end-md { - -webkit-box-pack: end; - -ms-flex-pack: end; - justify-content: flex-end; - text-align: end; - } - - .top-md { - -webkit-box-align: start; - -ms-flex-align: start; - align-items: flex-start; - } - - .middle-md { - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - } - - .bottom-md { - -webkit-box-align: end; - -ms-flex-align: end; - align-items: flex-end; - } - - .around-md { - -ms-flex-pack: distribute; - justify-content: space-around; - } - - .between-md { - -webkit-box-pack: justify; - -ms-flex-pack: justify; - justify-content: space-between; - } - - .first-md { - -webkit-box-ordinal-group: 0; - -ms-flex-order: -1; - order: -1; - } - - .last-md { - -webkit-box-ordinal-group: 2; - -ms-flex-order: 1; - order: 1; - } -} - -@media only screen and (min-width: 75em) { - .container { - width: 76rem; - } - - .col-lg, - .col-lg-1, - .col-lg-2, - .col-lg-3, - .col-lg-4, - .col-lg-5, - .col-lg-6, - .col-lg-7, - .col-lg-8, - .col-lg-9, - .col-lg-10, - .col-lg-11, - .col-lg-12, - .col-lg-offset-0, - .col-lg-offset-1, - .col-lg-offset-2, - .col-lg-offset-3, - .col-lg-offset-4, - .col-lg-offset-5, - .col-lg-offset-6, - .col-lg-offset-7, - .col-lg-offset-8, - .col-lg-offset-9, - .col-lg-offset-10, - .col-lg-offset-11, - .col-lg-offset-12 { - box-sizing: border-box; - -webkit-box-flex: 0; - -ms-flex: 0 0 auto; - flex: 0 0 auto; - padding-right: 0.5rem; - padding-left: 0.5rem; - } - - .col-lg { - -webkit-box-flex: 1; - -ms-flex-positive: 1; - flex-grow: 1; - -ms-flex-preferred-size: 0; - flex-basis: 0; - max-width: 100%; - } - - .col-lg-1 { - -ms-flex-preferred-size: 8.33333333%; - flex-basis: 8.33333333%; - max-width: 8.33333333%; - } - - .col-lg-2 { - -ms-flex-preferred-size: 16.66666667%; - flex-basis: 16.66666667%; - max-width: 16.66666667%; - } - - .col-lg-3 { - -ms-flex-preferred-size: 25%; - flex-basis: 25%; - max-width: 25%; - } - - .col-lg-4 { - -ms-flex-preferred-size: 33.33333333%; - flex-basis: 33.33333333%; - max-width: 33.33333333%; - } - - .col-lg-5 { - -ms-flex-preferred-size: 41.66666667%; - flex-basis: 41.66666667%; - max-width: 41.66666667%; - } - - .col-lg-6 { - -ms-flex-preferred-size: 50%; - flex-basis: 50%; - max-width: 50%; - } - - .col-lg-7 { - -ms-flex-preferred-size: 58.33333333%; - flex-basis: 58.33333333%; - max-width: 58.33333333%; - } - - .col-lg-8 { - -ms-flex-preferred-size: 66.66666667%; - flex-basis: 66.66666667%; - max-width: 66.66666667%; - } - - .col-lg-9 { - -ms-flex-preferred-size: 75%; - flex-basis: 75%; - max-width: 75%; - } - - .col-lg-10 { - -ms-flex-preferred-size: 83.33333333%; - flex-basis: 83.33333333%; - max-width: 83.33333333%; - } - - .col-lg-11 { - -ms-flex-preferred-size: 91.66666667%; - flex-basis: 91.66666667%; - max-width: 91.66666667%; - } - - .col-lg-12 { - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - max-width: 100%; - } - - .col-lg-offset-0 { - margin-left: 0; - } - - .col-lg-offset-1 { - margin-left: 8.33333333%; - } - - .col-lg-offset-2 { - margin-left: 16.66666667%; - } - - .col-lg-offset-3 { - margin-left: 25%; - } - - .col-lg-offset-4 { - margin-left: 33.33333333%; - } - - .col-lg-offset-5 { - margin-left: 41.66666667%; - } - - .col-lg-offset-6 { - margin-left: 50%; - } - - .col-lg-offset-7 { - margin-left: 58.33333333%; - } - - .col-lg-offset-8 { - margin-left: 66.66666667%; - } - - .col-lg-offset-9 { - margin-left: 75%; - } - - .col-lg-offset-10 { - margin-left: 83.33333333%; - } - - .col-lg-offset-11 { - margin-left: 91.66666667%; - } - - .start-lg { - -webkit-box-pack: start; - -ms-flex-pack: start; - justify-content: flex-start; - text-align: start; - } - - .center-lg { - -webkit-box-pack: center; - -ms-flex-pack: center; - justify-content: center; - text-align: center; - } - - .end-lg { - -webkit-box-pack: end; - -ms-flex-pack: end; - justify-content: flex-end; - text-align: end; - } - - .top-lg { - -webkit-box-align: start; - -ms-flex-align: start; - align-items: flex-start; - } - - .middle-lg { - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - } - - .bottom-lg { - -webkit-box-align: end; - -ms-flex-align: end; - align-items: flex-end; - } - - .around-lg { - -ms-flex-pack: distribute; - justify-content: space-around; - } - - .between-lg { - -webkit-box-pack: justify; - -ms-flex-pack: justify; - justify-content: space-between; - } - - .first-lg { - -webkit-box-ordinal-group: 0; - -ms-flex-order: -1; - order: -1; - } - - .last-lg { - -webkit-box-ordinal-group: 2; - -ms-flex-order: 1; - order: 1; - } -} diff --git a/website/static/css/funnel.css b/website/static/css/funnel.css deleted file mode 100644 index 825d6af..0000000 --- a/website/static/css/funnel.css +++ /dev/null @@ -1,245 +0,0 @@ -.global-header { - background-color: #23241f; - padding: .3rem .5rem; -} - -.global-header-container { - display: flex; - align-items: center; -} - -.global-header-container h1, -.global-header-container h2 { - margin: 0; - padding: 0; - color: white; -} -.global-header-container h1 { - font-size: 1.2rem; -} -.global-header-container h2 { - font-size: .9rem; -} - -.global-header-container, -.homepage { - max-width: 50rem; - margin: 0 auto; -} - -.homepage-intro .col { - padding: 0 1rem; -} -.homepage-intro li { - font-size: 0.8rem; -} - -.global-header-nav { - list-style-type: none; - padding: 0; - margin: 0; - margin-left: 3rem; - flex-grow: 1; - display: flex; - align-items: center; - justify-content: center; -} - -@media only screen and (max-width: 600px) { - .global-header-container, - .global-header-nav { - flex-direction: column; - } - .global-header-home, - .global-header-ohsucb { - margin: .5rem 0; - } - .global-header-nav { - margin: 0; - } - .global-header-nav li { - margin: .5rem 0; - } - - .homepage-demo .col h1, - .homepage-demo .col p { - margin-left: .3rem; - } - - .content { - padding: 0 .7rem; - } - - .sidebar { - padding: 1rem; - padding-bottom: 0; - } - - .sidebar-nav li { - margin: .3rem 0; - } -} - -.sidebar-nav { - font-size: .9rem; -} -.sidebar-nav span.intermediate { - color: #23241f; -} - -.global-header-nav li { - display: inline-block; - padding: 0 0.5rem; - font-size: .9rem; -} - -.global-header-nav li a { - color: white; -} - -.global-header a:hover, -.global-header a:hover h1, -.global-header a:hover h2, -.global-header-nav li a:hover { - color: #9ed9ff; - text-decoration: none; -} - -.lead { - font-size: .8rem; -} - -.lead a { - color: #b8d4e0; -} - -.homepage h2 { - text-align: center; - font-size: 1.5rem; - margin-bottom: 1rem; -} - -.homepage-lead { - background-color: #f1f1f1; - padding: 2rem 2rem 1rem 2rem; - border-radius: 10px; - margin-bottom: 1rem; - text-align: center; -} - -.homepage-lead-container { - max-width: 42rem; - margin: 0 auto; -} - -.homepage-lead h1 { - margin: 0; -} - -.homepage-footer { - height: 100px; -} - -.homepage-notice { - background-color: #fffcbf; - padding: 1rem 3rem; - border-radius: 10px; - margin-top: 0; - margin-bottom: 1rem; - text-align: center; -} - -.homepage-notice h4 { - font-size: 1rem; -} - -.homepage-notice h3, -.homepage-notice p { - margin: 0; -} - -.homepage-lead .download-button, -.homepage-lead .docs-button { - padding: 10px 30px; - border-radius: 5px; - border: 0; - color: white; - font-size: .7rem; - display: inline-block; - margin: 0.1rem 0.2rem; -} -.docs-button { - border-radius: 5px; - border: 0; - color: white; - font-size: .7rem; - background-color: #4ca0ea; - padding: 10px 30px; -} - -.homepage-lead .download-button { - background-color: #29b429; -} - -.homepage .row { - width: 100%; - margin-bottom: 20px; -} - -.homepage-demo { - margin-top: 3rem; - margin-bottom: 3rem; -} - -.homepage-demo h1.demo-header { - font-size: 1.5rem; - text-align: center; - margin-bottom: 2rem; -} - -.homepage-demo h1 { - font-size: 1rem; - margin: 0; -} - -.homepage-demo p { - margin: .7rem 0; - padding-right: .7rem; - font-size: .8rem; -} - -.homepage h3 { - font-size: 1rem; -} - -.homepage-more { - text-align: center; -} - -.homepage p { - font-size: .8rem; -} - -pre { - padding: 0; -} - -.homepage-demo .section { - margin-bottom: 2rem; -} - -.homepage-demo pre { - margin: 0; -} -.homepage-demo code { - width: 100%; - display: block; - font-size: .8rem; - border-radius: 0; -} - -.optional { - font-size: 1rem; - color: #aaa; - font-style: normal; -} diff --git a/website/static/css/highlight.min.css b/website/static/css/highlight.min.css deleted file mode 100644 index 7d8be18..0000000 --- a/website/static/css/highlight.min.css +++ /dev/null @@ -1 +0,0 @@ -.hljs{display:block;overflow-x:auto;padding:0.5em;background:#F0F0F0}.hljs,.hljs-subst{color:#444}.hljs-comment{color:#888888}.hljs-keyword,.hljs-attribute,.hljs-selector-tag,.hljs-meta-keyword,.hljs-doctag,.hljs-name{font-weight:bold}.hljs-type,.hljs-string,.hljs-number,.hljs-selector-id,.hljs-selector-class,.hljs-quote,.hljs-template-tag,.hljs-deletion{color:#880000}.hljs-title,.hljs-section{color:#880000;font-weight:bold}.hljs-regexp,.hljs-symbol,.hljs-variable,.hljs-template-variable,.hljs-link,.hljs-selector-attr,.hljs-selector-pseudo{color:#BC6060}.hljs-literal{color:#78A960}.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-addition{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold} \ No newline at end of file diff --git a/website/static/css/html5reset.css b/website/static/css/html5reset.css deleted file mode 100755 index 3bfbb3d..0000000 --- a/website/static/css/html5reset.css +++ /dev/null @@ -1,96 +0,0 @@ -/* html5reset.css - 01/11/2011 */ - -html, body, div, span, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -abbr, address, cite, code, -del, dfn, em, img, ins, kbd, q, samp, -small, strong, sub, sup, var, -b, i, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, figcaption, figure, -footer, header, hgroup, menu, nav, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - outline: 0; - font-size: 100%; - vertical-align: baseline; - background: transparent; -} - -body { - line-height: 1; -} - -article,aside,details,figcaption,figure, -footer,header,hgroup,menu,nav,section { - display: block; -} - -nav ul { - list-style: none; -} - -blockquote, q { - quotes: none; -} - -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; -} - -a { - margin: 0; - padding: 0; - font-size: 100%; - vertical-align: baseline; - background: transparent; -} - -/* change colours to suit your needs */ -ins { - background-color: #ff9; - color: #000; - text-decoration: none; -} - -/* change colours to suit your needs */ -mark { - background-color: #ff9; - color: #000; - font-style: italic; - font-weight: bold; -} - -del { - text-decoration: line-through; -} - -abbr[title], dfn[title] { - border-bottom: 1px dotted; - cursor: help; -} - -table { - border-collapse: collapse; - border-spacing: 0; -} - -/* change border colour to suit your needs */ -hr { - display: block; - height: 1px; - border: 0; - border-top: 1px solid #cccccc; - margin: 1em 0; - padding: 0; -} - -input, select { - vertical-align: middle; -} \ No newline at end of file diff --git a/website/static/css/hybrid.css b/website/static/css/hybrid.css deleted file mode 100644 index 29735a1..0000000 --- a/website/static/css/hybrid.css +++ /dev/null @@ -1,102 +0,0 @@ -/* - -vim-hybrid theme by w0ng (https://github.com/w0ng/vim-hybrid) - -*/ - -/*background color*/ -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #1d1f21; -} - -/*selection color*/ -.hljs::selection, -.hljs span::selection { - background: #373b41; -} - -.hljs::-moz-selection, -.hljs span::-moz-selection { - background: #373b41; -} - -/*foreground color*/ -.hljs { - color: #c5c8c6; -} - -/*color: fg_yellow*/ -.hljs-title, -.hljs-name { - color: #f0c674; -} - -/*color: fg_comment*/ -.hljs-comment, -.hljs-meta, -.hljs-meta .hljs-keyword { - color: #707880; -} - -/*color: fg_red*/ -.hljs-number, -.hljs-symbol, -.hljs-literal, -.hljs-deletion, -.hljs-link { - color: #cc6666 -} - -/*color: fg_green*/ -.hljs-string, -.hljs-doctag, -.hljs-addition, -.hljs-regexp, -.hljs-selector-attr, -.hljs-selector-pseudo { - color: #b5bd68; -} - -/*color: fg_purple*/ -.hljs-attribute, -.hljs-code, -.hljs-selector-id { - color: #b294bb; -} - -/*color: fg_blue*/ -.hljs-keyword, -.hljs-selector-tag, -.hljs-bullet, -.hljs-tag { - color: #81a2be; -} - -/*color: fg_aqua*/ -.hljs-subst, -.hljs-variable, -.hljs-template-tag, -.hljs-template-variable { - color: #8abeb7; -} - -/*color: fg_orange*/ -.hljs-type, -.hljs-built_in, -.hljs-builtin-name, -.hljs-quote, -.hljs-section, -.hljs-selector-class { - color: #de935f; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/website/static/css/monokai-sublime.css b/website/static/css/monokai-sublime.css deleted file mode 100644 index 2864170..0000000 --- a/website/static/css/monokai-sublime.css +++ /dev/null @@ -1,83 +0,0 @@ -/* - -Monokai Sublime style. Derived from Monokai by noformnocontent http://nn.mit-license.org/ - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #23241f; -} - -.hljs, -.hljs-tag, -.hljs-subst { - color: #f8f8f2; -} - -.hljs-strong, -.hljs-emphasis { - color: #a8a8a2; -} - -.hljs-bullet, -.hljs-quote, -.hljs-number, -.hljs-regexp, -.hljs-literal, -.hljs-link { - color: #ae81ff; -} - -.hljs-code, -.hljs-title, -.hljs-section, -.hljs-selector-class { - color: #a6e22e; -} - -.hljs-strong { - font-weight: bold; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-name, -.hljs-attr { - color: #f92672; -} - -.hljs-symbol, -.hljs-attribute { - color: #66d9ef; -} - -.hljs-params, -.hljs-class .hljs-title { - color: #f8f8f2; -} - -.hljs-string, -.hljs-type, -.hljs-built_in, -.hljs-builtin-name, -.hljs-selector-id, -.hljs-selector-attr, -.hljs-selector-pseudo, -.hljs-addition, -.hljs-variable, -.hljs-template-variable { - color: #e6db74; -} - -.hljs-comment, -.hljs-deletion, -.hljs-meta { - color: #75715e; -} diff --git a/website/static/css/poole.css b/website/static/css/poole.css deleted file mode 100644 index 03f9338..0000000 --- a/website/static/css/poole.css +++ /dev/null @@ -1,283 +0,0 @@ -/* - * ___ - * /\_ \ - * _____ ___ ___\//\ \ __ - * /\ '__`\ / __`\ / __`\\ \ \ /'__`\ - * \ \ \_\ \/\ \_\ \/\ \_\ \\_\ \_/\ __/ - * \ \ ,__/\ \____/\ \____//\____\ \____\ - * \ \ \/ \/___/ \/___/ \/____/\/____/ - * \ \_\ - * \/_/ - * - * Designed, built, and released under MIT license by @mdo. Learn more at - * https://github.com/poole/poole. - */ - - -/* - * Contents - * - * Body resets - * Custom type - * Messages - * Container - * Masthead - * Posts and pages - * Pagination - * Reverse layout - * Themes - */ - - -/* - * Body resets - * - * Update the foundational and global aspects of the page. - */ - -* { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -html, -body { - margin: 0; - padding: 0; -} - -html { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 16px; - line-height: 1.5; -} - -body { - color: #515151; - background-color: #fff; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; -} - -/* No `:visited` state is required by default (browsers will use `a`) */ -a { - color: #268bd2; - text-decoration: none; -} -/* `:focus` is linked to `:hover` for basic accessibility */ -a:hover, -a:focus { - text-decoration: underline; -} - -/* Headings */ -h1, h2, h3, h4, h5, h6 { - margin-bottom: .5rem; - font-weight: bold; - line-height: 1.25; - color: #313131; - text-rendering: optimizeLegibility; -} -h1 { - font-size: 2rem; -} -h2 { - margin-top: 1rem; - font-size: 1.5rem; -} -h3 { - margin-top: 1.5rem; - font-size: 1.25rem; -} -h4, h5, h6 { - margin-top: 1rem; - font-size: 1rem; -} - -/* Body text */ -p { - margin-top: 0; - margin-bottom: 1rem; -} - -strong { - color: #303030; -} - - -/* Lists */ -ul, ol, dl { - margin-top: 0; - margin-bottom: 1rem; -} - -dt { - font-weight: bold; -} -dd { - margin-bottom: .5rem; -} - -/* Misc */ -hr { - position: relative; - margin: 1.5rem 0; - border: 0; - border-top: 1px solid #eee; - border-bottom: 1px solid #fff; -} - -abbr { - font-size: 85%; - font-weight: bold; - color: #555; - text-transform: uppercase; -} -abbr[title] { - cursor: help; - border-bottom: 1px dotted #e5e5e5; -} - -/* Code */ -code, -pre { - font-family: Menlo, Monaco, "Courier New", monospace; -} -code { - padding: .25em .5em; - font-size: 85%; - color: #bf616a; - background-color: #f9f9f9; - border-radius: 3px; -} -pre { - display: block; - margin-top: 0; - margin-bottom: 1rem; - padding: 1rem; - font-size: .8rem; - line-height: 1.4; - white-space: pre; - white-space: pre-wrap; - word-break: break-all; - word-wrap: break-word; - background-color: #f9f9f9; -} -pre code { - padding: 0; - font-size: 100%; - color: inherit; - background-color: transparent; -} -.highlight { - margin-bottom: 1rem; - border-radius: 4px; -} -.highlight pre { - margin-bottom: 0; -} - -/* Quotes */ -blockquote { - padding: .5rem 1rem; - margin: .8rem 0; - color: #7a7a7a; - border-left: .25rem solid #e5e5e5; -} -blockquote p:last-child { - margin-bottom: 0; -} -@media (min-width: 30em) { - blockquote { - padding-right: 5rem; - padding-left: 1.25rem; - } -} - -img { - display: block; - margin: 0 0 1rem; - border-radius: 5px; - max-width: 100%; -} - -/* Tables */ -table { - margin-bottom: 1rem; - width: 100%; - border: 1px solid #e5e5e5; - border-collapse: collapse; -} -td, -th { - padding: .25rem .5rem; - border: 1px solid #e5e5e5; -} -tbody tr:nth-child(odd) td, -tbody tr:nth-child(odd) th { - background-color: #f9f9f9; -} - - -/* - * Custom type - * - * Extend paragraphs with `.lead` for larger introductory text. - */ - -.lead { - font-size: 1.25rem; - font-weight: 300; -} - - -/* - * Messages - * - * Show alert messages to users. You may add it to single elements like a `

`, - * or to a parent if there are multiple elements to show. - */ - -.message { - margin-bottom: 1rem; - padding: 1rem; - color: #717171; - background-color: #f9f9f9; -} - - -/* - * Masthead - * - * Super small header above the content for site name and short description. - */ - -.masthead { - padding-top: 1rem; - padding-bottom: 1rem; - margin-bottom: 3rem; -} -.masthead-title { - margin-top: 0; - margin-bottom: 0; - color: #505050; -} -.masthead-title a { - color: #505050; -} -.masthead-title small { - font-size: 75%; - font-weight: 400; - color: #c0c0c0; - letter-spacing: 0; -} - - -/* Meta data line below post title */ -.post-date { - display: block; - margin-top: -.5rem; - margin-bottom: 1rem; - color: #9a9a9a; -} diff --git a/website/static/css/syntax.css b/website/static/css/syntax.css deleted file mode 100644 index 1264b87..0000000 --- a/website/static/css/syntax.css +++ /dev/null @@ -1,66 +0,0 @@ -.hll { background-color: #ffffcc } - /*{ background: #f0f3f3; }*/ -.c { color: #999; } /* Comment */ -.err { color: #AA0000; background-color: #FFAAAA } /* Error */ -.k { color: #006699; } /* Keyword */ -.o { color: #555555 } /* Operator */ -.cm { color: #0099FF; font-style: italic } /* Comment.Multiline */ -.cp { color: #009999 } /* Comment.Preproc */ -.c1 { color: #999; } /* Comment.Single */ -.cs { color: #999; } /* Comment.Special */ -.gd { background-color: #FFCCCC; border: 1px solid #CC0000 } /* Generic.Deleted */ -.ge { font-style: italic } /* Generic.Emph */ -.gr { color: #FF0000 } /* Generic.Error */ -.gh { color: #003300; } /* Generic.Heading */ -.gi { background-color: #CCFFCC; border: 1px solid #00CC00 } /* Generic.Inserted */ -.go { color: #AAAAAA } /* Generic.Output */ -.gp { color: #000099; } /* Generic.Prompt */ -.gs { } /* Generic.Strong */ -.gu { color: #003300; } /* Generic.Subheading */ -.gt { color: #99CC66 } /* Generic.Traceback */ -.kc { color: #006699; } /* Keyword.Constant */ -.kd { color: #006699; } /* Keyword.Declaration */ -.kn { color: #006699; } /* Keyword.Namespace */ -.kp { color: #006699 } /* Keyword.Pseudo */ -.kr { color: #006699; } /* Keyword.Reserved */ -.kt { color: #007788; } /* Keyword.Type */ -.m { color: #FF6600 } /* Literal.Number */ -.s { color: #d44950 } /* Literal.String */ -.na { color: #4f9fcf } /* Name.Attribute */ -.nb { color: #336666 } /* Name.Builtin */ -.nc { color: #00AA88; } /* Name.Class */ -.no { color: #336600 } /* Name.Constant */ -.nd { color: #9999FF } /* Name.Decorator */ -.ni { color: #999999; } /* Name.Entity */ -.ne { color: #CC0000; } /* Name.Exception */ -.nf { color: #CC00FF } /* Name.Function */ -.nl { color: #9999FF } /* Name.Label */ -.nn { color: #00CCFF; } /* Name.Namespace */ -.nt { color: #2f6f9f; } /* Name.Tag */ -.nv { color: #003333 } /* Name.Variable */ -.ow { color: #000000; } /* Operator.Word */ -.w { color: #bbbbbb } /* Text.Whitespace */ -.mf { color: #FF6600 } /* Literal.Number.Float */ -.mh { color: #FF6600 } /* Literal.Number.Hex */ -.mi { color: #FF6600 } /* Literal.Number.Integer */ -.mo { color: #FF6600 } /* Literal.Number.Oct */ -.sb { color: #CC3300 } /* Literal.String.Backtick */ -.sc { color: #CC3300 } /* Literal.String.Char */ -.sd { color: #CC3300; font-style: italic } /* Literal.String.Doc */ -.s2 { color: #CC3300 } /* Literal.String.Double */ -.se { color: #CC3300; } /* Literal.String.Escape */ -.sh { color: #CC3300 } /* Literal.String.Heredoc */ -.si { color: #AA0000 } /* Literal.String.Interpol */ -.sx { color: #CC3300 } /* Literal.String.Other */ -.sr { color: #33AAAA } /* Literal.String.Regex */ -.s1 { color: #CC3300 } /* Literal.String.Single */ -.ss { color: #FFCC33 } /* Literal.String.Symbol */ -.bp { color: #336666 } /* Name.Builtin.Pseudo */ -.vc { color: #003333 } /* Name.Variable.Class */ -.vg { color: #003333 } /* Name.Variable.Global */ -.vi { color: #003333 } /* Name.Variable.Instance */ -.il { color: #FF6600 } /* Literal.Number.Integer.Long */ - -.css .o, -.css .o + .nt, -.css .nt + .nt { color: #999; } diff --git a/website/static/css/theme.css b/website/static/css/theme.css deleted file mode 100644 index af44672..0000000 --- a/website/static/css/theme.css +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Originally based on the Hyde theme, but heavily modified - # and who knows what original code remains. - * - * Designed, built, and released under MIT license by @mdo. Learn more at - * https://github.com/poole/hyde. - */ - - -/* - * Global resets - * - * Update the foundational and global aspects of the page. - */ - -html { - font-family: "PT Sans", Helvetica, Arial, sans-serif; - font-size: 20px; -} - -@media (max-width: 48em) { - .main { - font-size: 16px; - } -} - -/* SECTIONS ============================================================================= */ - -.section { - clear: both; - padding: 0px; - margin: 0px; -} - -/* GROUPING ============================================================================= */ - - -.group:before, -.group:after { - content:""; - display:table; -} -.group:after { - clear:both; -} -.group { - zoom:1; /* For IE 6/7 (trigger hasLayout) */ -} - -/* GRID COLUMN SETUP ==================================================================== */ - -.col { - display: block; - float:left; - margin: 1% 0 1% 1.6%; -} - -.col:first-child { margin-left: 0; } /* all browsers except IE6 and lower */ - - -/* REMOVE MARGINS AS ALL GO FULL WIDTH AT 600 PIXELS */ - -@media only screen and (max-width: 600px) { - .col { - margin: 1% 0 1% 0%; - } -} - -/* GRID OF THREE ============================================================================= */ - - -.span_3_of_3 { - width: 100%; -} - -.span_2_of_3 { - width: 66.13%; -} - -.span_1_of_3 { - width: 32.26%; -} - - -/* GO FULL WIDTH AT LESS THAN 600 PIXELS */ - -@media only screen and (max-width: 600px) { - .span_3_of_3 { - width: 100%; - } - .span_2_of_3 { - width: 100%; - } - .span_1_of_3 { - width: 100%; - } -} - -/* GRID OF TWELVE ============================================================================= */ - -.span_12_of_12 { - width: 100%; -} - -.span_11_of_12 { - width: 91.53%; -} - -.span_10_of_12 { - width: 83.06%; -} - -.span_9_of_12 { - width: 74.6%; -} - -.span_8_of_12 { - width: 66.13%; -} - -.span_7_of_12 { - width: 57.66%; -} - -.span_6_of_12 { - width: 49.2%; -} - -.span_5_of_12 { - width: 40.73%; -} - -.span_4_of_12 { - width: 32.26%; -} - -.span_3_of_12 { - width: 23.8%; -} - -.span_2_of_12 { - width: 15.33%; -} - -.span_1_of_12 { - width: 6.86%; -} - - -/* GO FULL WIDTH AT LESS THAN 600 PIXELS */ - -@media only screen and (max-width: 600px) { - .span_12_of_12 { - width: 100%; - } - .span_11_of_12 { - width: 100%; - } - .span_10_of_12 { - width: 100%; - } - .span_9_of_12 { - width: 100%; - } - .span_8_of_12 { - width: 100%; - } - .span_7_of_12 { - width: 100%; - } - .span_6_of_12 { - width: 100%; - } - .span_5_of_12 { - width: 100%; - } - .span_4_of_12 { - width: 100%; - } - .span_3_of_12 { - width: 100%; - } - .span_2_of_12 { - width: 100%; - } - .span_1_of_12 { - width: 100%; - } -} - - -/* - * Sidebar - * - * Flexible banner for housing site name, intro, and "footer" content. Starts - * out above content in mobile and later moves to the side with wider viewports. - */ - -.sidebar { - padding: 2rem; - padding-right: 0; - color: rgba(255,255,255,.5); - font-size: 1rem; -} - -.sidebar-nav { - padding-left: 0; - list-style: none; -} -.sidebar-nav-item { - display: block; -} -a.sidebar-nav-item:hover, -a.sidebar-nav-item:focus { - text-decoration: underline; -} -.sidebar-nav-item.active { - font-weight: bold; -} -.sidebar-nav-nested { - padding-left: 1rem; - margin-bottom: 0; -} diff --git a/website/static/sifter_example.png b/website/static/sifter_example.png deleted file mode 100644 index 284e0ddd67070af27f377bb207c7b7dabf78e80e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124447 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%0Ark#4x8mwj*}3#`CSD8i7)x$9^)#7#prm^ z6s`AG$8c+SXxe6w`LoL2_m2|`0V;&WsQFlzV&oZ&X~Z%+*y`k@WH$swE^zUlBAkZ%(B3evIId)(~FMAJXXh>l=nq=DTrMB`}S> zD4~e6_Nz*$dW+5NX!sQ7bvFBBKr*~fLHGG}XV9d_zQ9KoI-K#qy+x1=C3j>zIzD4W zxATB%t1WNpK+CyH2Z+f=z~+O8PrTXySp!GLk3OLHsNy7@;4W@h@Vg?O0i>*++HJjY zVwtY-OiC!*d<+C0C#khSk@1+iap@uEWPwR(CbyF}bl93=y!tGse#ThXXT7JKG=;J3 zh1=eGP~sp~;4!L$mJevy>s)e{rujZ8mNDr6YIbuLA<{Hb4I`6sH4nHoBxE+}cIsnX z2}M-5htPa4Get>SxKhAGKgWES`Q(b0=R_X~#7U-9jqc|c0`Dg25f``q-ZI1Eq~Jb( zqgPbY(%(fz#4rI^m_Yi~%IAdwhon%rltW5qu#)rzqlO*YFN+T@62Wkjm%Fo;j+L?A zrJl@W@)Rb-lPEkzwn&S8X;E04{ksU4-E}T<&*Z(?McW3{tK$ouCy3#kJZQA}o-@e) z76>G}76*_YZ7S>KnOXZIGFwrKv=e}DKgB*2y5?Ft>NxeSSj#IQzffVeY`7*(s;Uy% zJ~J>majat9H&`!Vzvh9?qG~t}6!zB_*@EWQwk8qg$^CYP;#7R z0PUZGEuaWn=S1QJ-4>K=8jVzDglltO_2iX9vklqV^o*6lUP29~&2X72xfTa;o)$G|MO{DX&8A?0YZ0cWsIYoXcR`$a4Bsk=L1yK3mUyTLwIVTOVGkkkko6 zRL$VlC5+v(-t*Q>qHA@7ZybqgAoyrVAVAUBco7d|#x()WV*HDl+O4+LGcq;yg6Ey( zyO3qyET}fH$6zU$x-?zkK+A}(YP%?_qR26&)`y);S$D3mdv?;Qx&vPQTuv~j*sXBy zqw4^Tg_;IT?zZMW-qp5*7cgh}I$s<1sl~b2y^U9_1*HIAqXY<}!K1--JIu@gwHY+(l&^Qf^oYF3bMoFP zBg1pzfbR>51$3Vr))J(W?(Va?9tV--nbIa#0z>zpp$xSfhgAnJSe~;JXEC9~K3aHx z!pcM(3|=fIUw<2SrXgLmQAH%qP9cy(%kYe{kHhopMXe_0}){H;;~D5%?XpuT*YAP1N8K9Uz&)) zXU=Ic`NTH0daR4z*56Rf28~BVDlBhg73nYe(aePg(KJ&Kg;%6`)`*MO@{1rev zudI4ND;>(M@NYhM=#WS6JD4&)sYQr@^%0BA;|Igz;X0P@w!UeGYsu3+OBUp@^rWXz z6!RQ$!r7H87(TGO48c#lmfnua{Ato+`>iN~s#}Q8$6%@FofeUATSHRWM=89^Cro#Jf+G7vIlo}Lljyn|o!n&k%w%ut&M~UNu*qax(>zXk zwE(w$HyzJA)~n-w9G5ZXF6f{>@#c*hW`?ty^flNGGL5qd8g@DY-dsl4D-N}1wz|vT zhkuzk>B!Ied751vw$9Gg}%bjvk#pyo7y4U}EpN3vvp9>z-bL@N#gYjQ=|xz5`%ELM_B>OE2Qij&n4_f456Bwu41_Q7xpe@>s8r{RjRG!LC$1S69B{*MheOiPGm*3>Lx{8kxaQ-{#b~VcL>Hkqb98PAwt#bi{ zfFSv~y!buAB%GqqAVKg6r3_d3nc9$pZ)Z|*3#F5n-_&i5db&v?ApPSj05xSZB<6s zy!$CuRyB$J+V7N3`03k2V%0x5f~@v(ISpnF``82YW|~)r%hw7$mcB%0k7!hGmdtPo5JRYOjD za4-W<*Z6gHiUDlU7~gL?D;ZIb(FQPBW90LTE38lZ^~PT|q8H&|Nf*C#1oTSkmc#+f zqXKCTK3YBm`l#V=K8~D9EzTW3$8D65CU0jW#j(!ex-dl+{nE4sUgwK#I0q9W%maX= zCvQuDfVc}lo1kAR)ql2EAScM z(MM9ilSgf-puU8c0$)ZBeHV(S66Q_|0O&)xq_Q$}(u1PGoZ2eeXg46(f?Pwlto^Ev zl^%MQpEvE;cL7ORRM$kJhetmg>L`0EzJ09vpxE@<<7lu>g9GJ09=jkCP-NK5LsO9_ z8$@>+Pp#25jW9qE`XH#*<(BH9O}?JnguuP|;sV$CJor{EAUU&L)h2}16^y*Fe{8hd z0QI!G+c@cB;ss~YUK#=2=fDMoVLQCnJ6~?l3e#;FZqDL1pzPL}smZufsIh}*EUq?Q zQ8A~K0E(aAYSUfmoHR@ZNLEa!Y4q}3hUqL;AE<8>P}>_UO!_I@SMv*)E>$K z0y>Kfb59eut&?&aqT#V($xV$>-5CKxBGgHPQz42r`p}%Gv|u)bCZ()S?Io~^;Yjd5 zEa>Z^wk;sn^!{Z8ZXvaeiERlQsX^kP_q2F79t%uPJh^sK0P*LVZ6Y|ao6W-x{vIkUNL8I} zJ415y@Bpj-qO+d0SWwapV*_gFpz166V$1ay^qLdC*ek*^{H=V==Cx%jRUDvK%W`_D zq0385`Nb4~?@a;en{hwh(=M+(m^E4eu#Lh*aOr3gIlzSv-JiXrK)M~KUSSGQgNC6zG=WXmU2VNZzOQY#H9u)ahIE z6>#dX~*ySC+u2tTl@4gL(RiA2w{;$j4E+a&p zy}auBt_14K=iH)capjKnR}|mWt*8K(VjE!PnEi=IzE0(MmbSQK#1%x%YYfp>Tc@^K5t?gdOb@TS!iW4r zos}e(x}`$RZW|GE>wD-mCQl#6R2EQ%^T^1l)|FoCSntSy0sif#VdY40ep)mNUNFxO zRULd7RIBLC`W2@>)&o8T^)1$R?t*|C&*=eGeIl?Otwrk0<0D3%mZ0ANC_y?Z1~Jn+ z@njInKyA7Fo)@k;)KET0G!jnT%1MNg77+0LK9kV3S*k3 zM#?cZuG%@K%Y5yWX@Fa6T{G5nA!3NJxnvp8RZLB#x#>+jor~?Q1PPu<(GAj+)|pZj zcwmwN0ah_U*OmcEOb=jExQN|}kkFQ5BtZ1BkbZaQaIa}EN=y#q0tm*w+Kf;DPZHh< zta`~;_EFI`5^O+619Icm87MN>qcpcTkBk*m5h~SwFZhX^-DPswgBvv78Vya-H?`7G znlgr8PSl9r|KHbnGh%k4I>Kfpn|2~V>TT>Ofl%>Gf{hCZ1(2vN1Nbn=HVvqLBV?l< zGtgA3ncP7*peKFYE`=ju2`JD^fy$QfwoyiDm7=mm;FZEUxW$w(L;3d=U?6bMh;)m( zxx{wEP;tQOYS4v?j~8GRg5QdL$?Vcbtcv*pU*W1w=L|B^$r}3rhMkigPKE?DQ8Ipb z&_;C~WTGnr6!~B|br1(n26Gvcj8rq1_C9Ggx)PuQA7N(qLxHu|#*`kJPVAq|5Y>$% zched#)}Dx`Ml6gO<1}}6Coheh?lWbp*VbtnMx!!S`tuyvAt!~88CH!BR{@l~{W|dO zXF;m-JxQC^c+{1lO6yMf4@r>%-$0007?RIso5WcU=(uhxL=w zXz)v64RJ8Ome}XDS4!1eh67X;du+mm0WK%*dg<1yzEUb;2J!q#uCZTzz5`G{GkD+E zzjiQiT2yn|I=ww;G|u-sD+{wV5Fa*qSP7ER@jvZz)mZ7}(niD7e+%Y=nX|{WoL33`Bh05?3jYZK2EVsPVHa^ulV>sVes4{WfxViIiPfVZyM26lOU}VbgiEw<$QgzT)$?%jkKpDA5BVQ#GBG_-<5CoF< zm6B?D8G|FHagkKDL~(^m!@Z5Sw0g4fVV@5LHzqPJ+wMj*QURnKG)dJa+3Ikrhv=gJ zq$Z+c6xhP^4=Vu3KVWc5d!~j_*>3KqvEtpjFEgU4rIjs*+CZ37E>VZmmNeWd26mX0 zsTWOIfo>XV=}4NK2ck-V-Cr=z(-xTR;thZ`m;eH7-0w3g0nZ(C+lYoMsO89GzWX(x zkytE%z~QJXf=qj_YVRR)wuY9RT!hm78C!n-MB_EK|A^@T%@xlMPshaU)4DPedsJ>x z(kB86ri$e-^i3YYby+|6&1fEwcU2ydnauc=z%m}4OO^4lPu+?73#fNiPrNjg6uPLHP)cSPu_#V=J zTV0T)IP1_y>8?!i=a$!iS&W9{ofZN-7_k$o@@zM3WXUY92*2pq`Q36xl(3hdZPBDL z_w=Qezir68=C5n{6?iz(GHg8{=bG&vKo^muKvcBW*n%KHQ3TcT0RCUPq$HXYuQ&j% z?1qWk@<2m<1oA;{tx!b7#J3pxLn8KExW{IabVz6(? zG+AK6Wh(EPVB7z;3iTCfjoB-~SiTvlJTBSrvzasD+fVY%YZ{wAA8vgJ=X7b@!IK}B z^<=r-I8#^q8?qam8NB#e>3b7D?$20Y`0im7y6AgaIB>8+*8=!Fz^j^mL1+|x)1D7n}ufTB_{$kVA8c3@W|3n|GVjfqB5tJLnML` ztmC@ZN?>6t7H6UkphJ&)5{?|V8Z7Q^DC=mL3kSk{Ra1~I>#*%1YedDSj) zA%6>)a{ok{nf5JcRNKRWJ%r8$I+Yz8lW{5ImXl1Ff;^kzt8sx4gdP;`?h8a)E!EGp zm6g^mi9Z8-989z0K=$exSuVxp6-AZM;o&t|Z6ob^Q>>HL+S)d_VK2 ztY0ge(#=2Sbg1ZflOp;NSda^?s_6d)9lz&V=)}PDv##kICb0mJZbK2J?ZXHdNX9v< zC*ll$^E*Mm0xVSPo9bM*GX7cN1|%5l&l>_WP1pFrsupmAz;a zKA&&{>544ABIC8UQYZ>a_n48-cl?w9K?^WF^xa8G!ltmEV*U5ISE+}~WscO-i&w`WOjzw-HU zN1RPWwc^DCrtU;2L}z9gn+UDRaa(Sat^4ju!aW;zm{Z_#(4TkHuKZsz?}O|9h2S=l zGn;kWtY!hzD=#+BqSwQZ$DczMdB4c0clK?xemwjvo9@JRI0cOt*94In32OWa$IxWMm6Yp))$$W3Y^j(v$`aOnLe zV(MQ&R{O>L(knj|Br0fP0`k(F?>D`AVY71}u@~x1)iL@#m7%UtaYVTMO*_x$d3@Qu z)3KwC{P?CA9-q=?=?A05MhElYL9rc<_VCMp)mv?)Ii1{tTUCv|M&}Hk#J+DL@>~fP@ z|F|!tP1bUX?E1Iv5#yg5&VrBy7SA6f656PTy|xOM-bBwGE;QrC&dCUDJww+TzEX;s zh0f+t)>DkG+%YDMrxc4Am_6FcElyUdj$;)!>z#~JHSxd@yQ_)UKy6v>?5%N-f(u$k zQ1=IvhxnDYe?X;qRbk~UE+pWKN9K;Ll#>SY`g!}Sy8kLyj-wJ5GuB*R+(&e7cPPT4m z82W{k{cP{>kS&_-XQDs(cDN&RF)#DG;TJQ7O569Hat+@`fW=j2wzI^lZ@e|-AAWhq z^2L~Sf?Jlt*4Z>$cle*HcNdpeiOG+u7xkJACw}RFng{G@ z7SS|lip$rohi9fFEBoObZ}j6EqYKFK?#*~O^h4z7bXGCad1Sib0P2hkh^n=Hf0$UX zcaQ$+cuLs}QisaFhpv7r!+F1WCZMLEIvW1xANe2;dAj9QU1FRr=>9%U0e|_3^jx>t ze}8T(lILzw!!amms(UzNU-0T{VF!L7OyKkD`ptnTcR}+X1Szjc@l|!@fJF=m(>Y5ww^QnjhzU%KxpWn12)L~A&av6(cag3AE?W{ov z`V4a-JOc7LnV2%Y_KDrg1EWuvepIMQ`8X_NY_0$&|=JEv~ zM(vqq`U7jVTAuT%sv|C{8IGksH~2!uyw_&&b@;>+P=RxdC~3^-UhbN3gp|hDP3K21 z?v{+}g5JUN&V}8xItx=Y%QdDTt(3J)Z&OeGVcfYO>5X5e1k`7@Q$A6@(YgPKTHri+ z#GX*qVW7P<=iRjXJC}{nc(?o1XAi7Y&hx_+H?z!245{~DYDQ_6|ET>-q`vJ_ZsJR; zkryw*geyE1LLl${oXezQ21|AaZJzA&_Zi;~H5g+yxjy)g@f_koi_XZpr^4y$8bO3Q z9n-)9?K7MOmIkRJ9(6TyukfXOuU`|T14ETZ+2L{ox&z%vOKdCO8(G`cZrbPeN22ld z+b+3OUkFCby}V(Qai4#$dMS(?jeHW^a_D9*g=`o+cU@y?e<$&C|7hOdmr?}Fc2__` zHi2CwBNT{4hWlM!(iS}=etqclY(n0EOY13jKj9HC=hfRXLpoy4^NeCn2X5Q8W9J#U zfztm79Y!nNJ@R(@i!5LErocPx(O!Wa;a)-W>E1E01S4(!h^9W3-vl2lLzWssEC`+) z5#z^s1y<%2VPd4B&=Q)w=pC;S|PY99T* zc$!gshO%7mMyk-|xa0AnJ+s;nS(LSuqM9Y|!{#EZBRuaJ1j`?`hkF%8iE7u5d;Pxk z#P!(Aco(Y@mUbrIJr&V1$0jd#Olg^)qhxT*hKfk1`Jjy^x(e&WMrsG$E3ZX^ZOI=V z{G1~re}HW+@5yE>d#H?6xL!2BZLQ8(kSGU?E4TSR|ap#>9@(Q8$r zvOf;%W{UmR&A+i1*}&wQIwgP7%iC`8cI1WVvVolE#*3PVvxBVD^TwA!w+7BcLlW;B zolJ9=?(}+nY_EHhmxJBHQ&00e`m8iku;KN@Mab` zzM-8Ycx>+bUg|(dKT^%#Cv(k!(-vy(__DY;#J#8#Kbi~6=83PSouTWw^BbPVh4#W^)lLCDrv zx$r8g5~cq+OM|!}vS5pm+*CCN?`j=qX+%F0qc^f+_rvh=a%ER%S`ung!MShR0zQU1 zs7^ert&B)KL_G?0(hb);K?ytW(LC5M=@jw$yFwsN+;w^^9+JMveeIsFY%37{FJ%ME z8(2{6T)!{N7xby>15<;4{7#n+KRCBFmv3phkd%?KEANf4vTDrvwWwj0fyPID9m-c0 z_C)o>e#O2b87_bGe!!;3A|*$V$U>s$F4yu@==yu1XT1)edmg_W;rj4bs7Ka`L>6tx zNrK70!*Vq$EM>GI`Z1*PR-9V@j8a+C8`hj#VaDTg!OSk$Gpm^8p1}Ni+=ibifanul z&qy8ASmiSjMt&O+7`ZHvJrHePWF>#L#D7~LFlu>Ve4#%<xjX%bwP99FxjVH}<|kTejsbN%}A^J!kR|zZ2`XsjS6LQG+7;K2Z6{7lhOf z#pxqJXl~4_Jj`6FiBXH3UpKYsAnD&|`qi!=w7YwWFxq&ZLO-GwbaqC=p)3|&ceWYB z6WkgvY=t2f5%(VaiF>4Zm+uYQ()xL7$J{D-gjH8wBlt0Va7gutxi*T1P1a0W_z9Ex zl)%OCvnNYq+XNMi7t1$HyuMI+6B=Lm+_BRbqjNOsjc>ONJIK0&M=T`d^E*oQh~gxY zg#G;j>v*@&nc{iqnW{nXA1iX^gV+yjUvLr7oKdy}HfFR9o{l$WG23D%&_U$)^X9Bl zJsw3(>vPs{wT+c4wvTmIDvL(|K`>JeVcvh)9pA*{!Ckq1pxJALT`{){%u^k6ps^ai z2r5GuZ?h!KZ9mK?w73H{xV-048Lr4=0c~%suGJy-aK8_fNmwm2X@4H`1xaJ<>q+r`$BQtpc5%Liy&;Hm>X~s~r(emjtv*#WM=+0*}L%GvO z!L*do0=~g&lEXKbvuqTGcuqs)G2J4{AAkP})Is-C7fJ6O@vcZl@&zy|G<*%|S6k8j za?5|;s5nTER%ff0L2t+T}~{vqi1a)~@R!5F8o%vmBSSwS@9**8Ri7~h`_*Tw&ad>zNPoW0vQ7wO^qT!Sgp@5n#~D}KDJQS zw=jwx5;LxD+SK>0n0g(?7u_HzAo%2E-`Jau7q-kaEA&_B+3slR`%#*ne@7)Fg$$M` zE`vJU8Aa4D@0?3^Xz|4jCF60R5Wac_AJ2bEK00Tg3L8>%p|JE*dCz}OKyviekAeZw z{jf_SAYbRmt04Bq2=&^$%`a}{k6-vPjikDBzutQ?eRi8H$1`+3TdC?iYXu_|y4ARY z+UGab4L4cj`WQOZkeVuuow_O0(ZT8VlmxENWnif2$9RE{JPYkaJf2%cmQ=#0eM^{V!efFD)rCIJ{X@_32pZ+Zd zFhD}AG#SIE3@*5#w4j1XkeGCr3-5BeZ}9W6s6m0Mpwnb^V59D7FZLnE#VKJtauuXs zX#D>5s;`uH(THLMIlZ`U{M#nJ03`RUD=~w8>!%LC=`;p*Z@LfxL6D?RL7y=P;Um@;^z$NHYUo zxHwgfR3y-nL_OoNZR-nsH|ZgerMJ76Ts*_uwaig%ibL~w=o8AosyQ=a@H;L)6u!^2 zwYAH^YFYdzX_VFuo$kfvuO_`W;NG9sWYzXU_gv;HrMyY^yzr@C{hEG(_a_O_h-Cn6 z0qV^kfA~9I4ISXR)5VZJ&?rtdY%2)zR6mf18=T0hPFeidVWee&&F| ziQChVK((oN4=P=*Js04quKIp-78Rk;AD>6_drg7J!YPI^eY<(|99Sf_Dw$NeDr2pKj$2cq?*_6U;KC=Q&w^{-le3ygGD+2T<;wx4iUT3g4?X3kC{cyP8cc{ZB zWAXA!xKPxpKTzNg-HY?g?|4?DY`ogNH3YwCcxVPr_PjBfwGdx_L?zC${Cq?cFRu7D zqX)~*Z29GF6*_G=P$G(z z^vN;Ji#j&gS96`h+h0h3xYsgWVP1P%wQp@PV-G=C^jR(j9(ItwXgIg zU6MZzNd2`vx0U-dpQ_F$Sj~V=^+m|J`X1%<*()`1V8L1f6MuO1G9)kuM`<5l$Yq;4 z+x9(C{k0Uz5Bc6e`aqDjd3X$Eyw*D~DB^UhDM^5U`s4w-${B6E#3{ZKI%XwRR2O(S z=-$%Qj4})XX&a;EHqbdmS6>i4CVwJ+Ae`sMy_Qf{)F$|D%waaZDT0KZQdapmCgIKN z^CuE#HvIn4IbN2V529b9cV@;fsP#A3UW+gO!%W5JJI6jHi+(5^5({j6E2tku zsCKp^A2yv;i&s$?%G08jX|u>bqxUbBOnJf|6$9H3n&;TZG$#v~MnFmK;9ipib_2DS zOI297ha?(tKHMwxG!KohvEuQ*11RMq{@nEeKSVIrsH7nEP>by_gZ0fH=dGpex%d2d%ZPcX!3JBFvP4<77vgkW()?p^=TK=D=eJSlZBt_T6B<`20miOC?&}sY3 zK>VoEMh;gFlzaF?M$X9MEnfV$iAnD>U9|)w(rvH(so`_TY7_%dFOf9xg-zi1ysE3m zbr>kLz0#@BJ<)R@m-jZI0;AJdjf*D*bafhvh>( z*ar-fjTCx?RCx!>{0}GySF{RjM(SMc;C%E4XQWR|)!WZD6y!B41I7@bvWOegw95I$ zB94a5&#Rht$O^wJOmiMYRs2Ac!QB?`!rwDfE`-mC_u}O73;P}bk5tLOQ-} z3OWa`N5f~wc4Ol$BO`bUpR+ujHAd;g-)Guuj5Cy%yf-i%p%B;5zGbeop^Sp0X3Gn8 z=SR_S4J#SE*z4|fBJb?vY@96mX^1&lTjXDESS?smn0)mo9|bn8SWwruNpQw*8a$Hv zXd$KaVP%4KzPQMu>&~T2Dv#Y5PV!@gqoGGfarb^xuldy2jv7^ZpJeet^_g{a{Ent7vZtcYh+e)&|pp-&^D)bmWqTgAF| z27^BznL#3HxcoqGr>(3yIdTU(lSfhbG>_y~dNbw!#TYS=|46Z}I?YDTXuFlt%nLF~ z*}+p)n=qF9+GJ4mL+}wkyG|-M=ZAlp>rn%`E_z4E&7lr-Mc4zY2z`UUYRez>pNQ1M zDOEII*308&^&13KyL2C>=N4bXY=As2TrbDUP2GOh%XrMC6ZIR;PxgbQpwe9w>=H)^CkX`fh#9Mc;lga|aH0 zN|5k4?(Ad_^!iCVWFATq9W=uw&wbIo)?XvU?~=nzEF|`g&x0bHSnRpqkrhTTY&I~k zQfaUPmxk))dRr`KhUTYG{Z$)zmGq!*cnM$JUS{|kVV^qjI@;fSw5?NthEzsK!n#focyA85c8)BT(WU3xhMQVTenay=_X8(eJ zPa^}}KSuarp1ZhdW-ML)?R!oS(zIIG4>W0UQIp_U*ijfc5MJk~H)IYv6dX5_WtXRu zIXf>7hD0BWPx`fK8>GJN=zaG3as8w|f3UsRWUn0J`1M-Ow8|8y5ns+D`A@c*9+2PC zkELNBR?=#fk^?%NnejYTiPu@9gqjJDE0QU>U0l8vp}*=TBYE8C9HQIT`>goho$O*^ zk9pyLtt{-duRGRXikAG+Q;EXuC!9=>e^6cD6C8YBl8I;4hf=~9Mn zsiBds8IY0&=?3W>P`W#$LAo2HzsvXi9XCABdwhR#3=Df;*WTy3&b8LLw{Ol2%ux}x zH(FX_=Qyjkzn{C?n5WcQW$YX&mZ?;!$T8$q} z@MNSmIE=T(b(vO>U*&`Fws?@7@4sAa_srKqRG6S$oO~XgJYF~Qd`Pvq(BNs(P?r*m z-Q}zC>tn&{urb#c;*(&^N-aI4d-pL z%3HZ^AMN(}*#va|0`C(Q{7bIa{)VdzPK$+sZyfWGW%KJ@PNu2QXs#`Wlm?uWW45@4!RFirscT+Gl zWu+QiWWPA%^|^(GKHeo^ljqZ>ge%~lB^a5&M%ml*X;BH9XfH_mq&^v#6tb?~#)0D6s77UC2cT2E{$sgzWizCFc;1i}8Es$P{r^Hl zQaat`9VY_pe+AhT7M%pUT{RvL{~$xv9O9;^s)=cyM_rc|VuLC{vIEH-jBCgtSKVq8 z0otvHSwdR}_1$K>3`&w~%xg{-u%6NiT_=*P+{?Qf8HbNZ0Ax976!o|K+I(8F9oN`= zp~@6zZN>5m`nqqgW&M8?oK>r}*5x+Trxo(4Cv|-E0?H`2mMkJU?Te{<{spyIKU*?T zNJ&(lyhQ)0sp$*9Hd_6IB0}4~xN8b=t-(z3cJh-CYvG#;J~qWlLcFjYA1Cii#n@A< zCyi}nKkA8yU>&e7BkHfKOK$X)kig>RM*pi=CNnv=#)S6%mj2VX*6U;Uc>HP9&dabL zv*Xdki8|#xk6|*D!~7iaqjTN=7W4oSLCa@t90N}S0t!HFJ_e}2(Ojtwzx4JO%kO2W z_qLcvmC-e+Gk~2TyIV|ovSO}*9vT|q#7{p$#Za-fjl=pb64D}nsE!VzgVg$$Ejg8Xs1xr-C8 zmcOO7%bFBoHMlEL@Y23QRxqcXrQYFTRF0<)y}jk{(eTP#*X-)Y)O~><$BUdUx2JC_ zOIA8=2>^oZiHy2ei4i^^j2-N<-v*r@Fx10;y5}g8UokuiB8sAhu=K@-OU%?th9|Cl zGJTv@u0oh>pI|>sLs=lE!Uay?8ujj6ApoTMF-iSD!*D%p4U%`Rh4`g->R+6X{`o7R zAK-lj8^r!;e_%)z(svFh{0cv~W;2v(k0m1ryVg&hQ0Zw00zQb{Q)5Ff*Y_Q3i96mI zo%l$^M{r=g|4L9(qcysLN;fP$w+Zs^F2->qL>i(Rb60DJrc7Yy&gKWkqIsngSg~_e z)YOg67HoLoNa$VYGWo|PyM%F*SHE2qZ z0lD(b3x@pV=;czweoK3#9?e372$t|&4D?2%R2s3xuV3EW=|UL%8;dDlZ^l7Uh0Ew_v4IqEx-!O?mVSg| z+<&bc?)Gp6K+wYcTg(wG^aLM!6UvK8hX8==am1Oi)}?}t>*Xk?!nQ)F>&rFcy`RA@ zsSy3@0(~Kvqy}!(D_r{G)}7GM z%9m*Vmb&e|o^ST;uM=_IXK|!Vy% zGlbukXB(>lwg>t1%xoi?kbuANfX+fFzyG|Op;iu_I;cmIm^KHaHDNk31=HC8BkXIt=E zm^)_RSk=e;-q7Ea_~2;UfgZAfR*12=n|R0r)sNqeBhVm{PWX99IQvT+9$#30#B#jB z`)*@?OanurtFP~Um0S+KV68sHnt)$)hpWTl!ghRg!#0HD_l$!SBAa#pwuGR8|gZcyFMz#!PD;BHa zzDjv}!Il}yl{sx`_NUS#l;X<8AKXY%`1Vc@pR~IN6Ud<9?p9#8WKXzM7u(MbL&Buy z58qDoxJ_;w)q8C$jd#wdc@1~;UssjP5)>H8;#79rTliD!K$>K@OOvL@bRl)>|NlQ* zS{?TyjRcYzH1d`td77y6YH5ioJ6-v=YK7F}cdzM~ds{U0$x#ngChEHq-r0O3Zv5cy z!sILD9J@wgfo_CI@p7A8(5GMX+);?Ov&QS1r@|}_nAS)2%0*@Nl9EoW@3Knq7`gd4 zx<}^MqwBYAD^Q1)1KbZKZ&qKVNQI{&>Tg;+ov|l7d5@CjBSNABXt27sG-qLdzE&Sr z1Ce1}j;8J*30mxVOCa80%bK+lOP|u&#t_@He=){}^Ex{LX!|cyS=;^v+(Ay(arGxA znsYi5q2Z3LtED*Wy2}!QYqDqUU0)4enXyLod;7_D(Z!+C`h(F0Vcj&R%G8XMS(wf? z2G?)xC9-c|8+2%TIn(Np`W ziX@SbEp-Wk^zmP`k)M6)RJTjw{73YQv#s^pINsLxziB8@5n7Aq^i53t=5_6{U6M4m zrPFBe{%d$zjq^L$bj6&Fc5SW8pWl6`#Bod_0emO!tU3s6i;~5wMOdleEjlxahd!(> z`Vd7~{^!Uns)+TN+ABOV^Wq~K^a}MZDYFNzAO01W05=fyv>V>V zSCopjsb1;s6W6j~-_*LCml}5_v52Zl{!zBi9K+`)TEJz8}YS9T4&4$s}KD8Q*!1YrncIf80?8z@s|_u5bqRR2Uw+H8 z7Jd@n&G7C5C7hn zviXz+avIU{(+w$KbH9B|5k5eEaGDMByS7bm3WjlEX%K$SNQX=?cVF-a=9sRjwv&a7 z8@MkpqN2L0G|m^zAF%*}K+5%>Wyq78C5bWo&AC}t6~Hn2ucr;5&bum|BCBel$98V4 zE*Bj@QTMHI2Y<>$e;h{vP&;*H=HGo95k~ftAmhneEua&N_XTo7^=jSP#s}c! zUP2#^P_|pXtfI8+H5$c#SKH)Rm{3QMKe(=^)}K7_@&*hUSeg&Ny-I#dtVai-i@Yez zyHvl!W%Yyt!L2(b38M2CTz{=xibJ1X)v6xFai37Oh6X53IpHMRmymxk6_?u+@729< zJ1%Yv-7b6n7t`ztgioS1K|UP`gM&3EnVdJe2W)Q!>uu-B9aVbugoQ~x57nNijMENy@gbRRY z=iEjdg;3S$Lv)Sy;eA~s+4x+LrMpY>+vs%BWZ zlINRrWFPiZpb=_IyI@DTcsm%)=Y!EM)V$b!M86Z|LCa-uKF9F-Pg{M=YJ5&r+OExA zzTP%JHbkW0bAsHZUT-F_^Uy#e9bu7G^d)xI^~r^kQCqOEdbH}PH42pr`}xV`V8e+{ zNe(>!CoU)fDv04^Dq~rvUYsuG_{jd+YfY*E`QAjaCBq|Jxv^*qqiWgO@zm4?wJ-wT zY%6$XYVyrWTm{ynYL8^n9tG0+lR915xqc0~)R#w=PBI^nssyV90oO76E;13jvb%Kz zr$pGBvjP-v?Fj7E)gp6@-s1PU?cYOtB}134G0YHi7DNb0Z{xr~u0@vT?6XvF*$Mv1 zhdZ8CB;)M0G<-?TFD!jm;%?&q4O~b$GH5N149jsRGuvFPxi(zE`ULK!Pdxzdttop;Nh(8nG;nOL`9!y)y2pdQ`{lg^JG3rk$OMSBKwTO%iRP6<$al&ULdeDHc)Etq zck>8NdW{bDh{Xix&fAOX?|`>Y@z?jT!AAKCz@vmPxh1NLo#(U?l2GfSn=;mGJ)YDz zSl)-R)|~$(I`KgsLAYd>5=>RQ&(*&DH7N;QW0-{Ef{b){IYF*0#pVM1^q!=(9EczgY;s;80I0PAJrweb zxaXI8$d3z5its*)**HP^0?c3dYDck}fMip!wSQbqxi#zCd9f-ChHLIwCftx+9ishL zYS*(qd6+GKE33lbrZzVoq&Lw_q)~Il2R55D!mi^S*kF;6IwA*L$zq{-^`ffgGj(6* z#JH=8qD(wPx_8TgWPlxAK@9nLoe%ob#2H{ShL0qpRAUXwJ}?3GA@Y>ppbgCb7mXnu34eYqaOs+mftKxFzz3#COZNn)@%MwOM^!HB=sJq43*&G?n1 ze}2oM7*>;4l!%V7wOrPZKHe=z06hKLk8?~L4s1eZNu zQdE85Gi-?UT0$ZYBzs?SK{hL;1;gtR`c#<(63YoWZ3C9#%{90ND5=sBz!g80tamqz zgP(Ob{>QG)>-8`?ldJVQJXyVTfTc;#*;FgA(=g4_P0~!t%YJLLCc(C+ohpv49*Nb9 z-IkfQI*DS-wyL$O97tB5q*vMfDEmz^+{z;``KrC5x zy7f9LU>I4~b|drHnirYw4Fx>H7S}+({3iz%qF{$|Bw#;!x_WD1>yG6>VI47U)6?=fq?hJj^0pI?V^eLQ&Na)}R zB+Ol5Yq?1OR{d|(IgS7sg=bXk#HFCcpDI%!vu|IO$+QC!Oe3xq-Vxs1d@x|E$v=n^ z*%EywG=8cqi?lfr09tMe1ImE*0-}MNXMbrMpb4N-FSSSAXXs|+&-jto=MMd^Y~v0l zf@Qx2;w;@>5Ivgjc-P`+*l{`bkJhyE==HsaM*=*&x+v4R)-E!rejEB`ASu8C4Pf?d zwasKTNeIuP0-D3eKZ7}|R~2Swl%uCYAzdm$kT(V5lwODog>hm*aUN%1)Ow8m{=5zA;BM*8ODA5)6}d;7X0prND(I4-O86?n@= z!#}KEl^oj}ooUJ@-_Qf?98kayKdR`|+Y|tFb8tA8*O|XcL>p+?kdvF0ofz#C?saSo z6%`d~eg}v>;(Q##CRQ^4Jw^)sLg5kmiQ-!Z&XjWu#FP}_>Vf(#S)uMFCBS?>1&pwc z5`BQuM(ncTWiBRP?D(fgamWR=2cYDNyL#vo2OWBSE>xG*`afQ^Vl0Mp8LThRYPfAG z{EhdcFLcNHw6ptf_|1-2Ti101r}aZ7hkE`T)Zb#yeQ@dKZd<1|n9Zf16XTzH9$PJ& z8ed5wE`V|`G>okqxypRe<@NsCs>S~Yq?KdyCUQCo-mE&X)lUBs+Ds*C+Pixzx6cIqc#g$y8e9&>)cGgPj7|fgX_5S@dcDyV}zg za0yUIz&LCp>*6<8EOUAOKLy!7g(T?e)jAGU)j3o}F}QAHCY=o0IbzXha9p;FIb* ziFrjBD5ONb1sOamNvCGADfRVF0Pnzl7;F)1V&eOQ(ApgQKEP4Y*;#vOf5Dbj zYY${jiRDekVNrkjHTq2(@_)zH?O}B+%LkIZ850&OVR>_acw~E4Ni8>@hULd1-@-oX zeJloYkD8TiC=cEa4}arqOVze6B{n!a<=44nAH>fYI@tC6I;pe-=Vt~KaM603d=ntx zVJv+20g9i7y@ZmS@(yr{^Gq1aP9!DB1^>$OM;%oRAWIhhV1@ZN<0slNyBjOd=R84O zNL&vnD1Gp9BnPlEZT$3^Zpw&jiG2Gf+wFKREX4fJswd6? zVpb*PwfK(r-ChRlTQWyP_T$q5fBOg2o_%q)c%%nSy6RcIPJA{R)TXcF4bTQ*WpDjh zWXavFl@alkEFg`>U}mvYJ+ZGKzrTKUy+jFHd;H*uiLoDTdRk+RI!p)vM3Fy6OxnXZ zTV`j1ezD8!vmBFO%tV5Mmu$x;D7SoD%pXGsSXvkY*xv_z-eCDImU@t8b zZHcV+J_ZV4Lc~$&i_aKISi^QWhvv-2&m)x=&=`@|icKW~L^gdo3L%6_<& z%c$mzYbX(0IgLqZ#2d|&&a_=4a6bI9D*rV?5(CfNaIoo@34H6VPP<0;0lNKdE-0?J z{bJd1)y-%%wt%40Qz&E#?JmFWnq<)Ch0jI26oV6BKcWff=9(@rmK$cFwxf;UmrBHW zq84bKz!*BZjv&Z@8OZh}i?wGq?bp)#4JeSam+16mX8PA@C5}E8{N1n0V_^=+D2tJo z(r#cc8{*FeRq|ZHSdX&v5+Ch!jmp`iB=8Wxu1*|V<-8i>^)d;{X^5&GW1#vUmii!qF%fzDf?}{b1M9lI$KFL5~Q(M9$^&uQ_fTemA&u?JFkdszPsCLUT7^Yl_zBi6a{g zDC_225UH?SN&vQrjEq0(QUZEPM%OoZ1)yvzmr~ktmkx=0b2FdUvF!;DM)np4CQd>Q zh#3C%mHo%?C)driZBihURIapnQ3g)OaUj`9)VvNjIZioML|Js7d=+&ByM0j{%`CV0 z6eN-;SJE}>G)D52`Z}k>T9Ca~o>McT(z^^Yz&?|fEsy>!jGfRl9 ztk-U6eMVpM%D?w!|9kP#^zMAqFB|w=fHh-AM=!Ub$G7ZH*0CYBG$Z^*N&hptcmIaY z=*!vl9Z3rPGrMh;G3Kp{Dr!hC-)PuiDaaYog?&_Iuh(vC(I*((-W8Upy|t%IWz3|U zTX|E}mzHfuN-{zMwgEd%XLDv@gj^8a#ofan_{b6!gSv;>Snoh4UREF{P+-~3-q(TY zp|G_e6B7MC8z zyt<-n-4H|U*OS~UUyr3HiQ6>QG3IA>>s`$gKjRqf1r&TN*d-C4HMvVIgX^=vy$3*V z(C6~ge(?^YwVs7Etl1s=wK$Nc@Fd(QlRfKNeqLFkr1~{rm6F7m`FN%fQg+Xhis3s1 z`2Wr1>VUG3DS&z9g<(0igeI6meGSvaofL?&oxM!H0Pi#H;yUe6Ei>;tp_a%47`-YT zg#R>i*fqk4fp}5DnQGZcF(JJpXrfvKdD6z%%|pNT27JgdOzU`@Z;t8q=?TGQw{pAG zeSp0Jcyg6hSlrDZXt|fMmtz%D%l)%cnE`Skx%Hb{KrQ{wSc+OChh`yhQy!N?h&KeV z?GomN3YKR&9%R0$CywHEQiduZ^gMZ9m6Q}LPSeA0fWxcDDp8V&6@V7Pv&d-0yh_-_ zsA5Q2-d)(k2pvL8j`xE#paF3o`Um$*yn3-xbq&ZeezgHtHI~q+l;Sa9QvjsrlYPJ) z{N7;sudUJ>o1NBpNa=m43atWS4hZ%r-6r7JSQDTmR#u7A)XB5BTvkQ@Ry7UuZ5Z7% z8j<2=;5Bml4o*rrUjTBxG1f+!2;z6}$EdSutG1aZ<4m?mxgiH47xmVbRhD(TzUCN66L4PNt5;S&f?RNhVddkSPK|_U?q%CGjFWq;D z7H>g+eZ&oLQ&%Fr@`POUL5|=`!WDa1HC1T-C7MIB{(fg}@lttMRQmQE_U>*mIBw1C zqG2ScH6pYcxx_E0eJUp#=jpU&wwb-19Uq~ZjwLWDYc!40y|#m+z>{r7A9yPga67BBfzAl$3F&ZqEQY!aHjsnpp_#dt_k5jzAGV8zVYe!K8A7j}y=z0BS3qkl zkI+lk$nc2FueDbI1=-zvy>P>E6uP%zS@8ulCC*}q7=!;I^gWN6i%P#~zuPeUIDcEg z1tR{f1OK}T`LcpbMi8%AP46fU8ud=JPp5`q>8?x#~?4_82O! z8bnFF)V#{D;BX;IrO3kBSY4lW?4!Dt+{&Gl&KNXxH*8>OQgbDLqM9J?F3v0?GLyd4 zrXpH!?x^cM>rvo2!|Q^#@lE<4FZ2!L>(gAj4V-KS@Lgzf_VRG}01&ILv|q1q9xl)X z@5Hv}Bf8n9p`RrO6J#|6W}aq0Lp_cwCI`>U+=KzAAb7M>NJzzu>(2A^rGj>+*DLcffR(iwFZ)ZW{&>-xn``O6 zPh}+ycna(`9w=#0XcsSRHSTIZCYeoOwupYmCWa8%SS0{rE(;F5G7T4~uP7H?i#0EB z(jTvW`BN#yJ;l7Powt%-%r=}>?4N&(nx({q-%DR2zksk(emVi`dlx_cSTgNvq3xM_ z*HL67PCMoe59HK3a#mqwk1D&;-1|PD1yd1XJ8}yp!sG$*h)jAsNjX_Eb8Fzcucd1vSMflg= z3%F+Y@3La6Hu2_g9ld!~!pXG;C4RM|4CiAk9YRcg`C0w;@kZi~_*IrFQ#bPv zzJxe9y}h?hUr&7fPu$HQ3|6@w?tU)J(l(Zb;|HsG1Z4Q*6gxfHNI2+pJ00>dQm8-5 zh|(atWXCii4h^PWPWWbvU6BB~#42XK;v%wirHQsUH(sV z1>ulY2JMpt*c2i3?V6GLh~fKE9CCX-Sqs3l8<|Db>v-6(Y(LDnLScA;r$W_kRDb$y zoP;BFMeEi;i0yZqxxuSd&CdCE=n>`F@~LuHS+5PfE^`yO@>J;BwpZfB+FL7nEVehE z?8u~s!~3rIFtMZK^&6qTT8XIG!FvC(h0jXn~Pg$=Wjfi(Ll z|d^rD&UxkM@ zTLGSh?|FXqW&*;T=+%_m$Cu4Zsbrbb_S?!>?Y1w$D>h8Tg_Fbee@=nGAO@0M6xq$I zPmyHD2AvHpI~Pl8o*ynJp4zax9xiG01}BYhMu=Rek%A$V4(>W$KSG0}P(tH4K^Cu+ zLYq=J7>CvOf8n!*7F1}|#XX`gePi6x#PK<BVmGoIq(Hx9FVCvj0EUeXx9Y`$k$axZ})CMSm?P8s<=Rrf<9EDhvpqR+t zuxa3p{b=GfZ3m~VzH1Cj$k2t)QECZ&H>c&z8W@P{TKI_tG#RGRi1zxImp%YmL5Gi} z!1V1nGC0yH3ggp6QtZxc{7WNCU28_NrGh!?YKt3|N-A{5n#xR~wCxLt8{(%D5*!a1 zAexQ#%cYuBb8{RW(kDF6)SfK>1n3QuQK7NrMFZ7vCD;?R31fFPuW7G`N9c^ZIrq8I zCzon#4}}e}d!d1{rfKFV&1$vJDhUNV0#@e-i@+q*MI0BS25hsA3{#A&OqS^7_#e(0 zy<&f%FtnWV%iSiAHJmz{(kMN zJ?_YKYVqFMgd{ezWkr1(U%QC9Rr zt-i`8l`^G8N!c7%zz_W4q&?yh4go3pvCglxB{ta-8G^%lo}D(~s>6cS9j*$Hl3H>z zjw(b-rivCk1=7^|4q0y2yIKL6KNhSfKqjwM8sYX96cp=GB7i!Cq@0|Faw{334IC5$ zDQtaj;^m+bZgF8?4`s_YYNztT3w|sej~culTpNnNv=x~U6lrIMp59=bxc1t^Rrp1@ zBzjbw7~(0RZ(&4P(ZTFRgP6YgF-6z09!M34IO$u9PG$^Kz)`d$aA9ohuh$zxMRW#s z==2Z;hpqV`{AR!nc8&1}v4sZgE)MSV zkK0^Nx8$*q)5YxVQl1qZDca)>5>n7;vJt{cHe;Cyfq1g(S4P<73#mKe9~>1&NR8Ox zjL>L9C_3n=0S7e&EtYujQ<^yqg@kOh5yGs<7wmsqkg#$A=ATz@_A_7szpNu98s(N+ z9-N(vW*gr*m>XMJbhnjk0WUW})Xr5UhF%_NRS2xjR zDr$Cu!%jo3$_f}iM+{a z{BgFczcSb z49y zsE9fU@*c!doTVSpaWfX7*#!LFlGq!2J*E`q-9I(tq?iQ{F& zv@+8VV|yxw-X3Nx1+A!x+G8*a65vN5dhx}e zsI$_X*wM!_ggM0~9LDO6^_jHYCkA0ap*+w5h3AUNthn{mReU+0Ga#nUQb~tT4@gV_ z+R4der{m4o1G%DOmeY;$aGm5a(2lX6A@W-sND73^7@AgwSl6C2b7k1Ul*hR+@;~9( zvqZd;hyf+FFL(a|M_Q(&i78W8zE{?Pv41ObQofbz@Pv0Wh6*RPaO-gpNSn*MamSeWgCH4HL(c&3C6Nhyb9RfWfY3hBap6lI1!DsL#$ znB(%&pso$xdEMF>ER0oN;Ds@gS_K-An0V5C#MZG~Yf)chLRWIADWfp6=+fn};?{jM zY`Vj2QLR!!S1divHW6HzQgY^gwasw=B@I6u?3Mt!TcY*X)H zxyF~URJXOIi=KZapyTM+mHe(+T1iq0RVu=GUbq zp`qTq2j0p2wf^SASLpbX{^_}1Nx3xq%Q>@`W7jh%1@Ty=xrP%bTkT5H+!C`=CQHmY zX_?HLlb>1j5$xKXR&!cYNSk_~r~0I!v2aH~`ZCH(0k&atIX^S2{O(NIprTraX}vUg zFZl}Nxy5DuJ$SB2ghURj;V`fK@hD7OeRDFSC)qcqZs?GUytByvHzxnTKkwmoWfLg> zgS3n#p{5T>|KAu`tM6xV(K2mZ4#2OQ&f0#wA-CI3UymKYBD=vHYvz4r z`23+)nPvvIZ@*R13L=@~FBk=m#UrQX&Sedbz4LnA(J?_g0b(*_h9Ge}F4ME=GzEAcCV@ zff{bKHOgQEpk^w@cLqjD>hy?1R8RM)7eB*c(Y>0d$Ix6C-aik}B&BsfXqY?k{ z;!?zZB{@0CW!2QWhatZ| zRM$l&h|yCqKUKIGZq(QFhh;wcdx9LI5QOx^`mKYAc)j){@&Xe#79nP z0bDH=nmUm$;OgF*?aPeN-kqRW*cdW!=GDdo8_Ra1N@@#ueE`hYY_0mbGK9!+8Ytm} z(`FZ-xkatA7CfH&Yv`BDgbP#UrT)ZT`gOK6V*aXVjlO&LULE!xvKQ>43(d((qE=0) zq95Xdxs^+gF9B!RfvHu7*^RDKZ1Rapw?g*^P6?sTz_@s8;6tIviiE;Pv>q{PRKx7)}wdmV|$y~|6?cWyR|x{pkqIU3G@ z^8nY8ctvy(IEda(#Ec{*3nG(13hHDsK98&~J=>`-<+5(LYzaN9UM{d9Y!N-h29 z6QQ9e1sh?Gd~!Z-U_!__#Mo#mXPO=)kUDMa6yBlSp3=|=ImKzNH?b}b)_m09wR-V3 z1^XI{!W>$oSl;AQwTqQ3^kV>Hd=fqO8QfGM=dpU# z*ViKnKL^s5MSNIdr(KAtQFz>p9T=&pDlHStrUWiIP4?hZSMJOh_HQ2LBn@Ft8)eV` zdo}P%FqW%#p+Ar z1O*Gc9ohT$_Tn-X5tb>uY@3NQ?=yw@Vy>e3GenlltCJnztA!BvzgjBoj1GNEs%*7v zV=p*3_`2fWSd6Nl3-}78mUWxZzem2Z->dKa(37!z^`OkT$6y!uWr3u7-{dkXViOMX zl`W){YQ%#f}Mk&sek7%XFqQ;%`!WIpw@^c8NCaU%g4o zHatMXBlMb~#)_q`s~! zU}GmWG#7V@o7Ps+_WQ5JJnflqtahy|cFwI>i-e~Pa*HfdC`aabgA*b5u>~RDftz`} zXS{$@3^La2YMHDCS+PUqc67KkuK8Wum4!YcHp}k=*~#v8P?5>?BfA{VNQSJGr!PJG zttwl%H1<_bbhEQ8Zlwe)_EbD~>R$QAN5@Wx!&ux!TLfqg#_4Hja4jyb|GgmINqU|7 z?P7HNq!=1hedE_AgM-70Q36q|teM%#*@>?PgC)OMMu(R~od>= zqdxv}Jxo@ZIUb-&xXmm7NVg+5%hSK%Cgk!$+<$H%jiqz7;KP7U-(dN+oN!V}XTQ{3!oxv0zh zDvNkBidC4Fa&ksrZs~f2EZjVAwk1OoyuJ(y4sJ{Eyv6+AtFeDaC%Er?~Aoh6H{Ne!g8U=~NpmR*s*@6E^_M@{ z?qZ}Rp1aH*U$xB-Ne<-2!LYEgL&5i-68$l0J4g83PdRe6Q3t=a#4&Ywo(LDV`jA?( z{==+*z*+^DHJ(Egm~H)-Hl$K?K)G*$oe_n60d@0CDRYhDbu}@iy7{9(W8Bf56?V_X zi${h|vC8Sxfl&=_&^gK-LQ+qjP;d;oM2ECogSK$~>%WKVtWO@&Rog{qndmQB#$kHK zLBr{N1spIx%&1oiCka>lT<9x5rx;~BLZ!8pqQ_tMTn`74V`f~}jHkQ`F=gj%=06=k z-*Cs6SPwT@94qHO?E1DkRH7m)%Q*g@J10;Xl{+zz4fVop-UQQsqYb(pALX-QU=Tic z@AtN&2SuC2>}e8j=F~)oaWe#BkrsddfIpBrP~g*r+usO-ycZWH>C>J)F!{6^IrciF zwt5VGoYC$a4GM(Q*5C8@zczEwx3j#q^2i2*ErThMUzlmJJA>01>yx$35%IUUxL+7- z_uhy?$e?eHJih?543l25 zFf;PAMWRsi$}2?{jCgKrrpaY#i*HGt4^7UFjjPNF_^xB33))e+h!qE3+Xf6&?4uaP z$f+22QN`^#}lOuDJXqU_NdjVU-W{0)Pz{O>>etkcu+d)&_7Dmki?}b=hYWgJFt%&Ys z{B8`a3y6fpqptaz_1)Knji`2y3&v{nM}GVq$TQh_g!}nsTONpJPtM}GkE&;ktNe4T zuQ8FIu2XApd_9W{3=|^oJ^x^dw7}j3f;Map*KKWc{Cd$HG#ll!%@Vo-_WD) z0-IV+E^8NfH!oJ+Gb_*ED9{(#`qf3hW8C6y(Q9Z>#Cvg;MCD7(XZ(MLqn;M4$1m?2 z*!Zoxv1gS3`V|@%3XAjnJ{tMB;4Ru)GXeRfoEypA3J9*}3utxBV10V)BGIyx&EoG( zOzYfvJd_ht5f%;cuw}wcp?w0kf0Pn2mR80J>C(=gpO^DS$GNNY*?;X3&otpswBL!m z&=%opOR>EFb_PUj78XkK%Fc$f2O0Kw6`-`93M6PY!`p3MA3^F#+V3CvxKZ(JXGNp!}IB!Kv3N~Ym8gOpknXyp&-mb z|AN~zzd19u=_Au`M|4Qo<$TvBU65u$+^;CZd#T~K()uShTpJ#@-~0qjIGr2m%U7O2 zx>0m$BYr))lQq*#?dTPUN`x`AJSn82bo5ca(dT>brgu)vkINT&1{Z@$Mx5wLSHfviu)yR!#6nH#PrAx6Y2Udx`=m<&K3xHTl-ncl zMjRpSAusf}L+kwpmS-odyJ!-dd|nmtfCknPZCOmQ4}VHC!*?;>E2k62?oG0Fz1I{T z-*uIi9iOuarwnD(J(A$8QdC+DiLA55Dj~6(XrB`B(GTXs&gJb$Hgg^;yd*%A>8flBpLuq9PB70ta@Am8 zo2c{bf*!<_dislf%n>g>#TX!_ihc#b?HJV zVnSb7h&?B&v?khpBAX8Mtr5F_m;S({lf_{136}3xYczkE(|AN>jyGdbW%ReH#U~Zv zm0xNbL)yOPy=`uO4&A@bM?LMdDu#-_zhIX8A{8v{`~&V!Ev=YmQb4)f1n&9Wuc(-= zJ0C}`l9>cwG|&qdea>-qKwie7uCj3MLfYbG5*At3Vm|ZmX8PExz65j|VyjnzV!4 z9#bE`iW0?9v|o&!m+=riKA27VNFI8trq^)nwrL?Gs~+wptKzHBcIEbeo*_#Y;qNI| zy4)01LD8q$q$?A}_9P8qS=F`sF07+6H}CxVNt_eaa%{eQN>qN1=|w<*VB~Fng5&_% zhFKP~hVo5+V0YVFC^7(LKMu1hj6rH(f-o&GRl978$^y|1FenDR+`g8O7qbSe^#;At z_1`>yNIm$rv$8}1+jGbfzp@mGNBb-B*+I-g-;kdnYiskchF<&D_m`Zd6L3+kF~Y$C zn30cw3qILz^k(+`-~4VDZ%!YwFn^z5{&NJJxr$FIBO&l4T5)>m{cpx^t&=^W(b1Ye z_WbmZF&gT`#^)fXZ<(jbAynUEh)}%2|z%t^H8f zr!L$V#ZndsoNZ*1a$$n&it9$I8l#{nHT0v3Q~yuvZhrY!l=<_N@TtiG3ln6gA zRFU>6&4CowOXq^Br_`3e_?+wm#9z0j)z$_y#I|=SDi-=nyp8btF6sQ|YM}3neO@TB zJqhFQT{klD*Stb_3!{g6%sXPMk7%0r(=#90~ZaSJX&*nrLd@HNHkr9}# zz&ksLEB3u6p6Vu5pvFP2b#N$rs1duYB6z~zc-i^QwfJ*WiIRs&LB{6+$mT6m>+g~K z2+b+&S@}6b-lxMSFBdCZd-6X0cENScdm_pKJJ8o$7gppLV=nvU_GK^>=Yb%xr~WMf$Iv6aqr(TA?i|kbJd`rU`O!^MSxZ|n9I2}a$a+ZRo23d z1dZ|1mR<9b$VHw%^k6Zj{DdpwthomFWv%LZo}fn5?KpAC^JWZEp@DNHYEnKI`O^yU z*!Nrx+?emQFYMh05Rvv-i2`=?O;bHq)2IT}auJSE^ml&I*wDF{>(yrnGVqGOABXyY!uFMAe>87CfqQ^)$_4GF zrd#Tvxf=b9?Ps7(4o1j(Ma?8|oyvIZ(=~S7+cmJhKP)5E>Fc*l-agEB`kDF=1z2Uj(x@-$wD}=H^c&G2s{SdL5?M<>f7~xy z@5oZaMd`Akr83yD+J2;GJ)y)G_KmZ9-9;9S@2O=96% zNOnd>Cv^B=lQum=MFRUn`4H?!D#BS$RAKYkk3)KWafU3i%#w-S`zT}|$2w(5zq6thlZZy+EI+L^j zaNOuTv0C2lCo<|8*%&M~b#=RRyuyfbGm8JOwYLn5>WlwHZ9pUh1nJNrq`N_elul`o z?v^f*?iv~fhX&~okdn?}=u~p(?uNTL=XuWM_doaL<(>zPPz>u3?&8*L}N| zhV|q_?C_7_ILli}5}4pSe|0Cf+Jq)rbNv-9e15#*BP@m#Ut_WARkRhSRhjR}X91|9902Llp#&eegrfN_E_^TeTx#r`;~@8g5g&x-%!Yjq>0X@m_nJ z5wp2#HJYRkiTmz3uw3)QSQEHQromdfDQ_V^pnc^gq za;jXKiku{S`uf30o|k!6C*Y3RH>CF0&$V+t%3gb!0X3ACFPuIvS-IOiHVXY`_h1GT zTmNVG!c%BPbwEYpKy08Xvn}RzbmX?6O+DX+S(&6-{Bm z3~96_*c(k!k;|@y1sABE$cRCfmC2pl!%`A9n11zC+VoJ_ySiu7Q>LG^V4qH&x_G8|$aO#3_*;@Q$H#xc{nZBCqWO;Ul8!D9{d zv5JJ(I!yO`&hy_y9R<+AWm;k6pA zrZ-5qd9L5n%;6dMo!m(_B33v*d=kG%Q_afllc!w`lRqfUl1hbR#*@!=yE;qE~jhexoz+edh>5DectCXqMXm6`crt)wCs-+QoM^INy7i>SU!G&vD7Ct z+%?B;<=Wn~oqBGUmCrgsT+kKdR;BPuMY$kGMt(sP&w0%Sbgd};giu~dO6Yv`L%qT) z3-Ot%c4LcD81n4YQORR$AN_BN(PQ&hN%k%Z__>yFT4_6rvf1MWSfkkCR)1Q}bA%}5|Z9q!{G?9 ze>~?_9}4Cbrr$V8qt3wqoL#7imc3hZrn&9Rco+AiUJ#;P)A1dRivH~;Y+`z{pkReI zMMs>W7-+wU>Lhzv*`Q2RkhAk1gZv^ACwR;9+FLrLX6Q%iPwz#!O=YLCS3>^IepQ(5WhyCu8@yHw76Zl&y%YQ3754&R-c~ zUUNY|{#HikeY3$fS0vdTsR=}u7KW2G9V_$oXdsDo-ts7|koRf^OUcrb^rUDWIi|%# zYOrHPv6qWhLfd$G8>IMp67x8nXacHc*!z|)e}VaPPyLK&;-E)K%=L+dRbbvC?&!xMxewc!@um_|Vj${vw`qmD23V%o&3T`W8D%a1XwRw#@4Z=9GH zPILZ(r9Ur4myK&F9Sfds;&@(`Cl=S zt*vx&#h5x@U%@+eA1pf%UB=_>3)LyNiz|!MFyJ^RRsQ4R-$LT1uB@E2ejz^lBw$68 zM0uszYhs3L7GW`sQXGK1tlHjeJ3{{3vp`u^Md z8v0p3;7YWhRWy{fpKoyJj~_y9-Z$&^X=ddarju;?MLbS0Zz@gs-pd`|+q>*mQT_4- z|MLLFUbw|tx$I5t%%zP36Z3F-uR(ffBEu#* z{T1;_j&XB6!76GNZ-i{s+0Ylbfy1idBB5I&Ial!jlRZLXl)b%{qYg$ zEa~n|QC7IQ5;m|S>;k9=<;>qq)_fhXlzSQ-V3mWVmNeb&bSy9g@}|{f$8XfR1IYwD z_VcFw$EDZ3Wi(f+q~A7okb&Ls7IppeZCBe`6fGB7f;GV!mVq<1%_=Ww-2A56i71Dx zZ&4AGd{-aJ&<(K>?d?fQY`v5@Pf>)k#XwRNEHsE~O|&W9q0s{N+_a*_>t>zw4- zz_DHWL`qcF+Z}to@pIroB;Tgy-M5jKoOU>aV~bvM$mtf9n`T&7FToUh5R05!J9b|v zi*)+opuW%J8osN*cVCmV65*%puh;yX zkDI_{1uLMBrvCuIT*CS_q@B$n5qs~^-a>&Ef8$pknUI)KpK-Iz?Rk~EW(AE$OR|dq zonL6P%Lx#CNbK>@QQxJST0~3h8aDh?Kr5s9Q7>fvc*`zlOaaRtl@=;q34ypy zUNBH}q+Sg@az{i}x?(J*2;|{-K%KIUgHYsk`OnS@eQp4L{OwKcTJ;coo8`H_wkd+@ z^0_U%!tAv{%IWV<29!LDq<#{nl z8#yj~TG~sksyniq-C~Zz*M;W7seuRRC3v;5P3V-%RURTLEVr#zIrY7~@`CCw0m4ux z{iZ#+2NEju7=)KW=ghFVFpZ9MIU>PM-TwpN&C)AQYs{;~wXF8Tng8w_V@%c{ZX;lY9h&kQ!lW=>fkorA_ zvPaR=iASzjHtD2JX{Rk?<-22EavyCqxr4)Bhzi5g6+-^U8@P$HrW9IxQI>RjtKScR zg=a6!uBXN>We4{MQ7bcNe&P1d)^s-953DmCJ~99OVFg*)?coPv5ir9V@|$`@r`&2; zU)fdaYN!AAi^74q@vV9vHBHzdq$z7i#tLtrWMC?9Q{A&-H;d)2NqB>ro7K+yr*ii>FYKiwDG`N=nwS-PZhTD@G#<7Pk?B+1(la)Nwo4-2*OUKD zpUG@Uc7Jt$UP-hvL2x~oJvimLmLuT_LtM33e9b=3=CpZz@G~>6Gf}!1#@l17N;a7P z&d;^snSZ85N8Om@RkOQVtJ18>%?{3o%^V^fVxQxgP-RU^DPgv;&WfCda_xq*Hr+b* z!EN8^Q?$*`n0xQv36a-4xF)Y*XDZ1!!h>KY!>ClZo-o4MwM7gnEJDF831~wZDdER< z43yrCqVY%9bnw*=Ica9iIhRG6zwOTjPx$lyserrd$Hb%Rqu|yl*c`XS>^e9G`RqDG z=wZEAV{gMj$@QgSI-Z`rzD^SKBR+25UKL@5<$E$m{S@++7OQcbkjMM#n)#M$J^^hI z=cS*C#ZTbycQR6A10pYz&;g8bosq{T@2odT|5`@;`R9Hp$PahoRrjo;6?jG_Elt5r zCsuklND}QAhpj=-)y*zYMwBU@C!8ce6Wl8_*GDLQP!dxE^h&*Y4r)wX{SD&#b@S?G=;83^ir)0KvQDP{k^AiRmg>Jd4zosC#K9Cjb6jV9YNvXJ3Msvs9lqIm zdPig7I@GYNEs`X@^2-keuL8$;2_tq%Av{j`+!aHjyw4kP`{>6o=6{?9a%=pcRH2>F z7kt0~reMF7w5nA}QSy(Il(n9<`Tf3)fkmb)_0ro= zt6JL4#Z>#}CSj`Lgv&lr_J(gey9C)F&}xY3e1LO9If%C@#3?&>WTZ1gNmmVT##no-~>y&`)8=NfBE;Z=G|w#0G6Evb=gaE)}G(D*~>__7xuJIe^5 zJdn_ReRc4!pU_Nzz$rYSr@PqUEPZTVM7vgrUFz+P>V&TskYv*uw5RungVldVm zH$?00XDh}%9?^H*&5IR>R%asxm&8l-`Ho7V{b0-s23tW1Z(w?QZRwC?&w6q}eY=|~4)s$07W<(b;JBHDHYQO|jRM-kXG6;XaVWruJwYJiZGME3gzFls8ltqp&$jUh z!)1&nU~e4ssSu%*m9!4^-9-s?-}2$kZr=orgO+flZ&&z0&lII8GuB*X3ps()znN2Y zZ!ltmFHXR~q~v_qN<97VWA9$6(_=R#q>pJpQ=%I*&|Kl^bKeT^oKrT#7=@mS@C;%P zgEBNmY4TR+@TUW*G?KcdNjlFGl=Zr~u!Ev85Kva|JF&eA4%T$`K-sCxj8%SACPt9h zVKG2mt>kIG@>Pi*dARGF!cfWxU&gqW-G*JB`4$BXaDsF`6f|xLcs6H|)c{kG^ukS4 z(Tmc`@-3yTQmYd|rfSUBV;m<`!kM~4GNjVj7$oNrt4w#qYFGkrD1S)q_sbiGiG?P5 z8~#>r%OI%1Xc@g_>d+M@I)DZXi0gZ|z_HG#O_isks=^-OO@Tb!D0VrYBC$j!8qVIO z^4!n>G9+Di-MwIUQ@b5I+Q*S|)NoqdQr59cJGtJ@KK^vFCuv-MbnPzI@n@p@4t9Wv zw2n*JO&H>$hPk;DjBh0p>*8gHS{pXPT3NukKJVYc*`@@@^gqp%w_2G4$ifOm_uq0g z+HLppO(9^AIs}3qhUaAy+DRgYF*v?RDC0>=NZ&`dqVCx{q5%h^Kf!|i0ET0pF;c=F;ws zhY(1^zl^Ek$B}ME4g~Ae0;~{;hDi7RWppG<9zb%LYDs_L?sl*++)gN0MX`Sa?l3{!7zb8tNlB+a5suw`DzwQd>#(KD7tD_5o+$b6W z<*%T8g9Si)ao=HjAFKs>3{p1$z1s6YpSKzp$`K&tQqtJ}J>P7{8{eQtbW+p$gnsdJ z)-#gJmGM$~gLB4l%y|mW^3>Upur4=ww~_Cb%>c;z_^wDH6-V>ZSsY`I4Yek{hbrZ7 zr)A?7gY?ZbDZ>olu;J0cT^h|2gE1<0W?@rd>ouKTa8JEUHt&T+#CsK|1 zMV^tN3x;$I=DHTRU#@L0YS&$oLsaTN8s*>qLDLRP#d1nE`wj|^J3<+A!@b5tbr4@S zbvBNh$g^{Nf_>vDFa&udE!%JO*rcd*XhyMEKrmC3d$B(Jmq;C;;BjZ7(>^vPwpol( zK>gr$K#{($voJ_l)K z18Dteu$Qw!_y%`Hwt4aSro$5#5C+5RYGC^PBXP{}0Bo77!~Lg$WMGLK*HyYtC*wFH z5PH^zfnKvZL{QS}Crorr-#T?T8mPPchLGQPX;t_7O_;Ol3sVs|T}~pd4}@4?(FG+} zT{LR-572MJH?`cO5slBmf($tnaSOeosHSU&{0uMBsPz}BTv*cm>GuRAV|UTVxXy@H zJ~LLJl>6S9ZOZ|2yCj!>KxSZF_+WUIJ9dQkmC&MM|9WaI(F{d%wbNH}I+!yA@jME8 zPAVL{wj(_yl5Xko}E4*9Ob^^_)#%1DkX6u7HRSSP+1DVU!0 zh?Ie3QB73XN>UJXXBdb$Skw%mbZZs>==gGxbJ#!Df32|0WUFXU#`7O%eb4IEgwwGC zxZ$1#5u%LV3JR5|oDv^+Skb$T4!B3!5FbgX8gUmBrDoc)Z1l;egVMO;`zf^Z)(Ra! zmgwg~0uK8x+pgZ;aAALqs+X5T+B9-c_+79F&79G`j;IQ~U0dT8&%Gt?j(;#3P6NaK z4MpBx3pobM1#kZ(ELhaOA>}Nn?S1TH+^m-G zmbS^k3an%kAzTBI&lr=Lf4?YxZIIfCdBS~3HGsPpQmz~kyjK3%Uod)pwydk|%D{TS zUq73DU=MW|l{Hjo5UC}!7P7Z~2vOAXcuH7u7SJb}f6FU=beuH)1@6lV2m@67ieA4BL-*#p9ZiqyFn8@!?rGaH#E?OJjb%h@(tn#Frs z?XEK}^I*HipdH+v>$ml%# z=)I~+BA=!M8PXNU{%U;g`Xp!H92FmAPlXMk?pb^PZrF1@Isn8YY05x5$06zyI}wEh z0b@0zh~|^`8jIkSBF4vN2rir)vG_9kCph_uw4ldM*}9Q|RhG1A*renjKwEbH5v>z^ z$sJ$1z&o}vds$-nA)i;=hQe#QOMLL_^}Vx~WhMyU(^c2>bhM2BQ09V;v9mGR7*oc5|deDEEF#Gf{qX zd>%C|FTHO{zsT`%7grR?%Pp~*NK}c%9q(MA8CvtIR6Kabu9f?9&xn#FS}6mQvF(n< zLfic97CX|{G_3Q3^fR}?2uBIoh-F^>wi@K$?iZRlFnQ9KMavm6nrs(M&eDoY6RK(= zy_R0rRQzU#SDu>t7fcEQtYG8~P`nKj%ECqqveVm4^hb@9{_@ zpqCLrzZDeQ&Oq6(uZAD|F->aG1>@fha3U2Y%iK3XFIP|PH3Lgm^c`6pAbcOE1MS;= znCQNF0yZF>0g}#6{=~VwU-gr8Am4A_VM0u4AG4G!>dOmh{Cl=0j%$irJby6OeladMc+f*%=_!jdd zE0r&4Kid+UFV(+t@4Uu57rNzl+Fl87)>f8J{?5#7v1j*f=*mNM-J1!#NAu8WdyiRu z=~%jjyuk8?y?M_}WK{8jlG}d4iODVM`v$G6A~0N5_T|wr3V;D~u@gr9fr_uFoHWTq zw2-9QQ}ucuU6XkeBEbXCQmf!p=eIu%t}!RuMEwpv%5%GWuYCP_VWRc6fv75P{c@)i zTvVF;-nTqX^d;l&6Ts)=6p|+y$_%0USm7X}&3FO%0Vd`2Ap=eai-?=oGm0i`)MEgS z%%PYyr}rF6w-RJ1Unr^iI_^a7Hmb;P>qLqws@BG2?<$WT+3|ZpusF0l7}FabIlk>D ziXqT6By(X;4#;V-H7%im(88>AQP69yjlYu(bkcB)ame8=pZj2BWiaFU<;x3O*{YuQ z2Jkv;01S9!@bjVh9L!WEG5Szi0F>LfV#>asy4e-+z7$pYs`Nso&qxHm za*872gd6;WFnv3n_lUZ773DhxJU1>+eZiRHSWj51jN{OuweThhKu%cs%%qF}0Ae{$ zzXAXQU?~H>1-3eBuNduW-jGTg?$Th9*n0t6x;-0I!TzA3+1a0w zC}jqYWZ^tcqHpHA-6s5~q3_NQvv6X)0UwtEbLQF@OWCB*zd^&%`W$y0O0Mbbvk|Ir z1*RZq<-vSx>eDQIsF)kya1hJjHzH!IAnws0&30LrXKV$&cVb6_<0yFlaX$lhuE>;0 z0%GXASJ*Z{9iPr&vX`tbs*{_p>T4WOCq)`qlvv=h&Nu!_FZ*!NiNj;bReF8Ad;vYk zgSNyxVuoODVSk{y?KKh4K4A*?OX#fEo3WNV6)m4rUuD~IwleqzU0!``Kj436FqNA( zK^5gtlkCYC!;VX{cf<#s+sk8+vF_Hsp4({RDFsoq);BA@4yO#^04!|s@`l8=re)-V zWs8kzxm8e6?$<$R(Ue_noVP)=9lU%2HeiP5zRf*GBD4SxeecW@6{jqv*j@;2pYkdR zFq|O>{zc%C+@2=tlg`V)@DI;}M>N=eFhZDw=4?^quz=To7r`|m+d7%#&NM$7zn!w| z>UjbkVoyk@aznDHo|LdsPZ&p-9~lki6tf`w&9Xmfb&&lhUGcw$unug!>4sZMK*#Md z+p&+rocj$OKPz-Of5H1?9HQMMdp`vG*c_F`k88_2zvBi3VzJwF`J(M6u}>2^nDZSs zdA$U*F|+Va1SRGg(+$05qZXHZ?d3my&$PT?rjFtbevB-}mXIf|t34wca-Q3osp@5w zV18}_rs;t=G-hoMR`iZ9W4Ne`rVNbB`|@Qt!TWtM486u@3S7YkV5x&MKXZl;lxKwo zj+t+%yTJtLwB2}vSCLgT*r8v_sSz6nJJ%VTR<=cp*fPZ!uw1`cr)`sVx6_Kx_||E6 zFG{T$1K1j3niBVC07jvv>f8QI;n_!GFas+$bS-8OqXM-exE^`iq1Jl~ zm%n-{;3gd*QZRK%G)>%PG2h9MUdIKW_5O>Un&9cyJdfCig?S++Vl}UMYeytm2~Cg$Fp_`L4lCZ$DyXf9lza~BK)5*YZ^eY^aKYY z16WUoC*%;MgI9}0V9iGN{~GRHV6`VHS8?|HHB47j^S*8lk6xp~)Th2CBaUxk%*f4& zosksD3zW0gN4BW6?|FxXt5&9ye*tcgD(#kW|I6zdj5h7iFCp#b#LqyD-*lE0=JmH} z9(rt}!-uZuUTv{V9ZnIyeMi*U0sZ(uD5L`4^X>)@2Bp6EcgH^F8cSF^cLXyoFv$G) z?W5VMirw|Syp)l>z_2#O2%Vd>PTjgy{pr&M&p*EchNyZYZu!OA1NP7fe4adTuJpxI z9AkT=01_bQ>G9cdPzOWr#Z9^c?xYmb@JQXU4X8O5li>maMCa;v*Fe0e>D%GfsdC)T zT&+K!OnMthVoZC;W0~z;%BDqEqcSIj|O$o0*cXqc{lP z8{tXPJRfxO6CR2Q=zPmWZ6!&1&ubHGnjs>H`lu-C(JO4Ic}lV;EK8$Q+v`jfb37%+B;b` z=CBlkeE;Uqr7}LRLre)s^fjkM@~7X|loW=3HovJ1&5CYUlWN)t?g64Vw^8qqSYpt( zt@#g9V88^AZ6uW*XFd@?AT~mV8|R$!(^HdS%rdylsYxJ&J-yZ+CJdGNI4j``QIW#H zvU@3r+E_Wr4iFPPn%u0v1T{J}c>ql-ErrKjpxCc_@>apOdYx}3>*ly?Z<8tS3Fz3K zjODjawib%zvtP{3_0!C~aVH-|3@QW7O%65r>h=ZXjx%%p!AU%|b*d8c5QmuCAn>WyK1%_Pf1 z)~feTo^rMbXz)b~>>SMHySEs-AR6 zdC{%RO%NLVNIZ^Y4DD;_iLEh?+*;OlbN;y2RoZ!4PVV@fsa&I7mY1-I^$V^CitIwbb?^Kji3u`ogrM90&tbVP^IA9+Q2{7Mp(oQT0}# zAnU5pGA(N{y9E|&G(WVk$gGK~<^4u=%0A`=rq>`Dm)jE=e)-Ys72oU%+?M>q!73dn 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 Date: Sun, 18 Jan 2026 18:49:24 -0800 Subject: [PATCH 02/19] Refactoring config -> params --- README.md | 10 ++-- cmd/inspect/main.go | 4 +- cmd/run/main.go | 22 +++---- cmd/run/run.go | 4 +- cmd/scan/main.go | 2 +- config/config.go | 40 ++++++++----- Playbook.md => docs/docs/Playbook.md | 16 ++--- evaluate/template.go | 4 +- examples/cbio.yaml | 16 ++--- examples/gdc-convert.yaml | 16 ++--- examples/gene-table.yaml | 4 +- examples/genome.yaml | 10 ++-- examples/hugo-ensembl.yaml | 4 +- extractors/avro_load.go | 6 +- extractors/glob_load.go | 6 +- extractors/interface.go | 6 +- extractors/json_load.go | 6 +- extractors/plugin_load.go | 6 +- extractors/sqldump_step.go | 6 +- extractors/sqlite_load.go | 6 +- extractors/tabular_load.go | 6 +- extractors/transpose_load.go | 6 +- extractors/xml_step.go | 6 +- playbook/execute.go | 60 ++++++++++++++----- playbook/inspect.go | 8 +-- playbook/load.go | 2 +- test/examples/gdc/gdc-convert.yaml | 16 +++-- test/examples/gene-table/gene-table.yaml | 8 ++- test/examples/lookup/inline-table.yaml | 8 ++- test/examples/lookup/tsv-table-replace.yaml | 14 +++-- .../examples/pathwaycommons/gene_collect.yaml | 8 ++- .../pathwaycommons/pathway_commons.yaml | 8 ++- test/examples/pfb/transform.yaml | 8 ++- test/resources/project.yaml | 8 ++- transform/graph_build.go | 6 +- transform/interface.go | 6 +- transform/lookup.go | 8 +-- transform/lookup_interval.go | 6 +- transform/object_validate.go | 6 +- transform/project.go | 6 +- 40 files changed, 228 insertions(+), 170 deletions(-) rename Playbook.md => docs/docs/Playbook.md (98%) diff --git a/README.md b/README.md index 794811a..1fae78f 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ More detailed descriptions can be found in out [Playbook manual](Playbook.md) class: sifter name: census_2010 -config: +params: census: ../data/census_2010_byzip.json date: "2010-01-01" schema: ../covid19_datadictionary/gdcdictionary/schemas/ @@ -39,7 +39,7 @@ config: inputs: censusData: jsonLoad: - input: "{{config.census}}" + input: "{{params.census}}" pipelines: transform: @@ -54,13 +54,13 @@ pipelines: method: f - project: mapping: - submitter_id: "{{row.geo_id}}:{{inputs.date}}" + submitter_id: "{{row.geo_id}}:{{params.date}}" type: census_report - date: "{{config.date}}" + date: "{{params.date}}" summary_location: "{{row.zipcode}}" - objectValidate: title: census_report - schema: "{{config.schema}}" + schema: "{{params.schema}}" ``` diff --git a/cmd/inspect/main.go b/cmd/inspect/main.go index 4375f45..36766e7 100644 --- a/cmd/inspect/main.go +++ b/cmd/inspect/main.go @@ -53,12 +53,12 @@ var Cmd = &cobra.Command{ out := map[string]any{} cf := map[string]string{} - for _, f := range pb.GetConfigFields() { + for _, f := range pb.GetRequiredParams() { cf[f.Name] = f.Name //f.Type } out["configFields"] = cf - ins := pb.GetConfigFields() + ins := pb.GetRequiredParams() out["config"] = ins outputs := map[string]any{} diff --git a/cmd/run/main.go b/cmd/run/main.go index f439127..8b3f016 100644 --- a/cmd/run/main.go +++ b/cmd/run/main.go @@ -11,9 +11,9 @@ import ( ) var outDir string = "" -var inputFile string = "" +var paramsFile string = "" var verbose bool = false -var cmdInputs map[string]string +var cmdParams map[string]string // Cmd is the declaration of the command line var Cmd = &cobra.Command{ @@ -26,15 +26,15 @@ var Cmd = &cobra.Command{ logger.Init(true, false) } - inputs := map[string]string{} - if inputFile != "" { - if err := playbook.ParseStringFile(inputFile, &inputs); err != nil { + params := map[string]string{} + if paramsFile != "" { + if err := playbook.ParseStringFile(paramsFile, ¶ms); err != nil { logger.Error("%s", err) return err } } - for k, v := range cmdInputs { - inputs[k] = v + for k, v := range cmdParams { + params[k] = v logger.Info("Input Params", k, v) } for _, playFile := range args { @@ -46,11 +46,11 @@ var Cmd = &cobra.Command{ } pb := playbook.Playbook{} playbook.ParseBytes(yaml, "./playbook.yaml", &pb) - if err := Execute(pb, "./", "./", outDir, inputs); err != nil { + if err := Execute(pb, "./", "./", outDir, params); err != nil { return err } } else { - if err := ExecuteFile(playFile, "./", outDir, inputs); err != nil { + if err := ExecuteFile(playFile, "./", outDir, params); err != nil { return err } } @@ -63,6 +63,6 @@ var Cmd = &cobra.Command{ func init() { flags := Cmd.Flags() flags.BoolVarP(&verbose, "verbose", "v", verbose, "Verbose logging") - flags.StringToStringVarP(&cmdInputs, "config", "c", cmdInputs, "Config variable") - flags.StringVarP(&inputFile, "configFile", "f", inputFile, "Config file") + flags.StringToStringVarP(&cmdParams, "param", "p", cmdParams, "Parameter variable") + flags.StringVarP(¶msFile, "params-file", "f", paramsFile, "Parameter file") } diff --git a/cmd/run/run.go b/cmd/run/run.go index a5607a6..42651b2 100644 --- a/cmd/run/run.go +++ b/cmd/run/run.go @@ -22,7 +22,7 @@ func ExecuteFile(playFile string, workDir string, outDir string, inputs map[stri return Execute(pb, baseDir, workDir, outDir, inputs) } -func Execute(pb playbook.Playbook, baseDir string, workDir string, outDir string, inputs map[string]string) error { +func Execute(pb playbook.Playbook, baseDir string, workDir string, outDir string, params map[string]string) error { if outDir == "" { outDir = pb.GetDefaultOutDir() @@ -32,7 +32,7 @@ func Execute(pb playbook.Playbook, baseDir string, workDir string, outDir string os.MkdirAll(outDir, 0777) } - nInputs, err := pb.PrepConfig(inputs, workDir) + nInputs, err := pb.PrepConfig(params, workDir) if err != nil { return err } diff --git a/cmd/scan/main.go b/cmd/scan/main.go index f1fbe48..bfb17b5 100644 --- a/cmd/scan/main.go +++ b/cmd/scan/main.go @@ -140,7 +140,7 @@ var ScriptCommand = &cobra.Command{ inputs := []string{} outputs := []string{} - for _, p := range pb.GetConfigFields() { + for _, p := range pb.GetRequiredParams() { if p.IsDir() || p.IsFile() { inputs = append(inputs, config[p.Name]) } diff --git a/config/config.go b/config/config.go index f1b3ac2..79c5931 100644 --- a/config/config.go +++ b/config/config.go @@ -2,33 +2,41 @@ package config import "strings" -type Config map[string]*string +type Params map[string]Param -type Type string - -const ( - Unknown Type = "" - File Type = "File" - Dir Type = "Dir" -) +type Param struct { + Type string `json:"type"` + Default any `json:"default,omitempty"` +} -type Variable struct { +type ParamRequest struct { + Type string `json:"type"` Name string `json:"name"` - Type Type } type Configurable interface { - GetConfigFields() []Variable + GetRequiredParams() []ParamRequest } -func (in *Variable) IsFile() bool { - return in.Type == File +func (in *Param) IsFile() bool { + return strings.ToLower(in.Type) == "file" } -func (in *Variable) IsDir() bool { - return in.Type == Dir +func (in *Param) IsDir() bool { + return strings.ToLower(in.Type) == "path" } func TrimPrefix(s string) string { - return strings.TrimPrefix(s, "config.") + if strings.HasPrefix(s, "params.") { + return strings.TrimPrefix(s, "params.") + } + return s +} + +func (in *ParamRequest) IsFile() bool { + return strings.ToLower(in.Type) == "file" +} + +func (in *ParamRequest) IsDir() bool { + return strings.ToLower(in.Type) == "path" } diff --git a/Playbook.md b/docs/docs/Playbook.md similarity index 98% rename from Playbook.md rename to docs/docs/Playbook.md index d21bce5..40567d9 100644 --- a/Playbook.md +++ b/docs/docs/Playbook.md @@ -64,12 +64,12 @@ any parameters. However, to apply this playbook to a new input file, the input parameter `zipcode` could be used to define the source file. ``` -config: +params: schema: - type: Dir + type: path default: ../covid19_datadictionary/gdcdictionary/schemas/ zipcode: - type: File + type: file default: ../data/ZIP-COUNTY-FIPS_2017-06.csv ``` @@ -78,13 +78,13 @@ only one input, which is to run the table loader. ``` inputs: tableLoad: - input: "{{config.zipcode}}" + input: "{{params.zipcode}}" sep: "," ``` Tableload operaters of the input file that was originally passed in using the `inputs` stanza. SIFTER string parsing is based on mustache template system. -To access the string passed in the template is `{{config.zipcode}}`. +To access the string passed in the template is `{{params.zipcode}}`. The seperator in the file input file is a `,` so that is also passed in as a parameter to the extractor. @@ -366,7 +366,7 @@ An array of Extractors, each defining a different extraction step ``` - desc: Untar the input file untar: - input: "{{inputs.tar}}" + input: "{{params.tar}}" - desc: Loading Patient List tableLoad: input: data_clinical_patient.txt @@ -458,7 +458,7 @@ An array of Extractors, each defining a different extraction step ``` - desc: Convert Census File jsonLoad: - input: "{{inputs.census}}" + input: "{{params.census}}" transform: ... ``` @@ -614,7 +614,7 @@ The transform: ``` - tableReplace: - input: "{{inputs.stateTable}}" + input: "{{params.stateTable}}" field: sub_region_1 ``` diff --git a/evaluate/template.go b/evaluate/template.go index ba0f859..91f8fa7 100644 --- a/evaluate/template.go +++ b/evaluate/template.go @@ -20,8 +20,8 @@ func init() { }) } -func ExpressionString(expression string, config map[string]string, row map[string]interface{}) (string, error) { - d := map[string]interface{}{"config": config} +func ExpressionString(expression string, params map[string]string, row map[string]interface{}) (string, error) { + d := map[string]interface{}{"params": params} if row != nil { d["row"] = row } diff --git a/examples/cbio.yaml b/examples/cbio.yaml index 8ce5e5c..ea36b31 100644 --- a/examples/cbio.yaml +++ b/examples/cbio.yaml @@ -2,7 +2,7 @@ class: sifter name: CBioPortal -config: +params: tar: "" geneTable: "" schema: bmeg-dictionary/gdcdictionary/schemas @@ -10,7 +10,7 @@ config: inputs: untar: plugin: - commandLine: tar -xzf {{config.tar}} + commandLine: tar -xzf {{params.tar}} patientReader: tableLoad: @@ -41,7 +41,7 @@ pipelines: submitter_id: "TCGA" - objectValidate: title: Case - schema: "{{config.schema}}" + schema: "{{params.schema}}" - emit: name: case @@ -56,7 +56,7 @@ pipelines: type: "sample" - objectValidate: title: Sample - schema: "{{config.schema}}" + schema: "{{params.schema}}" - emit: name: sample @@ -71,7 +71,7 @@ pipelines: type: "aliquot" - objectValidate: title: Aliquot - schema: "{{config.schema}}" + schema: "{{params.schema}}" - emit: name: aliquot @@ -83,7 +83,7 @@ pipelines: aliquot_id: "{{row.Entrez_Gene_Id}}-0000" - lookup: tsv: - input: "{{config.geneTable}}" + input: "{{params.geneTable}}" lookup: "{{row.Entrez_Gene_Id}}" - map: method: nodeMap @@ -120,7 +120,7 @@ pipelines: ensembl_transcript: "{{row.Transcript_ID}}" - objectValidate: title: SomaticVariant - schema: "{{config.schema}}" + schema: "{{params.schema}}" - emit: name: somatic_variant @@ -139,6 +139,6 @@ pipelines: effect: "{{row.Variant_Classification}}" - objectValidate: title: Allele - schema: "{{config.schema}}" + schema: "{{params.schema}}" - emit: name: allele diff --git a/examples/gdc-convert.yaml b/examples/gdc-convert.yaml index 861b83c..c5c16ee 100644 --- a/examples/gdc-convert.yaml +++ b/examples/gdc-convert.yaml @@ -3,8 +3,10 @@ class: sifter name: GDCConvert -config: - schema: bmeg-dictionary/gdcdictionary/schemas +params: + schema: + type: path + default: bmeg-dictionary/gdcdictionary/schemas inputs: projects_scrape: @@ -29,7 +31,7 @@ pipelines: programs: "{{row.program.name}}" - objectValidate: title: project - schema: "{{config.schema}}" + schema: "{{params.schema}}" - emit: name: project @@ -44,7 +46,7 @@ pipelines: type: experiment - objectValidate: title: experiment - schema: "{{config.schema}}" + schema: "{{params.schema}}" - emit: name: experiment @@ -57,7 +59,7 @@ pipelines: type: case - objectValidate: title: case - schema: "{{config.schema}}" + schema: "{{params.schema}}" - emit: name: case @@ -71,7 +73,7 @@ pipelines: id: "{{row.sample_id}}" - objectValidate: title: sample - schema: "{{config.schema}}" + schema: "{{params.schema}}" - emit: name: sample @@ -91,6 +93,6 @@ pipelines: id: "{{row.aliquot_id}}" - objectValidate: title: aliquot - schema: "{{config.schema}}" + schema: "{{params.schema}}" - emit: name: aliquot diff --git a/examples/gene-table.yaml b/examples/gene-table.yaml index 65d2956..5126308 100644 --- a/examples/gene-table.yaml +++ b/examples/gene-table.yaml @@ -2,13 +2,13 @@ class: sifter name: gene-table -config: +params: geneTSV: ftp://ftp.ncbi.nih.gov/gene/DATA/gene2ensembl.gz inputs: geneReader: tableLoad: - input: "{{config.geneTSV}}" + input: "{{params.geneTSV}}" columns: - tax_id - GeneID diff --git a/examples/genome.yaml b/examples/genome.yaml index 67ec9ea..510ee81 100644 --- a/examples/genome.yaml +++ b/examples/genome.yaml @@ -2,14 +2,14 @@ class: sifter name: RefGenome -config: +params: gtfPath: ftp://ftp.ensembl.org/pub/grch37/release-96/gff3/homo_sapiens/Homo_sapiens.GRCh37.87.gff3.gz schema: ./bmeg-dictionary/gdcdictionary/schemas inputs: gtfReader: tableLoad: - input: "{{config.gtfPath}}" + input: "{{params.gtfPath}}" columns: - seqid - source @@ -54,7 +54,7 @@ pipelines: return x - objectValidate: title: exon - schema: "{{config.schema}}" + schema: "{{params.schema}}" - emit: name: exon @@ -68,7 +68,7 @@ pipelines: gene_id: "{{row.gene_id}}" - objectValidate: title: gene - schema: "{{config.schema}}" + schema: "{{params.schema}}" - emit: name: gene @@ -87,6 +87,6 @@ pipelines: transcript_id: "{{row.transcript_id}}" - objectValidate: title: transcript - schema: "{{config.schema}}" + schema: "{{params.schema}}" - emit: name: transcript diff --git a/examples/hugo-ensembl.yaml b/examples/hugo-ensembl.yaml index 08783cd..295f68d 100644 --- a/examples/hugo-ensembl.yaml +++ b/examples/hugo-ensembl.yaml @@ -2,13 +2,13 @@ class: sifter name: hugo-ensembl -config: +params: hugoJSON: ftp://ftp.ebi.ac.uk/pub/databases/genenames/hgnc/json/locus_types/gene_with_protein_product.json inputs: hugoReader: jsonLoad: - input: "{{config.hugoJSON}}" + input: "{{params.hugoJSON}}" pipelines: transform: diff --git a/extractors/avro_load.go b/extractors/avro_load.go index dc2975d..66d4615 100644 --- a/extractors/avro_load.go +++ b/extractors/avro_load.go @@ -57,10 +57,10 @@ func (ml *AvroLoadStep) Start(task task.RuntimeTask) (chan map[string]interface{ return procChan, nil } -func (ml *AvroLoadStep) GetConfigFields() []config.Variable { - out := []config.Variable{} +func (ml *AvroLoadStep) GetRequiredParams() []config.ParamRequest { + out := []config.ParamRequest{} for _, s := range evaluate.ExpressionIDs(ml.Input) { - out = append(out, config.Variable{Type: "File", Name: config.TrimPrefix(s)}) + out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } return out } diff --git a/extractors/glob_load.go b/extractors/glob_load.go index 8c57c01..7f61a2d 100644 --- a/extractors/glob_load.go +++ b/extractors/glob_load.go @@ -97,10 +97,10 @@ func (gl *GlobLoadStep) Start(task task.RuntimeTask) (chan map[string]interface{ return nil, fmt.Errorf("not found") } -func (gl *GlobLoadStep) GetConfigFields() []config.Variable { - out := []config.Variable{} +func (gl *GlobLoadStep) GetRequiredParams() []config.ParamRequest { + out := []config.ParamRequest{} for _, s := range evaluate.ExpressionIDs(gl.Input) { - out = append(out, config.Variable{Type: "File", Name: config.TrimPrefix(s)}) + out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } return out } diff --git a/extractors/interface.go b/extractors/interface.go index 33d2892..6b1d6ed 100644 --- a/extractors/interface.go +++ b/extractors/interface.go @@ -44,15 +44,15 @@ func (ex *Extractor) Start(t task.RuntimeTask) (chan map[string]interface{}, err return nil, fmt.Errorf(("Extractor not defined")) } -func (ex *Extractor) GetConfigFields() []config.Variable { - out := []config.Variable{} +func (ex *Extractor) GetRequiredParams() []config.ParamRequest { + out := []config.ParamRequest{} v := reflect.ValueOf(ex).Elem() for i := 0; i < v.NumField(); i++ { f := v.Field(i) x := f.Interface() if z, ok := x.(config.Configurable); ok { if !f.IsNil() { - out = append(out, z.GetConfigFields()...) + out = append(out, z.GetRequiredParams()...) } } } diff --git a/extractors/json_load.go b/extractors/json_load.go index 6f4ccb6..a37c297 100644 --- a/extractors/json_load.go +++ b/extractors/json_load.go @@ -67,10 +67,10 @@ func (ml *JSONLoadStep) Start(task task.RuntimeTask) (chan map[string]interface{ return procChan, nil } -func (ml *JSONLoadStep) GetConfigFields() []config.Variable { - out := []config.Variable{} +func (ml *JSONLoadStep) GetRequiredParams() []config.ParamRequest { + out := []config.ParamRequest{} for _, s := range evaluate.ExpressionIDs(ml.Input) { - out = append(out, config.Variable{Type: "File", Name: config.TrimPrefix(s)}) + out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } return out } diff --git a/extractors/plugin_load.go b/extractors/plugin_load.go index 74893cb..f6c38e5 100644 --- a/extractors/plugin_load.go +++ b/extractors/plugin_load.go @@ -81,10 +81,10 @@ func (ml *PluginLoadStep) Start(task task.RuntimeTask) (chan map[string]interfac return procChan, nil } -func (ml *PluginLoadStep) GetConfigFields() []config.Variable { - out := []config.Variable{} +func (ml *PluginLoadStep) GetRequiredParams() []config.ParamRequest { + out := []config.ParamRequest{} for _, s := range evaluate.ExpressionIDs(ml.CommandLine) { - out = append(out, config.Variable{Type: "File", Name: config.TrimPrefix(s)}) + out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } return out } diff --git a/extractors/sqldump_step.go b/extractors/sqldump_step.go index dd06014..cf90c20 100644 --- a/extractors/sqldump_step.go +++ b/extractors/sqldump_step.go @@ -114,10 +114,10 @@ func (ml *SQLDumpStep) Start(task task.RuntimeTask) (chan map[string]interface{} return out, nil } -func (ml *SQLDumpStep) GetConfigFields() []config.Variable { - out := []config.Variable{} +func (ml *SQLDumpStep) GetRequiredParams() []config.ParamRequest { + out := []config.ParamRequest{} for _, s := range evaluate.ExpressionIDs(ml.Input) { - out = append(out, config.Variable{Type: "File", Name: config.TrimPrefix(s)}) + out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } return out } diff --git a/extractors/sqlite_load.go b/extractors/sqlite_load.go index de38204..4877d42 100644 --- a/extractors/sqlite_load.go +++ b/extractors/sqlite_load.go @@ -64,10 +64,10 @@ func (ml *SQLiteStep) Start(task task.RuntimeTask) (chan map[string]interface{}, return procChan, nil } -func (ml *SQLiteStep) GetConfigFields() []config.Variable { - out := []config.Variable{} +func (ml *SQLiteStep) GetRequiredParams() []config.ParamRequest { + out := []config.ParamRequest{} for _, s := range evaluate.ExpressionIDs(ml.Input) { - out = append(out, config.Variable{Type: "File", Name: config.TrimPrefix(s)}) + out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } return out } diff --git a/extractors/tabular_load.go b/extractors/tabular_load.go index 5f8b9f4..cceb8de 100644 --- a/extractors/tabular_load.go +++ b/extractors/tabular_load.go @@ -160,10 +160,10 @@ func (ml *TableLoadStep) Start(task task.RuntimeTask) (chan map[string]interface return procChan, nil } -func (ml *TableLoadStep) GetConfigFields() []config.Variable { - out := []config.Variable{} +func (ml *TableLoadStep) GetRequiredParams() []config.ParamRequest { + out := []config.ParamRequest{} for _, s := range evaluate.ExpressionIDs(ml.Input) { - out = append(out, config.Variable{Type: "File", Name: config.TrimPrefix(s)}) + out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } return out } diff --git a/extractors/transpose_load.go b/extractors/transpose_load.go index 436a064..95cf765 100644 --- a/extractors/transpose_load.go +++ b/extractors/transpose_load.go @@ -48,10 +48,10 @@ func (ml *TransposeLoadStep) Start(task task.RuntimeTask) (chan map[string]inter return out, nil } -func (ml *TransposeLoadStep) GetConfigFields() []config.Variable { - out := []config.Variable{} +func (ml *TransposeLoadStep) GetRequiredParams() []config.ParamRequest { + out := []config.ParamRequest{} for _, s := range evaluate.ExpressionIDs(ml.Input) { - out = append(out, config.Variable{Type: "File", Name: config.TrimPrefix(s)}) + out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } return out } diff --git a/extractors/xml_step.go b/extractors/xml_step.go index d7653d0..fd10086 100644 --- a/extractors/xml_step.go +++ b/extractors/xml_step.go @@ -110,10 +110,10 @@ func (ml *XMLLoadStep) Start(task task.RuntimeTask) (chan map[string]any, error) return procChan, nil } -func (ml *XMLLoadStep) GetConfigFields() []config.Variable { - out := []config.Variable{} +func (ml *XMLLoadStep) GetRequiredParams() []config.ParamRequest { + out := []config.ParamRequest{} for _, s := range evaluate.ExpressionIDs(ml.Input) { - out = append(out, config.Variable{Type: "File", Name: config.TrimPrefix(s)}) + out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } return out } diff --git a/playbook/execute.go b/playbook/execute.go index c312205..335cea1 100644 --- a/playbook/execute.go +++ b/playbook/execute.go @@ -21,35 +21,62 @@ func fileExists(filename string) bool { } */ -func (pb *Playbook) PrepConfig(inputs map[string]string, workdir string) (map[string]string, error) { - workdir, _ = filepath.Abs(workdir) - missing := map[string]bool{} - out := map[string]string{} - for _, v := range pb.GetConfigFields() { - if val, ok := inputs[v.Name]; ok { +func (pb *Playbook) PrepConfig(inputParams map[string]string, workdir string) (map[string]string, error) { + + playbookParams := map[string]string{} + for k, v := range pb.Params { + if _, ok := inputParams[k]; ok { if v.IsFile() || v.IsDir() { - var defaultPath = val - if !filepath.IsAbs(val) { - defaultPath = filepath.Join(workdir, val) + var defaultPath = inputParams[k] + if !filepath.IsAbs(inputParams[k]) { + defaultPath = filepath.Join(workdir, inputParams[k]) + } + playbookParams[k], _ = filepath.Abs(defaultPath) + } else { + playbookParams[k] = inputParams[k] + } + } else { + if v.Default != nil { + if v.IsFile() || v.IsDir() { + var defaultPath = fmt.Sprintf("%v", v.Default) + if !filepath.IsAbs(defaultPath) { + dirPath := filepath.Dir(pb.path) + defaultPath = filepath.Join(dirPath, defaultPath) + } + playbookParams[k], _ = filepath.Abs(defaultPath) + } else { + playbookParams[k] = fmt.Sprintf("%v", v.Default) } - out[v.Name], _ = filepath.Abs(defaultPath) } else { - out[v.Name] = val + return nil, fmt.Errorf("parameter %s not defined", k) } + } + } + + workdir, _ = filepath.Abs(workdir) + missing := map[string]bool{} + out := map[string]string{} + for _, v := range pb.GetRequiredParams() { + if val, ok := playbookParams[v.Name]; ok { + out[v.Name] = val logger.Debug("input: ", v.Name, out[v.Name]) - } else if val, ok := pb.Config[v.Name]; ok { - if val != nil { + } else if p, ok := pb.Params[v.Name]; ok { + if p.Default != nil { + val := fmt.Sprintf("%v", p.Default) if v.IsFile() || v.IsDir() { - defaultPath := filepath.Join(filepath.Dir(pb.path), *val) + var defaultPath = val + if !filepath.IsAbs(val) { + defaultPath = filepath.Join(filepath.Dir(pb.path), val) + } out[v.Name], _ = filepath.Abs(defaultPath) } else { - out[v.Name] = *val + out[v.Name] = val } } else { missing[v.Name] = true } } else { - return nil, fmt.Errorf("config %s not defined", v.Name) + return nil, fmt.Errorf("parameter %s not defined", v.Name) } } if len(missing) > 0 { @@ -59,6 +86,7 @@ func (pb *Playbook) PrepConfig(inputs map[string]string, workdir string) (map[st } return nil, fmt.Errorf("missing inputs: %s", strings.Join(o, ",")) } + logger.Debug("prep config inputs", "config", out) return out, nil } diff --git a/playbook/inspect.go b/playbook/inspect.go index 5c2feb6..2216a55 100644 --- a/playbook/inspect.go +++ b/playbook/inspect.go @@ -8,16 +8,16 @@ import ( "github.com/bmeg/sifter/task" ) -func (pb *Playbook) GetConfigFields() []config.Variable { - out := []config.Variable{} +func (pb *Playbook) GetRequiredParams() []config.ParamRequest { + out := []config.ParamRequest{} for _, v := range pb.Inputs { - out = append(out, v.GetConfigFields()...) + out = append(out, v.GetRequiredParams()...) } for _, v := range pb.Pipelines { for _, s := range v { - out = append(out, s.GetConfigFields()...) + out = append(out, s.GetRequiredParams()...) } } diff --git a/playbook/load.go b/playbook/load.go index a3b6b9c..379d96a 100644 --- a/playbook/load.go +++ b/playbook/load.go @@ -23,7 +23,7 @@ type Playbook struct { MemMB int `json:"memMB"` //annotation of potential memory usage, for build Snakefile Docs string `json:"docs"` Outdir string `json:"outdir"` - Config config.Config `json:"config,omitempty" jsonschema_description:"Configuration for Playbook"` + Params config.Params `json:"params,omitempty" jsonschema_description:"Parameters for Playbook"` Inputs map[string]extractors.Extractor `json:"inputs" jsonschema_description:"Steps of the transformation"` Pipelines map[string]transform.Pipe `json:"pipelines"` path string diff --git a/test/examples/gdc/gdc-convert.yaml b/test/examples/gdc/gdc-convert.yaml index e3358f6..a26ac30 100644 --- a/test/examples/gdc/gdc-convert.yaml +++ b/test/examples/gdc/gdc-convert.yaml @@ -2,14 +2,18 @@ name: gdc outdir: output/ -config: - cases: ../../resources/gdc-case.json.gz - schema: ../../resources/schemas +params: + cases: + type: file + default: ../../resources/gdc-case.json.gz + schema: + type: path + default: ../../resources/schemas inputs: caseData: jsonLoad: - input: "{{config.cases}}" + input: "{{params.cases}}" pipelines: caseObject: @@ -21,7 +25,7 @@ pipelines: type: case - objectValidate: title: Case - schema: "{{config.schema}}" + schema: "{{params.schema}}" - emit: # Testing that this doesn't do anything useName: False @@ -30,7 +34,7 @@ pipelines: caseGraph: - from: caseObject - graphBuild: - schema: "{{config.schema}}" + schema: "{{params.schema}}" title: Case EdgeFix: method: test diff --git a/test/examples/gene-table/gene-table.yaml b/test/examples/gene-table/gene-table.yaml index 0df1f11..833402d 100644 --- a/test/examples/gene-table/gene-table.yaml +++ b/test/examples/gene-table/gene-table.yaml @@ -6,13 +6,15 @@ docs: > This takes a Gene TSV, filters rows, selects columns and outputs a 2 column TSV into the working directory -config: - geneTSV: ../../resources/gene2ensembl.gz +params: + geneTSV: + type: File + default: ../../resources/gene2ensembl.gz inputs: genes: tableLoad: - input: "{{config.geneTSV}}" + input: "{{params.geneTSV}}" columns: - tax_id - GeneID diff --git a/test/examples/lookup/inline-table.yaml b/test/examples/lookup/inline-table.yaml index 2bcae4f..7d98ca5 100644 --- a/test/examples/lookup/inline-table.yaml +++ b/test/examples/lookup/inline-table.yaml @@ -1,14 +1,16 @@ outdir: output/ -config: - json: ../../resources/projects.json +params: + json: + type: File + default: ../../resources/projects.json inputs: jsonData: jsonLoad: - input: "{{config.json}}" + input: "{{params.json}}" pipelines: diff --git a/test/examples/lookup/tsv-table-replace.yaml b/test/examples/lookup/tsv-table-replace.yaml index 1d38d73..2adc83e 100644 --- a/test/examples/lookup/tsv-table-replace.yaml +++ b/test/examples/lookup/tsv-table-replace.yaml @@ -3,14 +3,18 @@ name: gdc-projects outdir: output-tsv/ -config: - cases: ../../resources/case.json - diseaseTSV: ../../resources/disease_table.tsv +params: + cases: + type: File + default: ../../resources/case.json + diseaseTSV: + type: File + default: ../../resources/disease_table.tsv inputs: caseReader: jsonLoad: - input: "{{config.cases}}" + input: "{{params.cases}}" pipelines: tranform: @@ -20,7 +24,7 @@ pipelines: - lookup: replace: disease_type tsv: - input: "{{config.diseaseTSV}}" + input: "{{params.diseaseTSV}}" header: - disease - mondo diff --git a/test/examples/pathwaycommons/gene_collect.yaml b/test/examples/pathwaycommons/gene_collect.yaml index 85fb3ab..3f84cf5 100644 --- a/test/examples/pathwaycommons/gene_collect.yaml +++ b/test/examples/pathwaycommons/gene_collect.yaml @@ -1,13 +1,15 @@ outdir: output_gene -config: - sifFile: ../../resources/pathways.sif +params: + sifFile: + type: File + default: ../../resources/pathways.sif inputs: sifFile: tableLoad: - input: "{{config.sifFile}}" + input: "{{params.sifFile}}" sep: "\t" columns: [_from, _label, _to] diff --git a/test/examples/pathwaycommons/pathway_commons.yaml b/test/examples/pathwaycommons/pathway_commons.yaml index d8d2c52..4a16fa1 100644 --- a/test/examples/pathwaycommons/pathway_commons.yaml +++ b/test/examples/pathwaycommons/pathway_commons.yaml @@ -2,13 +2,15 @@ name: pathway_commons outdir: output -config: - sifFile: ../../resources/pathways.sif +params: + sifFile: + type: File + default: ../../resources/pathways.sif inputs: sifFile: tableLoad: - input: "{{config.sifFile}}" + input: "{{params.sifFile}}" sep: "\t" columns: [_from, _label, _to] diff --git a/test/examples/pfb/transform.yaml b/test/examples/pfb/transform.yaml index 5847205..63687f4 100644 --- a/test/examples/pfb/transform.yaml +++ b/test/examples/pfb/transform.yaml @@ -1,13 +1,15 @@ -config: - file: ../../resources/1000G.pfb.avro +params: + file: + type: file + default: ../../resources/1000G.pfb.avro outdir: output/ inputs: pfb: avroLoad: - input: "{{config.file}}" + input: "{{params.file}}" pipelines: transform: diff --git a/test/resources/project.yaml b/test/resources/project.yaml index 74d68f5..da59a43 100644 --- a/test/resources/project.yaml +++ b/test/resources/project.yaml @@ -2,13 +2,15 @@ class: Playbook -config: - genes: ./gene_with_protein_product.json +params: + genes: + type: File + default: ./gene_with_protein_product.json inputs: geneData: jsonLoad: - input: "{{config.genes}}" + input: "{{params.genes}}" pipelines: diff --git a/transform/graph_build.go b/transform/graph_build.go index d92bbfe..8bb6458 100644 --- a/transform/graph_build.go +++ b/transform/graph_build.go @@ -66,11 +66,11 @@ func (ts GraphBuildStep) Init(task task.RuntimeTask) (Processor, error) { return &graphBuildProcess{ts, task, sc, ts.Title, edgeFix, 0, 0, 0}, nil } -func (ts GraphBuildStep) GetConfigFields() []config.Variable { - out := []config.Variable{} +func (ts GraphBuildStep) GetRequiredParams() []config.ParamRequest { + out := []config.ParamRequest{} if ts.Schema != "" { for _, s := range evaluate.ExpressionIDs(ts.Schema) { - out = append(out, config.Variable{Type: config.Dir, Name: config.TrimPrefix(s)}) + out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } } return out diff --git a/transform/interface.go b/transform/interface.go index 633da96..6795ad4 100644 --- a/transform/interface.go +++ b/transform/interface.go @@ -99,15 +99,15 @@ func (ts Step) Init(t task.RuntimeTask) (Processor, error) { return nil, fmt.Errorf(("Transform not defined")) } -func (ts Step) GetConfigFields() []config.Variable { - out := []config.Variable{} +func (ts Step) GetRequiredParams() []config.ParamRequest { + out := []config.ParamRequest{} v := reflect.ValueOf(ts) for i := 0; i < v.NumField(); i++ { f := v.Field(i) x := f.Interface() if z, ok := x.(config.Configurable); ok { if !f.IsNil() { - out = append(out, z.GetConfigFields()...) + out = append(out, z.GetRequiredParams()...) } } } diff --git a/transform/lookup.go b/transform/lookup.go index 0d1a506..135b242 100644 --- a/transform/lookup.go +++ b/transform/lookup.go @@ -101,15 +101,15 @@ func (tr *LookupStep) Init(task task.RuntimeTask) (Processor, error) { return nil, fmt.Errorf("table input not defined") } -func (tr *LookupStep) GetConfigFields() []config.Variable { - out := []config.Variable{} +func (tr *LookupStep) GetRequiredParams() []config.ParamRequest { + out := []config.ParamRequest{} if tr.TSV != nil && tr.TSV.Input != "" { for _, s := range evaluate.ExpressionIDs(tr.TSV.Input) { - out = append(out, config.Variable{Type: config.File, Name: config.TrimPrefix(s)}) + out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } } else if tr.JSON != nil && tr.JSON.Input != "" { for _, s := range evaluate.ExpressionIDs(tr.JSON.Input) { - out = append(out, config.Variable{Type: config.File, Name: config.TrimPrefix(s)}) + out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } } return out diff --git a/transform/lookup_interval.go b/transform/lookup_interval.go index 8ebd661..5d4cb46 100644 --- a/transform/lookup_interval.go +++ b/transform/lookup_interval.go @@ -97,11 +97,11 @@ func toInt64(v any) int64 { return 0 } -func (tr *IntervalStep) GetConfigFields() []config.Variable { - out := []config.Variable{} +func (tr *IntervalStep) GetRequiredParams() []config.ParamRequest { + out := []config.ParamRequest{} if tr.JSON != nil && tr.JSON.Input != "" { for _, s := range evaluate.ExpressionIDs(tr.JSON.Input) { - out = append(out, config.Variable{Type: config.File, Name: config.TrimPrefix(s)}) + out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } } return out diff --git a/transform/object_validate.go b/transform/object_validate.go index 413c803..69f32c2 100644 --- a/transform/object_validate.go +++ b/transform/object_validate.go @@ -54,11 +54,11 @@ func (ts ObjectValidateStep) Init(task task.RuntimeTask) (Processor, error) { return nil, fmt.Errorf("class not configured") } -func (ts ObjectValidateStep) GetConfigFields() []config.Variable { - out := []config.Variable{} +func (ts ObjectValidateStep) GetRequiredParams() []config.ParamRequest { + out := []config.ParamRequest{} if ts.Schema != "" { for _, s := range evaluate.ExpressionIDs(ts.Schema) { - out = append(out, config.Variable{Type: config.Dir, Name: config.TrimPrefix(s)}) + out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } } return out diff --git a/transform/project.go b/transform/project.go index 02bf0c7..a836fb3 100644 --- a/transform/project.go +++ b/transform/project.go @@ -22,13 +22,13 @@ func (pr ProjectStep) Init(t task.RuntimeTask) (Processor, error) { return &projectStepProcess{pr, t}, nil } -func (pr ProjectStep) GetConfigFields() []config.Variable { - out := []config.Variable{} +func (pr ProjectStep) GetRequiredParams() []config.ParamRequest { + out := []config.ParamRequest{} for _, v := range pr.Mapping { t := scanIds(v) for i := range t { if strings.HasPrefix(t[i], "config.") { - out = append(out, config.Variable{Name: config.TrimPrefix(t[i])}) + out = append(out, config.ParamRequest{Name: config.TrimPrefix(t[i])}) } } } From 899302fc0ed0b4828195f5c95b346810d8fbdd2d Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Sun, 18 Jan 2026 21:30:48 -0800 Subject: [PATCH 03/19] Working on docs --- docs/.hugo_build.lock | 0 docs/docs/config.md | 38 +++++++++++++++++ docs/docs/example.md | 8 ---- docs/{docs.md => docs/index.md} | 10 +---- docs/docs/{inputs.md => inputs/index.md} | 0 docs/docs/{Playbook.md => playbook.md} | 0 .../{Schema_Description.md => docs/schema.md} | 24 ++++++++++- docs/docs/transforms.md | 9 ---- docs/docs/transforms/debug.md | 4 -- docs/docs/transforms/flatmap.md | 41 ++++++++++++++++++- docs/{_index.md => index.md} | 2 - mkdocs.yml | 18 ++++++++ 12 files changed, 119 insertions(+), 35 deletions(-) delete mode 100644 docs/.hugo_build.lock create mode 100644 docs/docs/config.md rename docs/{docs.md => docs/index.md} (96%) rename docs/docs/{inputs.md => inputs/index.md} (100%) rename docs/docs/{Playbook.md => playbook.md} (100%) rename docs/{Schema_Description.md => docs/schema.md} (91%) delete mode 100644 docs/docs/transforms.md rename docs/{_index.md => index.md} (88%) create mode 100644 mkdocs.yml diff --git a/docs/.hugo_build.lock b/docs/.hugo_build.lock deleted file mode 100644 index e69de29..0000000 diff --git a/docs/docs/config.md b/docs/docs/config.md new file mode 100644 index 0000000..7b79b91 --- /dev/null +++ b/docs/docs/config.md @@ -0,0 +1,38 @@ +--- +title: Configuration Reference +--- + +## Configuration Variables + +Configuration variables allow playbooks to be parameterized. They are defined in the `config` section of the playbook YAML file. + +### Configuration Syntax +```yaml +config: + variableName: + type: File # or Dir + default: "path/to/default" +``` + +### Supported Types +- `File`: Represents a file path +- `Dir`: Represents a directory path + +### Example Configuration +```yaml +config: + inputDir: + type: Dir + default: "/data/input" + outputDir: + type: Dir + default: "/data/output" + schemaFile: + type: File + default: "/config/schema.json" +``` + +### Best Practices +1. Use descriptive names for configuration variables +2. Provide reasonable default values +3. Document all configuration variables in your playbook's documentation section \ No newline at end of file diff --git a/docs/docs/example.md b/docs/docs/example.md index d1d29b0..d1b4dbe 100644 --- a/docs/docs/example.md +++ b/docs/docs/example.md @@ -1,11 +1,3 @@ ---- -title: Example -menu: - main: - identifier: example - weight: 3 ---- - # Example Pipeline Our first task will be to convert a ZIP code TSV into a set of county level diff --git a/docs/docs.md b/docs/docs/index.md similarity index 96% rename from docs/docs.md rename to docs/docs/index.md index d45d044..0dcdc28 100644 --- a/docs/docs.md +++ b/docs/docs/index.md @@ -1,15 +1,7 @@ ---- -title: Overview -menu: - main: - identifier: overview - weight: 1 ---- - # Sifter pipelines -Sifter pipelines process steams of nested JSON messages. Sifter comes with a number of +Sifter pipelines process streams of nested JSON messages. Sifter comes with a number of file extractors that operate as inputs to these pipelines. The pipeline engine connects togeather arrays of transform steps into directed acylic graph that is processed in parallel. diff --git a/docs/docs/inputs.md b/docs/docs/inputs/index.md similarity index 100% rename from docs/docs/inputs.md rename to docs/docs/inputs/index.md diff --git a/docs/docs/Playbook.md b/docs/docs/playbook.md similarity index 100% rename from docs/docs/Playbook.md rename to docs/docs/playbook.md diff --git a/docs/Schema_Description.md b/docs/docs/schema.md similarity index 91% rename from docs/Schema_Description.md rename to docs/docs/schema.md index 71d43cb..dbe1ddd 100644 --- a/docs/Schema_Description.md +++ b/docs/docs/schema.md @@ -20,8 +20,10 @@ A Playbook is a YAML file that defines an ETL pipeline. ## Configuration Variables (`config`) -Configuration variables allow playbooks to be parameterized. +Configuration variables allow playbooks to be parameterized. They are defined in the `config` section of the playbook YAML file. + +### Configuration Syntax ```yaml config: variableName: @@ -29,7 +31,25 @@ config: default: "path/to/default" ``` ---- +### Supported Types +- `File`: Represents a file path +- `Dir`: Represents a directory path + +### Example Configuration +```yaml +config: + inputDir: + type: Dir + default: "./data/input" + outputDir: + type: Dir + default: "./data/output" + schemaFile: + type: File + default: "./config/schema.json" +``` + + ## Input Methods (Extractors) diff --git a/docs/docs/transforms.md b/docs/docs/transforms.md deleted file mode 100644 index 2365e78..0000000 --- a/docs/docs/transforms.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: Pipeline Steps -menu: - main: - identifier: transforms - weight: 5 ---- - -Transforms alter the data \ No newline at end of file diff --git a/docs/docs/transforms/debug.md b/docs/docs/transforms/debug.md index 5c87eae..e8479aa 100644 --- a/docs/docs/transforms/debug.md +++ b/docs/docs/transforms/debug.md @@ -1,9 +1,5 @@ --- title: debug -menu: - main: - parent: transforms - weight: 100 --- # debug diff --git a/docs/docs/transforms/flatmap.md b/docs/docs/transforms/flatmap.md index c880db2..e4a096a 100644 --- a/docs/docs/transforms/flatmap.md +++ b/docs/docs/transforms/flatmap.md @@ -3,5 +3,44 @@ title: flatMap menu: main: parent: transforms - weight: 100 + weight: 15 --- + +# flatMap + +Flatten an array field into separate messages, each containing a single element of the array. + +## Parameters + +| Parameter | Type | Description | +|-----------|--------|------------| +| `field` | string | Path to the array field to be flattened (e.g., `{{row.samples}}`). | +| `dest` | string | Optional name of the field to store the flattened element (defaults to the same field name). | +| `keep` | bool | If `true`, keep the original array alongside the flattened messages. | + +## Example + +```yaml +- flatMap: + field: "{{row.samples}}" + dest: sample +``` + +Given an input message: + +```json +{ "id": "P001", "samples": ["S1", "S2", "S3"] } +``` + +The step emits three messages: + +```json +{ "id": "P001", "sample": "S1" } +{ "id": "P001", "sample": "S2" } +{ "id": "P001", "sample": "S3" } +``` + +## See also + +- [filter](filter.md) – conditionally emit messages. +- [map](map.md) – apply a function to each flattened message. diff --git a/docs/_index.md b/docs/index.md similarity index 88% rename from docs/_index.md rename to docs/index.md index 5c37e23..6ad7988 100644 --- a/docs/_index.md +++ b/docs/index.md @@ -7,5 +7,3 @@ files and external databases. It includes a pipeline description language to define a set of Transform steps to create object messages that can be validated using a JSON schema data. - -![Example of sifter code](sifter_example.png) \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..75fc251 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,18 @@ +site_name: Sifter + +docs_dir: docs + +theme: + name: material + palette: + - scheme: default + primary: custom + features: + - navigation.indexes + # - navigation.footer + - content.code.copy + - navigation.tabs + # - navigation.sections + - navigation.top + - header.autohide + - navigation.tabs From 931ad1c6196528b196b6801ef1c5b7973b71d80b Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Tue, 20 Jan 2026 23:27:56 -0800 Subject: [PATCH 04/19] Starting to refactor the output stategy. Rather then scattered emit methods, there will be a single outputs stanza --- cmd/graphplan/main.go | 67 ------ cmd/inspect/main.go | 9 +- cmd/root.go | 4 - cmd/scan/main.go | 217 ------------------ graphplan/build_template.go | 140 ----------- loader/counter.go | 4 +- loader/dir.go | 4 +- loader/emitter.go | 2 +- loader/stdout.go | 2 +- playbook/execute.go | 7 +- playbook/inspect.go | 34 +-- playbook/load.go | 9 +- playbook/output.go | 26 +++ .../output_graph.go | 23 +- .../output_table.go | 7 +- {transform => playbook/refs}/code_block.go | 2 +- task/task.go | 30 +-- transform/emit.go | 35 --- transform/filter.go | 15 +- transform/flat_map.go | 7 +- transform/interface.go | 20 -- transform/mapping.go | 7 +- transform/reduce.go | 3 +- 23 files changed, 89 insertions(+), 585 deletions(-) delete mode 100644 cmd/graphplan/main.go delete mode 100644 cmd/scan/main.go delete mode 100644 graphplan/build_template.go create mode 100644 playbook/output.go rename transform/graph_build.go => playbook/output_graph.go (86%) rename transform/table_write.go => playbook/output_table.go (92%) rename {transform => playbook/refs}/code_block.go (97%) delete mode 100644 transform/emit.go diff --git a/cmd/graphplan/main.go b/cmd/graphplan/main.go deleted file mode 100644 index 56b93f8..0000000 --- a/cmd/graphplan/main.go +++ /dev/null @@ -1,67 +0,0 @@ -package graphplan - -import ( - "path/filepath" - - "github.com/bmeg/sifter/graphplan" - "github.com/bmeg/sifter/logger" - "github.com/bmeg/sifter/playbook" - "github.com/spf13/cobra" -) - -var outScriptDir = "" -var outDataDir = "./" -var objectExclude = []string{} -var verbose bool = false - -// Cmd is the declaration of the command line -var Cmd = &cobra.Command{ - Use: "graph-plan", - Short: "Scan directory to plan operations", - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - - if verbose { - logger.Init(true, false) - } - - scriptPath, _ := filepath.Abs(args[0]) - - /* - if outScriptDir != "" { - baseDir, _ = filepath.Abs(outScriptDir) - } else if len(args) > 1 { - return fmt.Errorf("for multiple input directories, based dir must be defined") - } - - _ = baseDir - */ - outScriptDir, _ = filepath.Abs(outScriptDir) - outDataDir, _ = filepath.Abs(outDataDir) - - outDataDir, _ = filepath.Rel(outScriptDir, outDataDir) - - pb := playbook.Playbook{} - - if sifterErr := playbook.ParseFile(scriptPath, &pb); sifterErr == nil { - if len(pb.Pipelines) > 0 || len(pb.Inputs) > 0 { - err := graphplan.NewGraphBuild( - &pb, outScriptDir, outDataDir, objectExclude, - ) - if err != nil { - logger.Error("Parse Error", "error", err) - } - } - } - - return nil - }, -} - -func init() { - flags := Cmd.Flags() - flags.BoolVarP(&verbose, "verbose", "v", verbose, "Verbose logging") - flags.StringVarP(&outScriptDir, "dir", "C", outScriptDir, "Change Directory for script base") - flags.StringVarP(&outDataDir, "out", "o", outDataDir, "Change output Directory") - flags.StringArrayVarP(&objectExclude, "exclude", "x", objectExclude, "Object Exclude") -} diff --git a/cmd/inspect/main.go b/cmd/inspect/main.go index 36766e7..bd2cbb8 100644 --- a/cmd/inspect/main.go +++ b/cmd/inspect/main.go @@ -63,13 +63,8 @@ var Cmd = &cobra.Command{ outputs := map[string]any{} - sinks, _ := pb.GetOutputs(task) - for k, v := range sinks { - outputs[k] = v - } - - emitters, _ := pb.GetEmitters(task) - for k, v := range emitters { + pouts, _ := pb.GetOutputs(task) + for k, v := range pouts { outputs[k] = v } diff --git a/cmd/root.go b/cmd/root.go index 2e889eb..d23161c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,10 +3,8 @@ package cmd import ( "os" - "github.com/bmeg/sifter/cmd/graphplan" "github.com/bmeg/sifter/cmd/inspect" "github.com/bmeg/sifter/cmd/run" - "github.com/bmeg/sifter/cmd/scan" "github.com/spf13/cobra" ) @@ -20,8 +18,6 @@ var RootCmd = &cobra.Command{ func init() { RootCmd.AddCommand(run.Cmd) RootCmd.AddCommand(inspect.Cmd) - RootCmd.AddCommand(graphplan.Cmd) - RootCmd.AddCommand(scan.Cmd) } var genBashCompletionCmd = &cobra.Command{ diff --git a/cmd/scan/main.go b/cmd/scan/main.go deleted file mode 100644 index bfb17b5..0000000 --- a/cmd/scan/main.go +++ /dev/null @@ -1,217 +0,0 @@ -package scan - -import ( - "encoding/json" - "fmt" - "io/fs" - "os" - "path/filepath" - "strings" - - "github.com/bmeg/sifter/playbook" - "github.com/bmeg/sifter/task" - "github.com/spf13/cobra" -) - -var jsonOut = false -var objectsOnly = false -var baseDir = "" - -type Entry struct { - ObjectType string `json:"objectType"` - SifterFile string `json:"sifterFile"` - Outfile string `json:"outFile"` -} - -var ObjectCommand = &cobra.Command{ - Use: "objects", - Short: "Scan for outputs", - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - - scanDir := args[0] - - outputs := []Entry{} - - PathWalker(scanDir, func(pb *playbook.Playbook) { - for pname, p := range pb.Pipelines { - emitName := "" - for _, s := range p { - if s.Emit != nil { - emitName = s.Emit.Name - } - } - if emitName != "" { - for _, s := range p { - outdir := pb.GetDefaultOutDir() - outname := fmt.Sprintf("%s.%s.%s.json.gz", pb.Name, pname, emitName) - outpath := filepath.Join(outdir, outname) - o := Entry{SifterFile: pb.GetPath(), Outfile: outpath} - if s.ObjectValidate != nil { - //outpath, _ = filepath.Rel(baseDir, outpath) - //fmt.Printf("%s\t%s\n", s.ObjectValidate.Title, outpath) - o.ObjectType = s.ObjectValidate.Title - } - if objectsOnly { - if o.ObjectType != "" { - outputs = append(outputs, o) - } - } else { - outputs = append(outputs, o) - } - } - } - } - }) - - if jsonOut { - j := json.NewEncoder(os.Stdout) - j.SetIndent("", " ") - j.Encode(outputs) - } else { - for _, i := range outputs { - fmt.Printf("%s\t%s\n", i.ObjectType, i.Outfile) - } - } - - return nil - - }, -} - -type ScriptEntry struct { - Name string `json:"name"` - Path string `json:"path"` - Inputs []string `json:"inputs"` - Outputs []string `json:"outputs"` -} - -func removeDuplicates(s []string) []string { - t := map[string]bool{} - - for _, i := range s { - t[i] = true - } - out := []string{} - for k := range t { - out = append(out, k) - } - return out -} - -func relPathArray(basedir string, paths []string) []string { - out := []string{} - for _, i := range paths { - if o, err := filepath.Rel(baseDir, i); err == nil { - out = append(out, o) - } - } - return out -} - -var ScriptCommand = &cobra.Command{ - Use: "scripts", - Short: "Scan for scripts", - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - - scanDir := args[0] - - scripts := []ScriptEntry{} - - if baseDir == "" { - baseDir, _ = os.Getwd() - } - baseDir, _ = filepath.Abs(baseDir) - //fmt.Printf("basedir: %s\n", baseDir) - - userInputs := map[string]string{} - - PathWalker(scanDir, func(pb *playbook.Playbook) { - path := pb.GetPath() - scriptDir := filepath.Dir(path) - - config, _ := pb.PrepConfig(userInputs, baseDir) - - task := task.NewTask(pb.Name, scriptDir, baseDir, pb.GetDefaultOutDir(), config) - sourcePath, _ := filepath.Abs(path) - - cmdPath, _ := filepath.Rel(baseDir, sourcePath) - - inputs := []string{} - outputs := []string{} - for _, p := range pb.GetRequiredParams() { - if p.IsDir() || p.IsFile() { - inputs = append(inputs, config[p.Name]) - } - } - //inputs = append(inputs, sourcePath) - - sinks, _ := pb.GetOutputs(task) - for _, v := range sinks { - outputs = append(outputs, v...) - } - - emitters, _ := pb.GetEmitters(task) - for _, v := range emitters { - outputs = append(outputs, v) - } - - //for _, e := range pb.Inputs { - //} - - s := ScriptEntry{ - Path: cmdPath, - Name: pb.Name, - Outputs: relPathArray(baseDir, removeDuplicates(outputs)), - Inputs: relPathArray(baseDir, removeDuplicates(inputs)), - } - scripts = append(scripts, s) - }) - - if jsonOut { - e := json.NewEncoder(os.Stdout) - e.SetIndent("", " ") - e.Encode(scripts) - } else { - for _, i := range scripts { - fmt.Printf("%s\n", i) - } - } - - return nil - }, -} - -// Cmd is the declaration of the command line -var Cmd = &cobra.Command{ - Use: "scan", - Short: "Scan for scripts or objects", -} - -func init() { - Cmd.AddCommand(ObjectCommand) - Cmd.AddCommand(ScriptCommand) - - objFlags := ObjectCommand.Flags() - objFlags.BoolVarP(&objectsOnly, "objects", "s", objectsOnly, "Objects Only") - objFlags.BoolVarP(&jsonOut, "json", "j", jsonOut, "Output JSON") - - scriptFlags := ScriptCommand.Flags() - scriptFlags.StringVarP(&baseDir, "base", "b", baseDir, "Base Dir") - scriptFlags.BoolVarP(&jsonOut, "json", "j", jsonOut, "Output JSON") - -} - -func PathWalker(baseDir string, userFunc func(*playbook.Playbook)) { - filepath.Walk(baseDir, - func(path string, info fs.FileInfo, err error) error { - if strings.HasSuffix(path, ".yaml") { - pb := playbook.Playbook{} - if parseErr := playbook.ParseFile(path, &pb); parseErr == nil { - userFunc(&pb) - } - } - return nil - }) -} diff --git a/graphplan/build_template.go b/graphplan/build_template.go deleted file mode 100644 index e564a3c..0000000 --- a/graphplan/build_template.go +++ /dev/null @@ -1,140 +0,0 @@ -package graphplan - -import ( - "fmt" - "os" - "path/filepath" - "text/template" - - "github.com/bmeg/sifter/evaluate" - "github.com/bmeg/sifter/logger" - "github.com/bmeg/sifter/playbook" - "github.com/bmeg/sifter/task" -) - -type ObjectConvertStep struct { - Name string - Input string - Class string - Schema string -} - -type GraphBuildStep struct { - Name string - Outdir string - Objects []ObjectConvertStep -} - -var graphScript string = ` - -name: {{.Name}} -class: sifter - -outdir: {{.Outdir}} - -config: -{{range .Objects}} - {{.Name}}: {{.Input}} - {{.Name}}Schema: {{.Schema}} -{{end}} - -inputs: -{{range .Objects}} - {{.Name}}: - jsonLoad: - input: "{{ "{{config." }}{{.Name}}{{"}}"}}" -{{end}} - -pipelines: -{{range .Objects}} - {{.Name}}-graph: - - from: {{.Name}} - - graphBuild: - schema: "{{ "{{config."}}{{.Name}}Schema{{ "}}" }}" - title: {{.Class}} -{{end}} -` - -func contains(n string, c []string) bool { - for _, c := range c { - if n == c { - return true - } - } - return false -} - -func uniqueName(name string, used []string) string { - if !contains(name, used) { - return name - } - for i := 1; ; i++ { - f := fmt.Sprintf("%s_%d", name, i) - if !contains(f, used) { - return f - } - } -} - -func NewGraphBuild(pb *playbook.Playbook, scriptOutDir, dataDir string, objectExclude []string) error { - userInputs := map[string]string{} - localInputs, _ := pb.PrepConfig(userInputs, filepath.Dir(pb.GetPath())) - - task := task.NewTask(pb.Name, filepath.Dir(pb.GetPath()), filepath.Dir(pb.GetPath()), pb.GetDefaultOutDir(), localInputs) - - convertName := fmt.Sprintf("%s-graph", pb.Name) - - gb := GraphBuildStep{Name: convertName, Objects: []ObjectConvertStep{}, Outdir: dataDir} - - for pname, p := range pb.Pipelines { - emitName := "" - for _, s := range p { - if s.Emit != nil { - emitName = s.Emit.Name - } - } - if emitName != "" { - for _, s := range p { - if s.ObjectValidate != nil { - if !contains(s.ObjectValidate.Title, objectExclude) { - schema, _ := evaluate.ExpressionString(s.ObjectValidate.Schema, task.GetConfig(), map[string]any{}) - outdir := pb.GetDefaultOutDir() - outname := fmt.Sprintf("%s.%s.%s.json.gz", pb.Name, pname, emitName) - - outpath := filepath.Join(outdir, outname) - outpath, _ = filepath.Rel(scriptOutDir, outpath) - - schemaPath, _ := filepath.Rel(scriptOutDir, schema) - - _ = schemaPath - - objCreate := ObjectConvertStep{Name: pname, Input: outpath, Class: s.ObjectValidate.Title, Schema: schemaPath} - gb.Objects = append(gb.Objects, objCreate) - } - } - } - } - } - - if len(gb.Objects) > 0 { - tmpl, err := template.New("graphscript").Parse(graphScript) - if err != nil { - panic(err) - } - - outPath := filepath.Join(scriptOutDir, fmt.Sprintf("%s.yaml", pb.Name)) - outfile, err := os.Create(outPath) - if err != nil { - logger.Error("File Error", "error", err) - } - - logger.Info("Summary", "ObjectFound", len(gb.Objects), "outPath", outPath) - - err = tmpl.Execute(outfile, gb) - outfile.Close() - if err != nil { - logger.Error("Template Error", "error", err) - } - } - return nil -} diff --git a/loader/counter.go b/loader/counter.go index 485b476..e71ae16 100644 --- a/loader/counter.go +++ b/loader/counter.go @@ -44,7 +44,7 @@ func (cd *CountDataEmitter) Close() { cd.d.Close() } -func (cd *CountDataEmitter) Emit(name string, e map[string]interface{}, useName bool) error { +func (cd *CountDataEmitter) Emit(name string, e map[string]interface{}) error { cd.cl.increment() - return cd.d.Emit(name, e, useName) + return cd.d.Emit(name, e) } diff --git a/loader/dir.go b/loader/dir.go index 3d62ecc..d9ef06b 100644 --- a/loader/dir.go +++ b/loader/dir.go @@ -65,14 +65,14 @@ func (s *DirLoader) Close() { s.dout = map[string]io.WriteCloser{} } -func (s *DirDataLoader) Emit(name string, v map[string]interface{}, useName bool) error { +func (s *DirDataLoader) Emit(name string, v map[string]interface{}) error { s.dl.mux.Lock() defer s.dl.mux.Unlock() f, ok := s.dl.dout[name] if !ok { // log.Printf("output path %s", outputPath) - opath := path.Join(s.dl.dir, name+".json.gz") + opath := path.Join(s.dl.dir, name) logger.Info("Creating emit file", "name", name, "path", opath) diff --git a/loader/emitter.go b/loader/emitter.go index 49bd50d..c7d81ff 100644 --- a/loader/emitter.go +++ b/loader/emitter.go @@ -6,7 +6,7 @@ import ( ) type DataEmitter interface { - Emit(name string, e map[string]interface{}, useName bool) error + Emit(name string, e map[string]interface{}) error Close() } diff --git a/loader/stdout.go b/loader/stdout.go index ec60903..6c78b70 100644 --- a/loader/stdout.go +++ b/loader/stdout.go @@ -21,7 +21,7 @@ type StdoutEmitter struct { func (s StdoutEmitter) Close() {} -func (s StdoutEmitter) Emit(name string, v map[string]interface{}, useName bool) error { +func (s StdoutEmitter) Emit(name string, v map[string]interface{}) error { if v != nil { o, _ := json.Marshal(v) fmt.Printf("%s\t%s\n", name, o) diff --git a/playbook/execute.go b/playbook/execute.go index 335cea1..430daa1 100644 --- a/playbook/execute.go +++ b/playbook/execute.go @@ -149,11 +149,10 @@ func (pb *Playbook) Execute(task task.RuntimeTask) error { joins := []joinStruct{} for k, v := range pb.Pipelines { - sub := task.SubTask(k) var lastStep flame.Emitter[map[string]any] var firstStep flame.Receiver[map[string]any] for i, s := range v { - b, err := s.Init(sub) + b, err := s.Init(task) if err != nil { logger.Error("Pipeline error", "name", k, "error", err) return err @@ -330,6 +329,10 @@ func (pb *Playbook) Execute(task task.RuntimeTask) error { } } + //for _, i := range pb.Outputs { + // outNode = flame.AddMapper(wf, mProcess.Process) + //} + //log.Printf("WF: %#v", wf) wf.Start() diff --git a/playbook/inspect.go b/playbook/inspect.go index 2216a55..521ef93 100644 --- a/playbook/inspect.go +++ b/playbook/inspect.go @@ -1,7 +1,6 @@ package playbook import ( - "fmt" "path/filepath" "github.com/bmeg/sifter/config" @@ -24,36 +23,13 @@ func (pb *Playbook) GetRequiredParams() []config.ParamRequest { return out } -func (pb *Playbook) GetOutputs(task task.RuntimeTask) (map[string][]string, error) { - out := map[string][]string{} - //inputs := task.GetInputs() - - for k, v := range pb.Pipelines { - for _, s := range v { - fArray := []string{} - for _, fileName := range s.GetOutputs() { - filePath := filepath.Join(pb.GetOutDir(task), fileName) - fArray = append(fArray, filePath) - } - if len(fArray) > 0 { - out[k] = fArray - } - } - } - return out, nil -} - -func (pb *Playbook) GetEmitters(task task.RuntimeTask) (map[string]string, error) { +func (pb *Playbook) GetOutputs(task task.RuntimeTask) (map[string]string, error) { out := map[string]string{} + //inputs := task.GetInputs() - for k, v := range pb.Pipelines { - for _, s := range v { - for _, e := range s.GetEmitters() { - fileName := fmt.Sprintf("%s.%s.%s.json.gz", pb.Name, k, e) - filePath := filepath.Join(pb.GetOutDir(task), fileName) - out[k+"."+e] = filePath - } - } + for k, v := range pb.Outputs { + filePath := filepath.Join(pb.GetOutDir(task), v.Path) + out[k] = filePath } return out, nil } diff --git a/playbook/load.go b/playbook/load.go index 379d96a..7747ab0 100644 --- a/playbook/load.go +++ b/playbook/load.go @@ -17,14 +17,21 @@ type Loader interface { Load() chan gripql.GraphElement } +type Output struct { + From string `json:"from"` + Type string `json:"type"` + Path string `json:"path"` +} + type Playbook struct { + Inputs map[string]extractors.Extractor `json:"inputs" jsonschema_description:"Steps of the transformation"` + Outputs map[string]Output `json:"outputs"` Class string `json:"class"` Name string `json:"name" jsonschema_description:"Unique name of the playbook"` MemMB int `json:"memMB"` //annotation of potential memory usage, for build Snakefile Docs string `json:"docs"` Outdir string `json:"outdir"` Params config.Params `json:"params,omitempty" jsonschema_description:"Parameters for Playbook"` - Inputs map[string]extractors.Extractor `json:"inputs" jsonschema_description:"Steps of the transformation"` Pipelines map[string]transform.Pipe `json:"pipelines"` path string } diff --git a/playbook/output.go b/playbook/output.go new file mode 100644 index 0000000..24c9123 --- /dev/null +++ b/playbook/output.go @@ -0,0 +1,26 @@ +package playbook + +import ( + "github.com/bmeg/sifter/logger" + "github.com/bmeg/sifter/task" +) + +type OutputProcessor interface { + Process(i map[string]any) +} + +type OutputProcess struct { + pb *Playbook + config Output + task task.RuntimeTask + count uint64 +} + +func (op *OutputProcess) Close() { + logger.Info("Emit Summary", "name", op.config.Path, "count", op.count) +} + +func (op *OutputProcess) Process(i map[string]interface{}) { + op.count++ + op.task.Emit(op.config.Path, i) +} diff --git a/transform/graph_build.go b/playbook/output_graph.go similarity index 86% rename from transform/graph_build.go rename to playbook/output_graph.go index 8bb6458..2a709ce 100644 --- a/transform/graph_build.go +++ b/playbook/output_graph.go @@ -1,4 +1,4 @@ -package transform +package playbook import ( "github.com/bmeg/grip/gripql" @@ -6,12 +6,13 @@ import ( "github.com/bmeg/sifter/config" "github.com/bmeg/sifter/evaluate" "github.com/bmeg/sifter/logger" + "github.com/bmeg/sifter/playbook/refs" "github.com/bmeg/sifter/task" ) type EdgeFix struct { - Method string `json:"method"` - GPython *CodeBlock `json:"gpython"` + Method string `json:"method"` + GPython *refs.CodeBlock `json:"gpython"` } type GraphBuildStep struct { @@ -34,7 +35,7 @@ type graphBuildProcess struct { edgeCount int } -func (ts GraphBuildStep) Init(task task.RuntimeTask) (Processor, error) { +func (ts GraphBuildStep) Init(task task.RuntimeTask) (OutputProcessor, error) { path, err := evaluate.ExpressionString(ts.Schema, task.GetConfig(), nil) if err != nil { @@ -47,8 +48,8 @@ func (ts GraphBuildStep) Init(task task.RuntimeTask) (Processor, error) { } //force the two emitters to be created. nil messages don't get emitted //but the output file will be created - task.Emit("vertex", nil, false) - task.Emit("edge", nil, false) + task.Emit("vertex", nil) + task.Emit("edge", nil) var edgeFix evaluate.Processor if ts.EdgeFix != nil { @@ -88,15 +89,14 @@ func (ts *graphBuildProcess) Close() { "class", ts.class) } -func (ts *graphBuildProcess) Process(i map[string]interface{}) []map[string]interface{} { +func (ts *graphBuildProcess) Process(i map[string]interface{}) { - out := []map[string]any{} if o, err := ts.sch.Generate(ts.class, i, ts.config.Clean, map[string]any{}); err == nil { ts.objectCount++ for i := range o { if o[i].Vertex != nil { ts.vertexCount++ - err := ts.task.Emit("vertex", ts.vertexToMap(o[i].Vertex), false) + err := ts.task.Emit("vertex", ts.vertexToMap(o[i].Vertex)) if err != nil { logger.Error("Emit Error: %s", err) } @@ -111,7 +111,7 @@ func (ts *graphBuildProcess) Process(i map[string]interface{}) []map[string]inte } } ts.edgeCount++ - err := ts.task.Emit("edge", edgeData, false) + err := ts.task.Emit("edge", edgeData) if err != nil { logger.Error("Emit Error: %s", err) } @@ -121,9 +121,6 @@ func (ts *graphBuildProcess) Process(i map[string]interface{}) []map[string]inte } else { logger.Error("Graphbuild %s error : %s", ts.config.Title, err) } - - return out - } func (ts *graphBuildProcess) edgeToMap(e *gripql.Edge) map[string]interface{} { diff --git a/transform/table_write.go b/playbook/output_table.go similarity index 92% rename from transform/table_write.go rename to playbook/output_table.go index 2c36d62..3e2eba4 100644 --- a/transform/table_write.go +++ b/playbook/output_table.go @@ -1,4 +1,4 @@ -package transform +package playbook import ( "compress/gzip" @@ -30,7 +30,7 @@ type tableWriteProcess struct { writer *csv.Writer } -func (tw *TableWriteStep) Init(task task.RuntimeTask) (Processor, error) { +func (tw *TableWriteStep) Init(task task.RuntimeTask) (OutputProcessor, error) { sep := '\t' if tw.Sep != "" { sep = rune(tw.Sep[0]) @@ -78,7 +78,7 @@ func (tp *tableWriteProcess) PoolReady() bool { return false } -func (tp *tableWriteProcess) Process(i map[string]any) map[string]any { +func (tp *tableWriteProcess) Process(i map[string]any) { o := make([]string, len(tp.columns)) for j, k := range tp.columns { if v, ok := i[k]; ok { @@ -91,7 +91,6 @@ func (tp *tableWriteProcess) Process(i map[string]any) map[string]any { } } tp.writer.Write(o) - return i } func (tp *tableWriteProcess) Close() { diff --git a/transform/code_block.go b/playbook/refs/code_block.go similarity index 97% rename from transform/code_block.go rename to playbook/refs/code_block.go index 015171e..2cdf62b 100644 --- a/transform/code_block.go +++ b/playbook/refs/code_block.go @@ -1,4 +1,4 @@ -package transform +package refs import ( "encoding/json" diff --git a/task/task.go b/task/task.go index 721aeb4..362e51e 100644 --- a/task/task.go +++ b/task/task.go @@ -1,18 +1,16 @@ package task import ( - "io/ioutil" + "os" "path/filepath" - "strings" "github.com/bmeg/sifter/loader" ) type RuntimeTask interface { SetName(name string) - SubTask(ext string) RuntimeTask - Emit(name string, e map[string]interface{}, useName bool) error + Emit(name string, e map[string]interface{}) error GetConfig() map[string]string AbsPath(p string) (string, error) @@ -61,18 +59,6 @@ func (m *Task) GetName() string { return m.Prefix + "." + m.Name } -func (m *Task) SubTask(ext string) RuntimeTask { - return &Task{ - Prefix: m.GetName(), - Name: ext, - Workdir: m.Workdir, - Basedir: m.Basedir, - Config: m.Config, - Emitter: m.Emitter, - Outdir: m.Outdir, - } -} - func (m *Task) GetConfig() map[string]string { return m.Config } @@ -93,7 +79,7 @@ func (m *Task) OutDir() string { } func (m *Task) TempDir() string { - name, _ := ioutil.TempDir(m.Workdir, "tmp") + name, _ := os.MkdirTemp(m.Workdir, "tmp") return name } @@ -105,12 +91,6 @@ func (m *Task) BaseDir() string { return m.Basedir } -func (m *Task) Emit(n string, e map[string]interface{}, useName bool) error { - - newName := m.GetName() + "." + n - if useName { - temp := strings.Split(n, ".") - newName = temp[len(temp)-1] - } - return m.Emitter.Emit(newName, e, useName) +func (m *Task) Emit(name string, e map[string]interface{}) error { + return m.Emitter.Emit(name, e) } diff --git a/transform/emit.go b/transform/emit.go deleted file mode 100644 index 800f93f..0000000 --- a/transform/emit.go +++ /dev/null @@ -1,35 +0,0 @@ -package transform - -import ( - "github.com/bmeg/sifter/evaluate" - "github.com/bmeg/sifter/logger" - "github.com/bmeg/sifter/task" -) - -type EmitStep struct { - Name string `json:"name"` - UseName bool `json:"UseName"` -} - -type emitProcess struct { - config EmitStep - task task.RuntimeTask - count uint64 -} - -func (ts EmitStep) Init(t task.RuntimeTask) (Processor, error) { - return &emitProcess{ts, t, 0}, nil -} - -func (ts *emitProcess) Close() { - logger.Info("Emit Summary", "name", ts.config.Name, "count", ts.count) -} - -func (ts *emitProcess) Process(i map[string]interface{}) []map[string]interface{} { - name, err := evaluate.ExpressionString(ts.config.Name, ts.task.GetConfig(), i) - if err == nil { - ts.count++ - ts.task.Emit(name, i, ts.config.UseName) - } - return []map[string]any{i} -} diff --git a/transform/filter.go b/transform/filter.go index c2a21c5..5c89317 100644 --- a/transform/filter.go +++ b/transform/filter.go @@ -5,17 +5,18 @@ import ( "github.com/bmeg/sifter/evaluate" "github.com/bmeg/sifter/logger" + "github.com/bmeg/sifter/playbook/refs" "github.com/bmeg/sifter/task" ) type FilterStep struct { - Field string `json:"field"` - Value string `json:"value"` - Match string `json:"match"` - Check string `json:"check" jsonschema_description:"How to check value, 'exists' or 'hasValue'"` - Method string `json:"method"` - Python string `json:"python"` - GPython *CodeBlock `json:"gpython"` + Field string `json:"field"` + Value string `json:"value"` + Match string `json:"match"` + Check string `json:"check" jsonschema_description:"How to check value, 'exists' or 'hasValue'"` + Method string `json:"method"` + Python string `json:"python"` + GPython *refs.CodeBlock `json:"gpython"` } type filterProcessor struct { diff --git a/transform/flat_map.go b/transform/flat_map.go index 0419759..80440aa 100644 --- a/transform/flat_map.go +++ b/transform/flat_map.go @@ -7,13 +7,14 @@ import ( "github.com/bmeg/sifter/evaluate" "github.com/bmeg/sifter/logger" + "github.com/bmeg/sifter/playbook/refs" "github.com/bmeg/sifter/task" ) type FlatMapStep struct { - Method string `json:"method" jsonschema_description:"Name of function to call"` - Python string `json:"python" jsonschema_description:"Python code to be run"` - GPython *CodeBlock `json:"gpython" jsonschema_description:"Python code to be run using GPython"` + Method string `json:"method" jsonschema_description:"Name of function to call"` + Python string `json:"python" jsonschema_description:"Python code to be run"` + GPython *refs.CodeBlock `json:"gpython" jsonschema_description:"Python code to be run using GPython"` } type flatMapProcess struct { diff --git a/transform/interface.go b/transform/interface.go index 6795ad4..9f71779 100644 --- a/transform/interface.go +++ b/transform/interface.go @@ -61,7 +61,6 @@ type Step struct { FieldParse *FieldParseStep `json:"fieldParse" jsonschema_description:"fieldParse to run"` FieldType *FieldTypeStep `json:"fieldType" jsonschema_description:"Change type of a field (ie string -> integer)"` ObjectValidate *ObjectValidateStep `json:"objectValidate" jsonschema_description:"Validate a JSON schema based object"` - Emit *EmitStep `json:"emit" jsonschema_description:"Write to unstructured JSON file"` Filter *FilterStep `json:"filter"` Clean *CleanStep `json:"clean"` Debug *DebugStep `json:"debug" jsonschema_description:"Print message contents to stdout"` @@ -77,8 +76,6 @@ type Step struct { Lookup *LookupStep `json:"lookup"` IntervalIntersect *IntervalStep `json:"intervalIntersect"` Hash *HashStep `json:"hash"` - GraphBuild *GraphBuildStep `json:"graphBuild"` - TableWrite *TableWriteStep `json:"tableWrite"` Accumulate *AccumulateStep `json:"accumulate"` UUID *UUIDStep `json:"uuid"` } @@ -113,20 +110,3 @@ func (ts Step) GetRequiredParams() []config.ParamRequest { } return out } - -func (ts Step) GetEmitters() []string { - if ts.Emit != nil { - return []string{ts.Emit.Name} - } - if ts.GraphBuild != nil { - return []string{"vertex", "edge"} - } - return []string{} -} - -func (ts Step) GetOutputs() []string { - if ts.TableWrite != nil { - return []string{ts.TableWrite.Output} - } - return []string{} -} diff --git a/transform/mapping.go b/transform/mapping.go index 36f30c3..e95ab35 100644 --- a/transform/mapping.go +++ b/transform/mapping.go @@ -7,13 +7,14 @@ import ( "github.com/bmeg/sifter/evaluate" "github.com/bmeg/sifter/logger" + "github.com/bmeg/sifter/playbook/refs" "github.com/bmeg/sifter/task" ) type MapStep struct { - Method string `json:"method" jsonschema_description:"Name of function to call"` - Python string `json:"python" jsonschema_description:"Python code to be run"` - GPython *CodeBlock `json:"gpython" jsonschema_description:"Python code to be run using GPython"` + Method string `json:"method" jsonschema_description:"Name of function to call"` + Python string `json:"python" jsonschema_description:"Python code to be run"` + GPython *refs.CodeBlock `json:"gpython" jsonschema_description:"Python code to be run using GPython"` } type mapProcess struct { diff --git a/transform/reduce.go b/transform/reduce.go index 005804f..ed36c02 100644 --- a/transform/reduce.go +++ b/transform/reduce.go @@ -5,6 +5,7 @@ import ( "github.com/bmeg/sifter/evaluate" "github.com/bmeg/sifter/logger" + "github.com/bmeg/sifter/playbook/refs" "github.com/bmeg/sifter/task" ) @@ -12,7 +13,7 @@ type ReduceStep struct { Field string `json:"field"` Method string `json:"method"` Python string `json:"python"` - GPython *CodeBlock `json:"gpython"` + GPython *refs.CodeBlock `json:"gpython"` InitData *map[string]interface{} `json:"init"` } From e95294e65f05a499f142ec66737733cc15dc21cc Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Wed, 21 Jan 2026 20:57:48 -0800 Subject: [PATCH 05/19] Working on output code --- playbook/execute.go | 29 +++++++++++++++++-- playbook/inspect.go | 6 ++-- playbook/load.go | 6 ++-- playbook/output.go | 26 ----------------- playbook/output_graph.go | 21 +++++++++++--- playbook/output_json.go | 60 ++++++++++++++++++++++++++++++++++++++++ playbook/output_table.go | 8 +++--- 7 files changed, 114 insertions(+), 42 deletions(-) delete mode 100644 playbook/output.go create mode 100644 playbook/output_json.go diff --git a/playbook/execute.go b/playbook/execute.go index 430daa1..1435ea1 100644 --- a/playbook/execute.go +++ b/playbook/execute.go @@ -132,6 +132,7 @@ func (pb *Playbook) Execute(task task.RuntimeTask) error { outNodes := map[string]flame.Emitter[map[string]any]{} inNodes := map[string]flame.Receiver[map[string]any]{} + outputs := map[string]OutputProcessor{} for n, v := range pb.Inputs { logger.Debug("Setting Up", "name", n) @@ -329,9 +330,27 @@ func (pb *Playbook) Execute(task task.RuntimeTask) error { } } - //for _, i := range pb.Outputs { - // outNode = flame.AddMapper(wf, mProcess.Process) - //} + for k, v := range pb.Outputs { + if v.JSON != nil { + proc, err := v.JSON.Init(task) + if err == nil { + flame.AddSink(wf, proc.Process) + outputs[k] = proc + } + } else if v.Table != nil { + proc, err := v.Table.Init(task) + if err == nil { + flame.AddSink(wf, proc.Process) + outputs[k] = proc + } + } else if v.Graph != nil { + proc, err := v.Graph.Init(task) + if err == nil { + flame.AddSink(wf, proc.Process) + outputs[k] = proc + } + } + } //log.Printf("WF: %#v", wf) @@ -344,6 +363,10 @@ func (pb *Playbook) Execute(task task.RuntimeTask) error { procs[p].Close() } + for k := range outputs { + outputs[k].Close() + } + task.Close() return nil } diff --git a/playbook/inspect.go b/playbook/inspect.go index 521ef93..f82bd6f 100644 --- a/playbook/inspect.go +++ b/playbook/inspect.go @@ -28,8 +28,10 @@ func (pb *Playbook) GetOutputs(task task.RuntimeTask) (map[string]string, error) //inputs := task.GetInputs() for k, v := range pb.Outputs { - filePath := filepath.Join(pb.GetOutDir(task), v.Path) - out[k] = filePath + for _, o := range v.GetOutputs(task) { + filePath := filepath.Join(pb.GetOutDir(task), o) + out[k] = filePath + } } return out, nil } diff --git a/playbook/load.go b/playbook/load.go index 7747ab0..11ef5f2 100644 --- a/playbook/load.go +++ b/playbook/load.go @@ -18,9 +18,9 @@ type Loader interface { } type Output struct { - From string `json:"from"` - Type string `json:"type"` - Path string `json:"path"` + JSON *OutputJSON `json:"json"` + Table *OutputTable `json:"table"` + Graph *OutputGraph `json:"graph"` } type Playbook struct { diff --git a/playbook/output.go b/playbook/output.go deleted file mode 100644 index 24c9123..0000000 --- a/playbook/output.go +++ /dev/null @@ -1,26 +0,0 @@ -package playbook - -import ( - "github.com/bmeg/sifter/logger" - "github.com/bmeg/sifter/task" -) - -type OutputProcessor interface { - Process(i map[string]any) -} - -type OutputProcess struct { - pb *Playbook - config Output - task task.RuntimeTask - count uint64 -} - -func (op *OutputProcess) Close() { - logger.Info("Emit Summary", "name", op.config.Path, "count", op.count) -} - -func (op *OutputProcess) Process(i map[string]interface{}) { - op.count++ - op.task.Emit(op.config.Path, i) -} diff --git a/playbook/output_graph.go b/playbook/output_graph.go index 2a709ce..273b0fa 100644 --- a/playbook/output_graph.go +++ b/playbook/output_graph.go @@ -1,6 +1,8 @@ package playbook import ( + "path/filepath" + "github.com/bmeg/grip/gripql" "github.com/bmeg/jsonschemagraph/graph" "github.com/bmeg/sifter/config" @@ -15,7 +17,8 @@ type EdgeFix struct { GPython *refs.CodeBlock `json:"gpython"` } -type GraphBuildStep struct { +type OutputGraph struct { + Path string `json:"path"` Schema string `json:"schema"` Title string `json:"title"` Clean bool `json:"clean"` @@ -23,8 +26,18 @@ type GraphBuildStep struct { EdgeFix *EdgeFix `json:"edgeFix"` } +func (oj *OutputGraph) GetOutputs(task task.RuntimeTask) []string { + output, err := evaluate.ExpressionString(oj.Path, task.GetConfig(), nil) + if err != nil { + return []string{} + } + outputPath := filepath.Join(task.OutDir(), output) + logger.Debug("table output %s %s", task.OutDir(), output) + return []string{outputPath + ".edge", outputPath + ".vertex"} +} + type graphBuildProcess struct { - config GraphBuildStep + config OutputGraph task task.RuntimeTask sch graph.GraphSchema class string @@ -35,7 +48,7 @@ type graphBuildProcess struct { edgeCount int } -func (ts GraphBuildStep) Init(task task.RuntimeTask) (OutputProcessor, error) { +func (ts OutputGraph) Init(task task.RuntimeTask) (OutputProcessor, error) { path, err := evaluate.ExpressionString(ts.Schema, task.GetConfig(), nil) if err != nil { @@ -67,7 +80,7 @@ func (ts GraphBuildStep) Init(task task.RuntimeTask) (OutputProcessor, error) { return &graphBuildProcess{ts, task, sc, ts.Title, edgeFix, 0, 0, 0}, nil } -func (ts GraphBuildStep) GetRequiredParams() []config.ParamRequest { +func (ts OutputGraph) GetRequiredParams() []config.ParamRequest { out := []config.ParamRequest{} if ts.Schema != "" { for _, s := range evaluate.ExpressionIDs(ts.Schema) { diff --git a/playbook/output_json.go b/playbook/output_json.go new file mode 100644 index 0000000..869984b --- /dev/null +++ b/playbook/output_json.go @@ -0,0 +1,60 @@ +package playbook + +import ( + "path/filepath" + + "github.com/bmeg/sifter/evaluate" + "github.com/bmeg/sifter/logger" + "github.com/bmeg/sifter/task" +) + +func (pout *Output) GetOutputs(task task.RuntimeTask) []string { + if pout.JSON != nil { + return pout.JSON.GetOutputs(task) + } else if pout.Graph != nil { + return pout.Graph.GetOutputs(task) + } else if pout.Table != nil { + return pout.Table.GetOutputs(task) + } + + return []string{} +} + +type OutputProcessor interface { + Process(i map[string]any) + //GetOutputs(task task.RuntimeTask) []string + Close() +} + +type OutputJSON struct { + Path string `json:"path"` +} + +func (oj *OutputJSON) GetOutputs(task task.RuntimeTask) []string { + output, err := evaluate.ExpressionString(oj.Path, task.GetConfig(), nil) + if err != nil { + return []string{} + } + outputPath := filepath.Join(task.OutDir(), output) + logger.Debug("table output %s %s", task.OutDir(), output) + return []string{outputPath} +} + +func (ts OutputJSON) Init(task task.RuntimeTask) (OutputProcessor, error) { + return &jsonOutputProcess{config: ts, task: task}, nil +} + +type jsonOutputProcess struct { + config OutputJSON + task task.RuntimeTask + count uint64 +} + +func (op *jsonOutputProcess) Close() { + logger.Info("Emit Summary", "name", op.config.Path, "count", op.count) +} + +func (op *jsonOutputProcess) Process(i map[string]interface{}) { + op.count++ + op.task.Emit(op.config.Path, i) +} diff --git a/playbook/output_table.go b/playbook/output_table.go index 3e2eba4..db7eca3 100644 --- a/playbook/output_table.go +++ b/playbook/output_table.go @@ -14,7 +14,7 @@ import ( "github.com/bmeg/sifter/task" ) -type TableWriteStep struct { +type OutputTable struct { Output string `json:"output" jsonschema_description:"Name of file to create"` Columns []string `json:"columns" jsonschema_description:"Columns to be written into table file"` Header string `json:"header"` @@ -23,14 +23,14 @@ type TableWriteStep struct { } type tableWriteProcess struct { - config *TableWriteStep + config *OutputTable columns []string out io.WriteCloser handle io.WriteCloser writer *csv.Writer } -func (tw *TableWriteStep) Init(task task.RuntimeTask) (OutputProcessor, error) { +func (tw *OutputTable) Init(task task.RuntimeTask) (OutputProcessor, error) { sep := '\t' if tw.Sep != "" { sep = rune(tw.Sep[0]) @@ -64,7 +64,7 @@ func (tw *TableWriteStep) Init(task task.RuntimeTask) (OutputProcessor, error) { return &te, nil } -func (tw *TableWriteStep) GetOutputs(task task.RuntimeTask) []string { +func (tw *OutputTable) GetOutputs(task task.RuntimeTask) []string { output, err := evaluate.ExpressionString(tw.Output, task.GetConfig(), nil) if err != nil { return []string{} From ac696c58b99a7879c06fe16f67dcfac720ee195d Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Wed, 21 Jan 2026 22:16:03 -0800 Subject: [PATCH 06/19] Getting the unit tests to work again --- loader/dir.go | 7 +++- playbook/execute.go | 21 +++++++---- playbook/output_graph.go | 35 +++++++++++++++---- playbook/output_json.go | 9 ++--- playbook/output_table.go | 1 + test/config.yaml | 10 +++--- test/examples/gdc/gdc-convert.yaml | 31 ++++++++-------- test/examples/gene-table/gene-table.yaml | 14 +++++--- test/examples/lookup/inline-table.yaml | 7 ++-- test/examples/lookup/tsv-table-replace.yaml | 10 ++++-- .../examples/pathwaycommons/gene_collect.yaml | 12 +++---- .../pathwaycommons/pathway_commons.yaml | 17 +++++---- test/examples/pfb/transform.yaml | 15 +++++--- test/resources/project.yaml | 7 ++-- 14 files changed, 129 insertions(+), 67 deletions(-) diff --git a/loader/dir.go b/loader/dir.go index d9ef06b..e334247 100644 --- a/loader/dir.go +++ b/loader/dir.go @@ -7,6 +7,7 @@ import ( "os" "path" "path/filepath" + "strings" "sync" "github.com/bmeg/sifter/logger" @@ -80,7 +81,11 @@ func (s *DirDataLoader) Emit(name string, v map[string]interface{}) error { if err != nil { return err } - f = gzip.NewWriter(j) + if strings.HasSuffix(opath, ".gz") { + f = gzip.NewWriter(j) + } else { + f = j + } s.dl.dout[name] = f } if v != nil { diff --git a/playbook/execute.go b/playbook/execute.go index 1435ea1..ed126ab 100644 --- a/playbook/execute.go +++ b/playbook/execute.go @@ -334,20 +334,29 @@ func (pb *Playbook) Execute(task task.RuntimeTask) error { if v.JSON != nil { proc, err := v.JSON.Init(task) if err == nil { - flame.AddSink(wf, proc.Process) - outputs[k] = proc + if srcNode, ok := outNodes[v.JSON.From]; ok { + s := flame.AddSink(wf, proc.Process) + outputs[k] = proc + s.Connect(srcNode) + } } } else if v.Table != nil { proc, err := v.Table.Init(task) if err == nil { - flame.AddSink(wf, proc.Process) - outputs[k] = proc + if srcNode, ok := outNodes[v.Table.From]; ok { + s := flame.AddSink(wf, proc.Process) + outputs[k] = proc + s.Connect(srcNode) + } } } else if v.Graph != nil { proc, err := v.Graph.Init(task) if err == nil { - flame.AddSink(wf, proc.Process) - outputs[k] = proc + if srcNode, ok := outNodes[v.Graph.From]; ok { + s := flame.AddSink(wf, proc.Process) + outputs[k] = proc + s.Connect(srcNode) + } } } } diff --git a/playbook/output_graph.go b/playbook/output_graph.go index 273b0fa..7477c6f 100644 --- a/playbook/output_graph.go +++ b/playbook/output_graph.go @@ -18,7 +18,8 @@ type EdgeFix struct { } type OutputGraph struct { - Path string `json:"path"` + From string `json:"from"` + Output string `json:"output"` Schema string `json:"schema"` Title string `json:"title"` Clean bool `json:"clean"` @@ -27,7 +28,7 @@ type OutputGraph struct { } func (oj *OutputGraph) GetOutputs(task task.RuntimeTask) []string { - output, err := evaluate.ExpressionString(oj.Path, task.GetConfig(), nil) + output, err := evaluate.ExpressionString(oj.Output, task.GetConfig(), nil) if err != nil { return []string{} } @@ -42,6 +43,9 @@ type graphBuildProcess struct { sch graph.GraphSchema class string + edgeName string + verrtexName string + edgeFix evaluate.Processor objectCount int vertexCount int @@ -59,10 +63,17 @@ func (ts OutputGraph) Init(task task.RuntimeTask) (OutputProcessor, error) { if err != nil { return nil, err } + + output, err := evaluate.ExpressionString(ts.Output, task.GetConfig(), nil) + + //TODO: make this more flexible + edgeName := output + ".edge.json.gz" + vertexName := output + ".vertex.json.gz" + //force the two emitters to be created. nil messages don't get emitted //but the output file will be created - task.Emit("vertex", nil) - task.Emit("edge", nil) + task.Emit(vertexName, nil) + task.Emit(edgeName, nil) var edgeFix evaluate.Processor if ts.EdgeFix != nil { @@ -77,7 +88,17 @@ func (ts OutputGraph) Init(task task.RuntimeTask) (OutputProcessor, error) { edgeFix = c } } - return &graphBuildProcess{ts, task, sc, ts.Title, edgeFix, 0, 0, 0}, nil + return &graphBuildProcess{ + config: ts, + task: task, + sch: sc, + edgeName: edgeName, + verrtexName: vertexName, + class: ts.Title, + edgeFix: edgeFix, + objectCount: 0, + vertexCount: 0, + edgeCount: 0}, nil } func (ts OutputGraph) GetRequiredParams() []config.ParamRequest { @@ -109,7 +130,7 @@ func (ts *graphBuildProcess) Process(i map[string]interface{}) { for i := range o { if o[i].Vertex != nil { ts.vertexCount++ - err := ts.task.Emit("vertex", ts.vertexToMap(o[i].Vertex)) + err := ts.task.Emit(ts.verrtexName, ts.vertexToMap(o[i].Vertex)) if err != nil { logger.Error("Emit Error: %s", err) } @@ -124,7 +145,7 @@ func (ts *graphBuildProcess) Process(i map[string]interface{}) { } } ts.edgeCount++ - err := ts.task.Emit("edge", edgeData) + err := ts.task.Emit(ts.edgeName, edgeData) if err != nil { logger.Error("Emit Error: %s", err) } diff --git a/playbook/output_json.go b/playbook/output_json.go index 869984b..56e5097 100644 --- a/playbook/output_json.go +++ b/playbook/output_json.go @@ -27,11 +27,12 @@ type OutputProcessor interface { } type OutputJSON struct { - Path string `json:"path"` + Output string `json:"output"` + From string `json:"from"` } func (oj *OutputJSON) GetOutputs(task task.RuntimeTask) []string { - output, err := evaluate.ExpressionString(oj.Path, task.GetConfig(), nil) + output, err := evaluate.ExpressionString(oj.Output, task.GetConfig(), nil) if err != nil { return []string{} } @@ -51,10 +52,10 @@ type jsonOutputProcess struct { } func (op *jsonOutputProcess) Close() { - logger.Info("Emit Summary", "name", op.config.Path, "count", op.count) + logger.Info("Emit Summary", "name", op.config.Output, "count", op.count) } func (op *jsonOutputProcess) Process(i map[string]interface{}) { op.count++ - op.task.Emit(op.config.Path, i) + op.task.Emit(op.config.Output, i) } diff --git a/playbook/output_table.go b/playbook/output_table.go index db7eca3..149929d 100644 --- a/playbook/output_table.go +++ b/playbook/output_table.go @@ -15,6 +15,7 @@ import ( ) type OutputTable struct { + From string `json:"from"` Output string `json:"output" jsonschema_description:"Name of file to create"` Columns []string `json:"columns" jsonschema_description:"Columns to be written into table file"` Header string `json:"header"` diff --git a/test/config.yaml b/test/config.yaml index 4405dba..9b45d65 100644 --- a/test/config.yaml +++ b/test/config.yaml @@ -4,8 +4,8 @@ - 200 - 192 outputs: - - output/pathway_commons.edges.edge.json.gz - - output/pathway_commons.nodes.node.json.gz + - output/pathway_commons.edges.json.gz + - output/pathway_commons.nodes.json.gz - playbook: examples/pathwaycommons/gene_collect.yaml LineCount: - 3 @@ -26,7 +26,7 @@ LineCount: - 10 outputs: - - output-tsv/gdc-projects.tranform.case-mondo.json.gz + - output-tsv/gdc-projects.transform.case-mondo.json.gz - playbook: examples/gdc/gdc-convert.yaml LineCount: - 0 #TODO: fix this test @@ -41,8 +41,8 @@ - 1138 - 873 outputs: - - output/sifter.edge.edge.json.gz - - output/sifter.vertex.vertex.json.gz + - output/pfb.edge.json + - output/pfb.vertex.json - playbook: examples/code-ref/Pipeline.yaml - playbook: examples/code-ref/flatMappipeline.yaml LineCount: diff --git a/test/examples/gdc/gdc-convert.yaml b/test/examples/gdc/gdc-convert.yaml index a26ac30..3293edf 100644 --- a/test/examples/gdc/gdc-convert.yaml +++ b/test/examples/gdc/gdc-convert.yaml @@ -15,6 +15,23 @@ inputs: jsonLoad: input: "{{params.cases}}" +outputs: + caseFile: + json: + output: gdc.caseObject.case.json.gz + from: caseObject + caseGraph: + graph: + output: gdc.caseGraph + from: caseObject + schema: "{{params.schema}}" + title: Case + EdgeFix: + method: test + gpython: + $ref: test.py + + pipelines: caseObject: - from: caseData @@ -26,17 +43,3 @@ pipelines: - objectValidate: title: Case schema: "{{params.schema}}" - - emit: - # Testing that this doesn't do anything - useName: False - name: case - - caseGraph: - - from: caseObject - - graphBuild: - schema: "{{params.schema}}" - title: Case - EdgeFix: - method: test - gpython: - $ref: test.py diff --git a/test/examples/gene-table/gene-table.yaml b/test/examples/gene-table/gene-table.yaml index 833402d..f718658 100644 --- a/test/examples/gene-table/gene-table.yaml +++ b/test/examples/gene-table/gene-table.yaml @@ -24,15 +24,19 @@ inputs: - protein_accession.version - Ensembl_protein_identifier +outputs: + geneTable: + table: + from: translate + output: "gene.table" + columns: + - GeneID + - Ensembl_gene_identifier + pipelines: translate: - from: genes - filter: field: tax_id match: "9606" - - tableWrite: - output: "gene.table" - columns: - - GeneID - - Ensembl_gene_identifier diff --git a/test/examples/lookup/inline-table.yaml b/test/examples/lookup/inline-table.yaml index 7d98ca5..f5a9d48 100644 --- a/test/examples/lookup/inline-table.yaml +++ b/test/examples/lookup/inline-table.yaml @@ -12,6 +12,11 @@ inputs: jsonLoad: input: "{{params.json}}" +outputs: + translated: + json: + output: sifter.transform.test.json.gz + from: transform pipelines: transform: @@ -21,5 +26,3 @@ pipelines: table: TCGA-KIRC: 1 TCGA-SARC: 2 - - emit: - name: test diff --git a/test/examples/lookup/tsv-table-replace.yaml b/test/examples/lookup/tsv-table-replace.yaml index 2adc83e..58a6575 100644 --- a/test/examples/lookup/tsv-table-replace.yaml +++ b/test/examples/lookup/tsv-table-replace.yaml @@ -16,8 +16,14 @@ inputs: jsonLoad: input: "{{params.cases}}" +outputs: + case-mondo: + json: + output: gdc-projects.transform.case-mondo.json.gz + from: transform + pipelines: - tranform: + transform: - from: caseReader - fieldProcess: field: project @@ -30,5 +36,3 @@ pipelines: - mondo key: disease value: mondo - - emit: - name: case-mondo \ No newline at end of file diff --git a/test/examples/pathwaycommons/gene_collect.yaml b/test/examples/pathwaycommons/gene_collect.yaml index 3f84cf5..1fa2685 100644 --- a/test/examples/pathwaycommons/gene_collect.yaml +++ b/test/examples/pathwaycommons/gene_collect.yaml @@ -13,6 +13,12 @@ inputs: sep: "\t" columns: [_from, _label, _to] +outputs: + sifout: + json: + output: sifout.json.gz + from: geneReduce + pipelines: geneReduce: - from: sifFile @@ -25,9 +31,3 @@ pipelines: y["_from"] = x["_from"] y["_to"].append(x["_to"]) return y - newpip: - - from: geneReduce - - emit: - useName: True - name: sifout - diff --git a/test/examples/pathwaycommons/pathway_commons.yaml b/test/examples/pathwaycommons/pathway_commons.yaml index 4a16fa1..5752d52 100644 --- a/test/examples/pathwaycommons/pathway_commons.yaml +++ b/test/examples/pathwaycommons/pathway_commons.yaml @@ -14,6 +14,16 @@ inputs: sep: "\t" columns: [_from, _label, _to] +outputs: + edgeFile: + json: + from: sifFile + output: pathway_commons.edges.json.gz + nodeFile: + json: + from: nodes + output: pathway_commons.nodes.json.gz + pipelines: nodes: - from: sifFile @@ -29,10 +39,3 @@ pipelines: - project: mapping: _label: "Protein" - - emit: - name: node - - edges: - - from: sifFile - - emit: - name: edge diff --git a/test/examples/pfb/transform.yaml b/test/examples/pfb/transform.yaml index 63687f4..b2c7ab5 100644 --- a/test/examples/pfb/transform.yaml +++ b/test/examples/pfb/transform.yaml @@ -11,6 +11,16 @@ inputs: avroLoad: input: "{{params.file}}" +outputs: + vertexFile: + json: + output: pfb.vertex.json + from: vertex + edgeFile: + json: + output: pfb.edge.json + from: edge + pipelines: transform: - from: pfb @@ -56,9 +66,6 @@ pipelines: o = x["object"][x["name"]] return { "gid" : x["name"] + ":" + x["id"], "label" : x["name"], "data" : o } - - emit: - name: vertex - edge: - from: transform - fieldProcess: @@ -74,5 +81,3 @@ pipelines: - to - from - label - - emit: - name: edge \ No newline at end of file diff --git a/test/resources/project.yaml b/test/resources/project.yaml index da59a43..1f19e15 100644 --- a/test/resources/project.yaml +++ b/test/resources/project.yaml @@ -12,6 +12,11 @@ inputs: jsonLoad: input: "{{params.genes}}" +outputs: + new_ids: + json: + from: step1 + output: new_ids.ndjson pipelines: step1: @@ -19,5 +24,3 @@ pipelines: - project: mapping: _id: "{{row.ensembl_gene_id}}" - - emit: - name: new_ids From c92f5b672a1646f814c0783753ee24a160810d9b Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Wed, 21 Jan 2026 22:38:49 -0800 Subject: [PATCH 07/19] Fixing input/output field names to just be `path` --- extractors/avro_load.go | 6 +++--- extractors/glob_load.go | 12 ++++++------ extractors/json_load.go | 6 +++--- extractors/sqldump_step.go | 6 +++--- extractors/tabular_load.go | 9 ++++----- extractors/transpose_load.go | 8 ++++---- extractors/xml_step.go | 6 +++--- playbook/output_graph.go | 6 +++--- playbook/output_json.go | 10 +++++----- playbook/output_table.go | 8 ++++---- test/examples/gdc/gdc-convert.yaml | 6 +++--- test/examples/gene-table/gene-table.yaml | 4 ++-- test/examples/lookup/inline-table.yaml | 4 ++-- test/examples/lookup/tsv-table-replace.yaml | 4 ++-- test/examples/pathwaycommons/gene_collect.yaml | 4 ++-- test/examples/pathwaycommons/pathway_commons.yaml | 6 +++--- test/examples/pfb/transform.yaml | 6 +++--- test/resources/project.yaml | 4 ++-- 18 files changed, 57 insertions(+), 58 deletions(-) diff --git a/extractors/avro_load.go b/extractors/avro_load.go index 66d4615..3308e44 100644 --- a/extractors/avro_load.go +++ b/extractors/avro_load.go @@ -13,13 +13,13 @@ import ( ) type AvroLoadStep struct { - Input string `json:"input" jsonschema_description:"Path of avro object file to transform"` + Path string `json:"path" jsonschema_description:"Path of avro object file to transform"` } func (ml *AvroLoadStep) Start(task task.RuntimeTask) (chan map[string]interface{}, error) { logger.Debug("Starting Avro Load") - input, err := evaluate.ExpressionString(ml.Input, task.GetConfig(), nil) + input, err := evaluate.ExpressionString(ml.Path, task.GetConfig(), nil) if err != nil { return nil, err } @@ -59,7 +59,7 @@ func (ml *AvroLoadStep) Start(task task.RuntimeTask) (chan map[string]interface{ func (ml *AvroLoadStep) GetRequiredParams() []config.ParamRequest { out := []config.ParamRequest{} - for _, s := range evaluate.ExpressionIDs(ml.Input) { + for _, s := range evaluate.ExpressionIDs(ml.Path) { out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } return out diff --git a/extractors/glob_load.go b/extractors/glob_load.go index 7f61a2d..fc752ea 100644 --- a/extractors/glob_load.go +++ b/extractors/glob_load.go @@ -14,7 +14,7 @@ import ( type GlobLoadStep struct { StoreFilename string `json:"storeFilename"` StoreFilepath string `json:"storeFilepath"` - Input string `json:"input" jsonschema_description:"Path of avro object file to transform"` + Path string `json:"path" jsonschema_description:"Path of avro object file to transform"` Parallelize bool `json:"parallelize"` XMLLoad *XMLLoadStep `json:"xmlLoad"` TableLoad *TableLoadStep `json:"tableLoad" jsonschema_description:"Run transform pipeline on a TSV or CSV"` @@ -28,7 +28,7 @@ type fileSource struct { } func (gl *GlobLoadStep) Start(task task.RuntimeTask) (chan map[string]interface{}, error) { - input, err := evaluate.ExpressionString(gl.Input, task.GetConfig(), nil) + input, err := evaluate.ExpressionString(gl.Path, task.GetConfig(), nil) if err != nil { return nil, err } @@ -53,15 +53,15 @@ func (gl *GlobLoadStep) Start(task task.RuntimeTask) (chan map[string]interface{ var a Source if gl.XMLLoad != nil { t := *gl.XMLLoad - t.Input = f + t.Path = f a = &t } else if gl.JSONLoad != nil { t := *gl.JSONLoad - t.Input = f + t.Path = f a = &t } else if gl.TableLoad != nil { t := *gl.TableLoad - t.Input = f + t.Path = f a = &t } sources <- fileSource{source: a, file: f} @@ -99,7 +99,7 @@ func (gl *GlobLoadStep) Start(task task.RuntimeTask) (chan map[string]interface{ func (gl *GlobLoadStep) GetRequiredParams() []config.ParamRequest { out := []config.ParamRequest{} - for _, s := range evaluate.ExpressionIDs(gl.Input) { + for _, s := range evaluate.ExpressionIDs(gl.Path) { out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } return out diff --git a/extractors/json_load.go b/extractors/json_load.go index a37c297..d2faa8f 100644 --- a/extractors/json_load.go +++ b/extractors/json_load.go @@ -15,14 +15,14 @@ import ( ) type JSONLoadStep struct { - Input string `json:"input" jsonschema_description:"Path of multiline JSON file to transform"` + Path string `json:"path" jsonschema_description:"Path of multiline JSON file to transform"` Transform transform.Pipe `json:"transform" jsonschema_description:"Transformation Pipeline"` Multiline bool `json:"multiline" jsonschema_description:"Load file as a single multiline JSON object"` } func (ml *JSONLoadStep) Start(task task.RuntimeTask) (chan map[string]interface{}, error) { logger.Debug("Starting JSON Load") - input, err := evaluate.ExpressionString(ml.Input, task.GetConfig(), nil) + input, err := evaluate.ExpressionString(ml.Path, task.GetConfig(), nil) if err != nil { return nil, err } @@ -69,7 +69,7 @@ func (ml *JSONLoadStep) Start(task task.RuntimeTask) (chan map[string]interface{ func (ml *JSONLoadStep) GetRequiredParams() []config.ParamRequest { out := []config.ParamRequest{} - for _, s := range evaluate.ExpressionIDs(ml.Input) { + for _, s := range evaluate.ExpressionIDs(ml.Path) { out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } return out diff --git a/extractors/sqldump_step.go b/extractors/sqldump_step.go index cf90c20..256638f 100644 --- a/extractors/sqldump_step.go +++ b/extractors/sqldump_step.go @@ -15,13 +15,13 @@ import ( ) type SQLDumpStep struct { - Input string `json:"input" jsonschema_description:"Path to the SQL dump file"` + Path string `json:"Path" jsonschema_description:"Path to the SQL dump file"` Tables []string `json:"tables" jsonschema_description:"Array of transforms for the different tables in the SQL dump"` } func (ml *SQLDumpStep) Start(task task.RuntimeTask) (chan map[string]interface{}, error) { - input, err := evaluate.ExpressionString(ml.Input, task.GetConfig(), nil) + input, err := evaluate.ExpressionString(ml.Path, task.GetConfig(), nil) if err != nil { return nil, err } @@ -116,7 +116,7 @@ func (ml *SQLDumpStep) Start(task task.RuntimeTask) (chan map[string]interface{} func (ml *SQLDumpStep) GetRequiredParams() []config.ParamRequest { out := []config.ParamRequest{} - for _, s := range evaluate.ExpressionIDs(ml.Input) { + for _, s := range evaluate.ExpressionIDs(ml.Path) { out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } return out diff --git a/extractors/tabular_load.go b/extractors/tabular_load.go index cceb8de..002503b 100644 --- a/extractors/tabular_load.go +++ b/extractors/tabular_load.go @@ -16,7 +16,7 @@ import ( ) type TableLoadStep struct { - Input string `json:"input" jsonschema_description:"TSV to be transformed"` + Path string `json:"Path" jsonschema_description:"TSV to be transformed"` RowSkip int `json:"rowSkip" jsonschema_description:"Number of header rows to skip"` Columns []string `json:"columns" jsonschema_description:"Manually set names of columns"` ExtraColumns string `json:"extraColumns" jsonschema_description:"Columns beyond originally declared columns will be placed in this array"` @@ -50,7 +50,7 @@ func buildUniqueArray(src []string) []string { func (ml *TableLoadStep) Start(task task.RuntimeTask) (chan map[string]interface{}, error) { logger.Info("Starting Table Load") - input, err := evaluate.ExpressionString(ml.Input, task.GetConfig(), nil) + input, err := evaluate.ExpressionString(ml.Path, task.GetConfig(), nil) if err != nil { return nil, err } @@ -75,8 +75,7 @@ func (ml *TableLoadStep) Start(task task.RuntimeTask) (chan map[string]interface } else { inputStream = gfile } - } - if err != nil { + } else { return nil, err } @@ -162,7 +161,7 @@ func (ml *TableLoadStep) Start(task task.RuntimeTask) (chan map[string]interface func (ml *TableLoadStep) GetRequiredParams() []config.ParamRequest { out := []config.ParamRequest{} - for _, s := range evaluate.ExpressionIDs(ml.Input) { + for _, s := range evaluate.ExpressionIDs(ml.Path) { out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } return out diff --git a/extractors/transpose_load.go b/extractors/transpose_load.go index 95cf765..326914d 100644 --- a/extractors/transpose_load.go +++ b/extractors/transpose_load.go @@ -20,7 +20,7 @@ import ( ) type TransposeLoadStep struct { - Input string `json:"input" jsonschema_description:"TSV to be transformed"` + Path string `json:"path" jsonschema_description:"TSV to be transformed"` RowSkip int `json:"rowSkip" jsonschema_description:"Number of header rows to skip"` Sep string `json:"sep" jsonschema_description:"Separator \\t for TSVs or , for CSVs"` UseDB bool `json:"useDB" jsonschema_description:"Do transpose without caching matrix in memory. Takes longer but works on large files"` @@ -28,7 +28,7 @@ type TransposeLoadStep struct { } func (ml *TransposeLoadStep) Start(task task.RuntimeTask) (chan map[string]interface{}, error) { - input, err := evaluate.ExpressionString(ml.Input, task.GetConfig(), nil) + input, err := evaluate.ExpressionString(ml.Path, task.GetConfig(), nil) if err != nil { return nil, err } @@ -50,7 +50,7 @@ func (ml *TransposeLoadStep) Start(task task.RuntimeTask) (chan map[string]inter func (ml *TransposeLoadStep) GetRequiredParams() []config.ParamRequest { out := []config.ParamRequest{} - for _, s := range evaluate.ExpressionIDs(ml.Input) { + for _, s := range evaluate.ExpressionIDs(ml.Path) { out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } return out @@ -316,7 +316,7 @@ func transposeInTable(workdir string, fieldSize int, c csvReader, out chan map[s columns := []string{} for row := int64(0); row < rowCount; row++ { buf := make([]byte, fieldSize) - table.ReadAt(buf, row*stepSize) + _, err := table.ReadAt(buf, row*stepSize) tmp := bytes.Split(buf, []byte{0}) if err == nil { columns = append(columns, string(tmp[0])) diff --git a/extractors/xml_step.go b/extractors/xml_step.go index fd10086..b3743b6 100644 --- a/extractors/xml_step.go +++ b/extractors/xml_step.go @@ -19,12 +19,12 @@ import ( ) type XMLLoadStep struct { - Input string `json:"input"` + Path string `json:"path"` Level int `json:"level"` } func (ml *XMLLoadStep) Start(task task.RuntimeTask) (chan map[string]any, error) { - input, err := evaluate.ExpressionString(ml.Input, task.GetConfig(), nil) + input, err := evaluate.ExpressionString(ml.Path, task.GetConfig(), nil) if err != nil { logger.Error("Error open xml", "error", err) return nil, err @@ -112,7 +112,7 @@ func (ml *XMLLoadStep) Start(task task.RuntimeTask) (chan map[string]any, error) func (ml *XMLLoadStep) GetRequiredParams() []config.ParamRequest { out := []config.ParamRequest{} - for _, s := range evaluate.ExpressionIDs(ml.Input) { + for _, s := range evaluate.ExpressionIDs(ml.Path) { out = append(out, config.ParamRequest{Type: "File", Name: config.TrimPrefix(s)}) } return out diff --git a/playbook/output_graph.go b/playbook/output_graph.go index 7477c6f..34d43ee 100644 --- a/playbook/output_graph.go +++ b/playbook/output_graph.go @@ -19,7 +19,7 @@ type EdgeFix struct { type OutputGraph struct { From string `json:"from"` - Output string `json:"output"` + Path string `json:"path"` Schema string `json:"schema"` Title string `json:"title"` Clean bool `json:"clean"` @@ -28,7 +28,7 @@ type OutputGraph struct { } func (oj *OutputGraph) GetOutputs(task task.RuntimeTask) []string { - output, err := evaluate.ExpressionString(oj.Output, task.GetConfig(), nil) + output, err := evaluate.ExpressionString(oj.Path, task.GetConfig(), nil) if err != nil { return []string{} } @@ -64,7 +64,7 @@ func (ts OutputGraph) Init(task task.RuntimeTask) (OutputProcessor, error) { return nil, err } - output, err := evaluate.ExpressionString(ts.Output, task.GetConfig(), nil) + output, err := evaluate.ExpressionString(ts.Path, task.GetConfig(), nil) //TODO: make this more flexible edgeName := output + ".edge.json.gz" diff --git a/playbook/output_json.go b/playbook/output_json.go index 56e5097..f1182bb 100644 --- a/playbook/output_json.go +++ b/playbook/output_json.go @@ -27,12 +27,12 @@ type OutputProcessor interface { } type OutputJSON struct { - Output string `json:"output"` - From string `json:"from"` + Path string `json:"path"` + From string `json:"from"` } func (oj *OutputJSON) GetOutputs(task task.RuntimeTask) []string { - output, err := evaluate.ExpressionString(oj.Output, task.GetConfig(), nil) + output, err := evaluate.ExpressionString(oj.Path, task.GetConfig(), nil) if err != nil { return []string{} } @@ -52,10 +52,10 @@ type jsonOutputProcess struct { } func (op *jsonOutputProcess) Close() { - logger.Info("Emit Summary", "name", op.config.Output, "count", op.count) + logger.Info("Emit Summary", "name", op.config.Path, "count", op.count) } func (op *jsonOutputProcess) Process(i map[string]interface{}) { op.count++ - op.task.Emit(op.config.Output, i) + op.task.Emit(op.config.Path, i) } diff --git a/playbook/output_table.go b/playbook/output_table.go index 149929d..2210fb9 100644 --- a/playbook/output_table.go +++ b/playbook/output_table.go @@ -16,7 +16,7 @@ import ( type OutputTable struct { From string `json:"from"` - Output string `json:"output" jsonschema_description:"Name of file to create"` + Path string `json:"path" jsonschema_description:"Name of file to create"` Columns []string `json:"columns" jsonschema_description:"Columns to be written into table file"` Header string `json:"header"` SkipColumnHeader bool `json:"skipColumnHeader"` @@ -37,7 +37,7 @@ func (tw *OutputTable) Init(task task.RuntimeTask) (OutputProcessor, error) { sep = rune(tw.Sep[0]) } - output, err := evaluate.ExpressionString(tw.Output, task.GetConfig(), nil) + output, err := evaluate.ExpressionString(tw.Path, task.GetConfig(), nil) if err != nil { return nil, err } @@ -66,7 +66,7 @@ func (tw *OutputTable) Init(task task.RuntimeTask) (OutputProcessor, error) { } func (tw *OutputTable) GetOutputs(task task.RuntimeTask) []string { - output, err := evaluate.ExpressionString(tw.Output, task.GetConfig(), nil) + output, err := evaluate.ExpressionString(tw.Path, task.GetConfig(), nil) if err != nil { return []string{} } @@ -95,7 +95,7 @@ func (tp *tableWriteProcess) Process(i map[string]any) { } func (tp *tableWriteProcess) Close() { - logger.Debug("Closing tableWriter: %s", tp.config.Output) + logger.Debug("Closing tableWriter: %s", tp.config.Path) tp.writer.Flush() tp.out.Close() tp.handle.Close() diff --git a/test/examples/gdc/gdc-convert.yaml b/test/examples/gdc/gdc-convert.yaml index 3293edf..428740e 100644 --- a/test/examples/gdc/gdc-convert.yaml +++ b/test/examples/gdc/gdc-convert.yaml @@ -13,16 +13,16 @@ params: inputs: caseData: jsonLoad: - input: "{{params.cases}}" + path: "{{params.cases}}" outputs: caseFile: json: - output: gdc.caseObject.case.json.gz + path: gdc.caseObject.case.json.gz from: caseObject caseGraph: graph: - output: gdc.caseGraph + path: gdc.caseGraph from: caseObject schema: "{{params.schema}}" title: Case diff --git a/test/examples/gene-table/gene-table.yaml b/test/examples/gene-table/gene-table.yaml index f718658..773c279 100644 --- a/test/examples/gene-table/gene-table.yaml +++ b/test/examples/gene-table/gene-table.yaml @@ -14,7 +14,7 @@ params: inputs: genes: tableLoad: - input: "{{params.geneTSV}}" + path: "{{params.geneTSV}}" columns: - tax_id - GeneID @@ -28,7 +28,7 @@ outputs: geneTable: table: from: translate - output: "gene.table" + path: "gene.table" columns: - GeneID - Ensembl_gene_identifier diff --git a/test/examples/lookup/inline-table.yaml b/test/examples/lookup/inline-table.yaml index f5a9d48..83d20d5 100644 --- a/test/examples/lookup/inline-table.yaml +++ b/test/examples/lookup/inline-table.yaml @@ -10,12 +10,12 @@ params: inputs: jsonData: jsonLoad: - input: "{{params.json}}" + path: "{{params.json}}" outputs: translated: json: - output: sifter.transform.test.json.gz + path: sifter.transform.test.json.gz from: transform pipelines: diff --git a/test/examples/lookup/tsv-table-replace.yaml b/test/examples/lookup/tsv-table-replace.yaml index 58a6575..cede805 100644 --- a/test/examples/lookup/tsv-table-replace.yaml +++ b/test/examples/lookup/tsv-table-replace.yaml @@ -14,12 +14,12 @@ params: inputs: caseReader: jsonLoad: - input: "{{params.cases}}" + path: "{{params.cases}}" outputs: case-mondo: json: - output: gdc-projects.transform.case-mondo.json.gz + path: gdc-projects.transform.case-mondo.json.gz from: transform pipelines: diff --git a/test/examples/pathwaycommons/gene_collect.yaml b/test/examples/pathwaycommons/gene_collect.yaml index 1fa2685..688b65d 100644 --- a/test/examples/pathwaycommons/gene_collect.yaml +++ b/test/examples/pathwaycommons/gene_collect.yaml @@ -9,14 +9,14 @@ params: inputs: sifFile: tableLoad: - input: "{{params.sifFile}}" + path: "{{params.sifFile}}" sep: "\t" columns: [_from, _label, _to] outputs: sifout: json: - output: sifout.json.gz + path: sifout.json.gz from: geneReduce pipelines: diff --git a/test/examples/pathwaycommons/pathway_commons.yaml b/test/examples/pathwaycommons/pathway_commons.yaml index 5752d52..d4d379a 100644 --- a/test/examples/pathwaycommons/pathway_commons.yaml +++ b/test/examples/pathwaycommons/pathway_commons.yaml @@ -10,7 +10,7 @@ params: inputs: sifFile: tableLoad: - input: "{{params.sifFile}}" + path: "{{params.sifFile}}" sep: "\t" columns: [_from, _label, _to] @@ -18,11 +18,11 @@ outputs: edgeFile: json: from: sifFile - output: pathway_commons.edges.json.gz + path: pathway_commons.edges.json.gz nodeFile: json: from: nodes - output: pathway_commons.nodes.json.gz + path: pathway_commons.nodes.json.gz pipelines: nodes: diff --git a/test/examples/pfb/transform.yaml b/test/examples/pfb/transform.yaml index b2c7ab5..76f6084 100644 --- a/test/examples/pfb/transform.yaml +++ b/test/examples/pfb/transform.yaml @@ -9,16 +9,16 @@ outdir: output/ inputs: pfb: avroLoad: - input: "{{params.file}}" + path: "{{params.file}}" outputs: vertexFile: json: - output: pfb.vertex.json + path: pfb.vertex.json from: vertex edgeFile: json: - output: pfb.edge.json + path: pfb.edge.json from: edge pipelines: diff --git a/test/resources/project.yaml b/test/resources/project.yaml index 1f19e15..72ed3b8 100644 --- a/test/resources/project.yaml +++ b/test/resources/project.yaml @@ -10,13 +10,13 @@ params: inputs: geneData: jsonLoad: - input: "{{params.genes}}" + path: "{{params.genes}}" outputs: new_ids: json: from: step1 - output: new_ids.ndjson + path: new_ids.ndjson pipelines: step1: From fd629273c344f2ff8408baa16463c7e48c09e7a2 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Wed, 21 Jan 2026 22:44:49 -0800 Subject: [PATCH 08/19] Simplfying input type selection (jsonLoad -> json) --- extractors/interface.go | 16 ++++++++-------- test/examples/gdc/gdc-convert.yaml | 2 +- test/examples/gene-table/gene-table.yaml | 2 +- test/examples/lookup/inline-table.yaml | 2 +- test/examples/lookup/tsv-table-replace.yaml | 2 +- test/examples/pathwaycommons/gene_collect.yaml | 2 +- .../examples/pathwaycommons/pathway_commons.yaml | 2 +- test/examples/pfb/transform.yaml | 2 +- test/resources/project.yaml | 2 +- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/extractors/interface.go b/extractors/interface.go index 6b1d6ed..9655dc8 100644 --- a/extractors/interface.go +++ b/extractors/interface.go @@ -14,16 +14,16 @@ type Source interface { type Extractor struct { Description string `json:"description" jsonschema_description:"Human Readable description of step"` - XMLLoad *XMLLoadStep `json:"xmlLoad"` - TableLoad *TableLoadStep `json:"tableLoad" jsonschema_description:"Run transform pipeline on a TSV or CSV"` - JSONLoad *JSONLoadStep `json:"jsonLoad" jsonschema_description:"Run a transform pipeline on a multi line json file"` - SQLDumpLoad *SQLDumpStep `json:"sqldumpLoad" jsonschema_description:"Parse the content of a SQL dump to find insert and run a transform pipeline"` - GripperLoad *GripperLoadStep `json:"gripperLoad" jsonschema_description:"Use a GRIPPER server to get data and run a transform pipeline"` - AvroLoad *AvroLoadStep `json:"avroLoad" jsonschema_description:"Load data from avro file"` + XMLLoad *XMLLoadStep `json:"xml"` + TableLoad *TableLoadStep `json:"table" jsonschema_description:"Run transform pipeline on a TSV or CSV"` + JSONLoad *JSONLoadStep `json:"json" jsonschema_description:"Run a transform pipeline on a multi line json file"` + SQLDumpLoad *SQLDumpStep `json:"sqldump" jsonschema_description:"Parse the content of a SQL dump to find insert and run a transform pipeline"` + GripperLoad *GripperLoadStep `json:"gripper" jsonschema_description:"Use a GRIPPER server to get data and run a transform pipeline"` + AvroLoad *AvroLoadStep `json:"avro" jsonschema_description:"Load data from avro file"` Embedded *EmbeddedLoader `json:"embedded"` Glob *GlobLoadStep `json:"glob"` - SQLiteLoad *SQLiteStep `json:"sqliteLoad"` - TransposeLoad *TransposeLoadStep `json:"transposeLoad"` + SQLiteLoad *SQLiteStep `json:"sqlite"` + TransposeLoad *TransposeLoadStep `json:"transpose"` Plugin *PluginLoadStep `json:"plugin"` //Untar *UntarStep `json:"untar" jsonschema_description:"Untar a file"` //FileGlob *FileGlobStep `json:"fileGlob" jsonschema_description:"Scan a directory and run a ETL pipeline on each of the files"` diff --git a/test/examples/gdc/gdc-convert.yaml b/test/examples/gdc/gdc-convert.yaml index 428740e..ddd9e68 100644 --- a/test/examples/gdc/gdc-convert.yaml +++ b/test/examples/gdc/gdc-convert.yaml @@ -12,7 +12,7 @@ params: inputs: caseData: - jsonLoad: + json: path: "{{params.cases}}" outputs: diff --git a/test/examples/gene-table/gene-table.yaml b/test/examples/gene-table/gene-table.yaml index 773c279..852fc2e 100644 --- a/test/examples/gene-table/gene-table.yaml +++ b/test/examples/gene-table/gene-table.yaml @@ -13,7 +13,7 @@ params: inputs: genes: - tableLoad: + table: path: "{{params.geneTSV}}" columns: - tax_id diff --git a/test/examples/lookup/inline-table.yaml b/test/examples/lookup/inline-table.yaml index 83d20d5..feed4e7 100644 --- a/test/examples/lookup/inline-table.yaml +++ b/test/examples/lookup/inline-table.yaml @@ -9,7 +9,7 @@ params: inputs: jsonData: - jsonLoad: + json: path: "{{params.json}}" outputs: diff --git a/test/examples/lookup/tsv-table-replace.yaml b/test/examples/lookup/tsv-table-replace.yaml index cede805..85a1030 100644 --- a/test/examples/lookup/tsv-table-replace.yaml +++ b/test/examples/lookup/tsv-table-replace.yaml @@ -13,7 +13,7 @@ params: inputs: caseReader: - jsonLoad: + json: path: "{{params.cases}}" outputs: diff --git a/test/examples/pathwaycommons/gene_collect.yaml b/test/examples/pathwaycommons/gene_collect.yaml index 688b65d..dc5db4b 100644 --- a/test/examples/pathwaycommons/gene_collect.yaml +++ b/test/examples/pathwaycommons/gene_collect.yaml @@ -8,7 +8,7 @@ params: inputs: sifFile: - tableLoad: + table: path: "{{params.sifFile}}" sep: "\t" columns: [_from, _label, _to] diff --git a/test/examples/pathwaycommons/pathway_commons.yaml b/test/examples/pathwaycommons/pathway_commons.yaml index d4d379a..efa016f 100644 --- a/test/examples/pathwaycommons/pathway_commons.yaml +++ b/test/examples/pathwaycommons/pathway_commons.yaml @@ -9,7 +9,7 @@ params: inputs: sifFile: - tableLoad: + table: path: "{{params.sifFile}}" sep: "\t" columns: [_from, _label, _to] diff --git a/test/examples/pfb/transform.yaml b/test/examples/pfb/transform.yaml index 76f6084..8eb35bf 100644 --- a/test/examples/pfb/transform.yaml +++ b/test/examples/pfb/transform.yaml @@ -8,7 +8,7 @@ outdir: output/ inputs: pfb: - avroLoad: + avro: path: "{{params.file}}" outputs: diff --git a/test/resources/project.yaml b/test/resources/project.yaml index 72ed3b8..477d0a7 100644 --- a/test/resources/project.yaml +++ b/test/resources/project.yaml @@ -9,7 +9,7 @@ params: inputs: geneData: - jsonLoad: + json: path: "{{params.genes}}" outputs: From ec21042f00198c20ec608d6af8013c5a0c7f23e4 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Thu, 22 Jan 2026 12:12:03 -0800 Subject: [PATCH 09/19] Working on the documentation --- docs/docs/.nav.yml | 10 +++++++ docs/docs/example.md | 8 +++--- docs/docs/inputs/{avroLoad.md => avro.md} | 6 ++--- docs/docs/inputs/glob.md | 2 +- docs/docs/inputs/{jsonLoad.md => json.md} | 10 +++---- docs/docs/inputs/sqldump.md | 4 +-- docs/docs/inputs/{sqliteLoad.md => sqlite.md} | 8 +++--- docs/docs/inputs/{tableLoad.md => table.md} | 8 +++--- docs/docs/inputs/{xmlLoad.md => xml.md} | 8 +++--- .../{transforms => outputs}/graphBuild.md | 2 +- docs/docs/outputs/json.md | 26 +++++++++++++++++++ .../{transforms => outputs}/tableWrite.md | 0 docs/docs/transforms/emit.md | 24 ----------------- mkdocs.yml | 4 +++ 14 files changed, 68 insertions(+), 52 deletions(-) create mode 100644 docs/docs/.nav.yml rename docs/docs/inputs/{avroLoad.md => avro.md} (68%) rename docs/docs/inputs/{jsonLoad.md => json.md} (77%) rename docs/docs/inputs/{sqliteLoad.md => sqlite.md} (83%) rename docs/docs/inputs/{tableLoad.md => table.md} (88%) rename docs/docs/inputs/{xmlLoad.md => xml.md} (67%) rename docs/docs/{transforms => outputs}/graphBuild.md (92%) create mode 100644 docs/docs/outputs/json.md rename docs/docs/{transforms => outputs}/tableWrite.md (100%) delete mode 100644 docs/docs/transforms/emit.md diff --git a/docs/docs/.nav.yml b/docs/docs/.nav.yml new file mode 100644 index 0000000..f6aa456 --- /dev/null +++ b/docs/docs/.nav.yml @@ -0,0 +1,10 @@ + +nav: + - index.md + - example.md + - playbook.md + - schema.md + - config.md + - inputs + - transforms + - outputs \ No newline at end of file diff --git a/docs/docs/example.md b/docs/docs/example.md index d1b4dbe..3ebeb56 100644 --- a/docs/docs/example.md +++ b/docs/docs/example.md @@ -19,7 +19,7 @@ ZIP,COUNTYNAME,STATE,STCOUNTYFP,CLASSFP First is the header of the pipeline. This declares the unique name of the pipeline and it's output directory. -``` +```yaml name: zipcode_map outdir: ./ docs: Converts zipcode TSV into graph elements @@ -30,7 +30,7 @@ There is a default value, so the pipeline can be invoked without passing in any parameters. However, to apply this pipeline to a new input file, the input parameter `zipcode` could be used to define the source file. -``` +```yaml config: schema: ../covid19_datadictionary/gdcdictionary/schemas/ zipcode: ../data/ZIP-COUNTY-FIPS_2017-06.csv @@ -41,7 +41,7 @@ only one input, which is to run the table loader. ``` inputs: tableLoad: - input: "{{config.zipcode}}" + path: "{{config.zipcode}}" sep: "," ``` @@ -179,7 +179,7 @@ outputs: zip2fips: tableWrite: from: - output: zip2fips + path: zip2fips.tsv columns: - ZIP - STCOUNTYFP diff --git a/docs/docs/inputs/avroLoad.md b/docs/docs/inputs/avro.md similarity index 68% rename from docs/docs/inputs/avroLoad.md rename to docs/docs/inputs/avro.md index a271c90..570afbb 100644 --- a/docs/docs/inputs/avroLoad.md +++ b/docs/docs/inputs/avro.md @@ -1,17 +1,17 @@ --- -title: avroLoad +title: avro menu: main: parent: inputs weight: 100 --- -# avroLoad +# avro Load an AvroFile ## Parameters | name | Description | | --- | --- | -| input | Path to input file | +| path | Path to input file | diff --git a/docs/docs/inputs/glob.md b/docs/docs/inputs/glob.md index 5c20fdc..29f7f96 100644 --- a/docs/docs/inputs/glob.md +++ b/docs/docs/inputs/glob.md @@ -27,7 +27,7 @@ as input. inputs: pubmedRead: glob: - input: "{{config.baseline}}/*.xml.gz" + path: "{{config.baseline}}/*.xml.gz" xmlLoad: {} ``` \ No newline at end of file diff --git a/docs/docs/inputs/jsonLoad.md b/docs/docs/inputs/json.md similarity index 77% rename from docs/docs/inputs/jsonLoad.md rename to docs/docs/inputs/json.md index 42a4a2b..9625dcc 100644 --- a/docs/docs/inputs/jsonLoad.md +++ b/docs/docs/inputs/json.md @@ -1,19 +1,19 @@ --- -title: jsonLoad +title: json menu: main: parent: inputs weight: 100 --- -# jsonLoad +# json Load data from a JSON file. Default behavior expects a single dictionary per line. Each line is a seperate entry. The `multiline` parameter reads all of the lines of the files and returns a single object. ## Parameters | name | Description | | --- | --- | -| input | Path of JSON file to transform | +| path | Path of JSON file to transform | | multiline | Load file as a single multiline JSON object | @@ -22,6 +22,6 @@ Load data from a JSON file. Default behavior expects a single dictionary per lin ```yaml inputs: caseData: - jsonLoad: - input: "{{config.casesJSON}}" + json: + path: "{{config.casesJSON}}" ``` \ No newline at end of file diff --git a/docs/docs/inputs/sqldump.md b/docs/docs/inputs/sqldump.md index 1a958d0..f68d48b 100644 --- a/docs/docs/inputs/sqldump.md +++ b/docs/docs/inputs/sqldump.md @@ -13,7 +13,7 @@ Scan file produced produced from sqldump. | Name | Type | Description | |-------|---|--------| -| input | string | Path to the SQL dump file | +| path | string | Path to the SQL dump file | | tables | []string | Names of tables to read out | ## Example @@ -22,7 +22,7 @@ Scan file produced produced from sqldump. inputs: database: sqldumpLoad: - input: "{{config.sql}}" + path: "{{config.sql}}" tables: - cells - cell_tissues diff --git a/docs/docs/inputs/sqliteLoad.md b/docs/docs/inputs/sqlite.md similarity index 83% rename from docs/docs/inputs/sqliteLoad.md rename to docs/docs/inputs/sqlite.md index b0f323f..885f8aa 100644 --- a/docs/docs/inputs/sqliteLoad.md +++ b/docs/docs/inputs/sqlite.md @@ -1,12 +1,12 @@ --- -title: sqliteLoad +title: sqlite menu: main: parent: inputs weight: 100 --- -# sqliteLoad +# sqlite Extract data from an sqlite file @@ -14,7 +14,7 @@ Extract data from an sqlite file | Name | Type | Description | |-------|---|--------| -| input | string | Path to the SQLite file | +| path | string | Path to the SQLite file | | query | string | SQL select statement based input | ## Example @@ -24,7 +24,7 @@ Extract data from an sqlite file inputs: sqlQuery: sqliteLoad: - input: "{{config.sqlite}}" + path: "{{config.sqlite}}" query: "select * from drug_mechanism as a LEFT JOIN MECHANISM_REFS as b on a.MEC_ID=b.MEC_ID LEFT JOIN TARGET_COMPONENTS as c on a.TID=c.TID LEFT JOIN COMPONENT_SEQUENCES as d on c.COMPONENT_ID=d.COMPONENT_ID LEFT JOIN MOLECULE_DICTIONARY as e on a.MOLREGNO=e.MOLREGNO" ``` \ No newline at end of file diff --git a/docs/docs/inputs/tableLoad.md b/docs/docs/inputs/table.md similarity index 88% rename from docs/docs/inputs/tableLoad.md rename to docs/docs/inputs/table.md index 57eaf28..26f0cb8 100644 --- a/docs/docs/inputs/tableLoad.md +++ b/docs/docs/inputs/table.md @@ -1,12 +1,12 @@ --- -title: tableLoad +title: table menu: main: parent: inputs weight: 100 --- -# tableLoad +# table Extract data from tabular file, includiong TSV and CSV files. @@ -14,7 +14,7 @@ Extract data from tabular file, includiong TSV and CSV files. | Name | Type | Description | |-------|---|--------| -| input | string | File to be transformed | +| path | string | File to be transformed | | rowSkip | int | Number of header rows to skip | | columns | []string | Manually set names of columns | | extraColumns | string | Columns beyond originally declared columns will be placed in this array | @@ -31,7 +31,7 @@ config: inputs: gafLoad: tableLoad: - input: "{{config.gafFile}}" + path: "{{config.gafFile}}" columns: - db - id diff --git a/docs/docs/inputs/xmlLoad.md b/docs/docs/inputs/xml.md similarity index 67% rename from docs/docs/inputs/xmlLoad.md rename to docs/docs/inputs/xml.md index ed8c306..e48c069 100644 --- a/docs/docs/inputs/xmlLoad.md +++ b/docs/docs/inputs/xml.md @@ -1,19 +1,19 @@ --- -title: xmlLoad +title: xml menu: main: parent: inputs weight: 100 --- -# xmlLoad +# xml Load an XML file ## Parameters | name | Description | | --- | --- | -| input | Path to input file | +| path | Path to input file | ## Example @@ -21,5 +21,5 @@ Load an XML file inputs: loader: xmlLoad: - input: "{{config.xmlPath}}" + path: "{{config.xmlPath}}" ``` \ No newline at end of file diff --git a/docs/docs/transforms/graphBuild.md b/docs/docs/outputs/graphBuild.md similarity index 92% rename from docs/docs/transforms/graphBuild.md rename to docs/docs/outputs/graphBuild.md index d2bef50..f273cdf 100644 --- a/docs/docs/transforms/graphBuild.md +++ b/docs/docs/outputs/graphBuild.md @@ -6,7 +6,7 @@ menu: weight: 100 --- -# graphBuild +# Output: graphBuild Build graph elements from JSON objects using the JSON Schema graph extensions. diff --git a/docs/docs/outputs/json.md b/docs/docs/outputs/json.md new file mode 100644 index 0000000..d6e93e2 --- /dev/null +++ b/docs/docs/outputs/json.md @@ -0,0 +1,26 @@ +--- +title: json +menu: + main: + parent: transforms + weight: 100 +--- + +# Output: json + +Send data to output file. The naming of the file is `outdir`/`path` + +## Parameters + +| name | Type | Description | +| --- | --- | --- | +| path | string | Path to output file | + +## example + +```yaml +output: + outfile: + json: + path: protein_compound_association.ndjson +``` \ No newline at end of file diff --git a/docs/docs/transforms/tableWrite.md b/docs/docs/outputs/tableWrite.md similarity index 100% rename from docs/docs/transforms/tableWrite.md rename to docs/docs/outputs/tableWrite.md diff --git a/docs/docs/transforms/emit.md b/docs/docs/transforms/emit.md deleted file mode 100644 index 6b7d9ed..0000000 --- a/docs/docs/transforms/emit.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: emit -menu: - main: - parent: transforms - weight: 100 ---- - -# emit - -Send data to output file. The naming of the file is `outdir`/`script name`.`pipeline name`.`emit name`.json.gz - -## Parameters - -| name | Type | Description | -| --- | --- | --- | -| name | string | Name of emit value | - -## example - -```yaml - - emit: - name: protein_compound_association -``` \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 75fc251..0327120 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,6 +2,10 @@ site_name: Sifter docs_dir: docs +plugins: + - search + - awesome-nav + theme: name: material palette: From d88a0fd6dbba3a078103942383def03176c21c73 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Thu, 22 Jan 2026 13:54:40 -0800 Subject: [PATCH 10/19] Working on the documentation --- docs/docs/developers/source_mapping.md | 48 ++++++++++++++++++++++++++ docs/docs/schema.md | 44 ++++++++++++++--------- docs/docs/transforms/fieldParse.md | 18 ++++++++++ docs/docs/transforms/from.md | 16 ++++----- docs/docs/transforms/uuid.md | 20 +++++++++++ extractors/glob_load.go | 8 ++--- 6 files changed, 124 insertions(+), 30 deletions(-) create mode 100644 docs/docs/developers/source_mapping.md diff --git a/docs/docs/developers/source_mapping.md b/docs/docs/developers/source_mapping.md new file mode 100644 index 0000000..335e52f --- /dev/null +++ b/docs/docs/developers/source_mapping.md @@ -0,0 +1,48 @@ +# SIFTER Project Documentation to Source Code Mapping + +## Inputs + +| Documentation File | Source Code File | +|-------------------|------------------| +| docs/docs/inputs/avro.md | extractors/avro_load.go | +| docs/docs/inputs/embedded.md | extractors/embedded.go | +| docs/docs/inputs/glob.md | extractors/glob_load.go | +| docs/docs/inputs/json.md | extractors/json_load.go | +| docs/docs/inputs/plugin.md | extractors/plugin_load.go | +| docs/docs/inputs/sqldump.md | extractors/sqldump_step.go | +| docs/docs/inputs/sqlite.md | extractors/sqlite_load.go | +| docs/docs/inputs/table.md | extractors/tabular_load.go | +| docs/docs/inputs/xml.md | extractors/xml_step.go | + +## Transforms + +| Documentation File | Source Code File | +|-------------------|------------------| +| docs/docs/transforms/accumulate.md | transform/accumulate.go | +| docs/docs/transforms/clean.md | transform/clean.go | +| docs/docs/transforms/debug.md | transform/debug.go | +| docs/docs/transforms/distinct.md | transform/distinct.go | +| docs/docs/transforms/fieldParse.md | transform/field_parse.go | +| docs/docs/transforms/fieldProcess.md | transform/field_process.go | +| docs/docs/transforms/fieldType.md | transform/field_type.go | +| docs/docs/transforms/filter.md | transform/filter.go | +| docs/docs/transforms/flatmap.md | transform/flat_map.go | +| docs/docs/transforms/from.md | transform/from.go | +| docs/docs/transforms/hash.md | transform/hash.go | +| docs/docs/transforms/lookup.md | transform/lookup.go | +| docs/docs/transforms/map.md | transform/mapping.go | +| docs/docs/transforms/objectValidate.md | transform/object_validate.go | +| docs/docs/transforms/plugin.md | transform/plugin.go | +| docs/docs/transforms/project.md | transform/project.go | +| docs/docs/transforms/reduce.md | transform/reduce.go | +| docs/docs/transforms/regexReplace.md | transform/regex.go | +| docs/docs/transforms/split.md | transform/split.go | +| docs/docs/transforms/uuid.md | transform/uuid.go | + +## Outputs + +| Documentation File | Source Code File | +|-------------------|------------------| +| docs/docs/outputs/graphBuild.md | playbook/output_graph.go | +| docs/docs/outputs/json.md | playbook/output_json.go | +| docs/docs/outputs/tableWrite.md | playbook/output_table.go | \ No newline at end of file diff --git a/docs/docs/schema.md b/docs/docs/schema.md index dbe1ddd..abf127f 100644 --- a/docs/docs/schema.md +++ b/docs/docs/schema.md @@ -57,33 +57,32 @@ Extractors produce a stream of messages from various sources. ### `tableLoad` Loads data from a delimited file (TSV/CSV). -- `input`: Path to the file. -- `sep`: Separator (default `\t`). +- `path`: Path to the file. - `rowSkip`: Number of header rows to skip. - `columns`: Optional list of column names. - `extraColumns`: Field name to store any columns beyond the declared ones. -- `comment`: Comment character (default `#`). -- `lazyQuotes`: Allow lazy quoting in CSV. +- `sep`: Separator (default `\t` for TSVs, `,` for CSVs). -### `jsonLoad` +### `json` Loads data from a JSON file (standard or line-delimited). -- `input`: Path to the file. +- `path`: Path to the file. +- `multiline`: Load file as a single multiline JSON object. -### `avroLoad` +### `avro` Loads data from an Avro object file. -- `input`: Path to the file. +- `path`: Path to the file. -### `xmlLoad` +### `xml` Loads and parses XML data. -- `input`: Path to the file. +- `path`: Path to the file. - `level`: Depth level to start breaking XML into discrete messages. -### `sqliteLoad` +### `sqlite` Loads data from a SQLite database. -- `input`: Path to the database file. +- `path`: Path to the database file. - `query`: SQL SELECT statement. -### `transposeLoad` +### `transpose` Loads a TSV and transposes it (making rows from columns). - `input`: Path to the file. - `rowSkip`: Rows to skip. @@ -94,6 +93,19 @@ Loads a TSV and transposes it (making rows from columns). Runs an external command that produces JSON messages to stdout. - `commandLine`: The command to execute. +### `embedded` (Extractor) +Load data from embedded structure. +- No parameters required. + +### `glob` (Extractor) +Scan files using `*` based glob statement and open all files as input. +- `path`: Path of avro object file to transform. +- `storeFilename`: Store value of filename in parameter each row. +- `xml`: xmlLoad configuration. +- `table`: Run transform pipeline on a TSV or CSV. +- `json`: Run a transform pipeline on a multi line json file. +- `avro`: Load data from avro file. + --- ## Transformation Steps @@ -104,8 +116,8 @@ Transformation pipelines are arrays of steps. Each step can be one of the follow - `from`: Start a pipeline from a named input or another pipeline. - `emit`: Write messages to a JSON file. Fields: `name`, `useName` (bool). - `objectValidate`: Validate messages against a JSON schema. Fields: `title`, `schema` (directory), `uri`. -- `debug`: Print message contents to stdout. -- `plugin` (Transform): Pipe messages through an external script via stdin/stdout. +- `debug`: Print message contents to stdout. Fields: `label`, `format`. +- `plugin` (Transform): Pipe messages through an external script via stdin/stdout. Fields: `commandLine`. ### Mapping and Projection - `project`: Map templates into new fields. Fields: `mapping` (key-template pairs), `rename` (simple rename). @@ -129,6 +141,6 @@ Transformation pipelines are arrays of steps. Each step can be one of the follow ### Specialized - `hash`: Generate a hash of a field. Fields: `field` (dest), `value` (template), `method` (`md5`, `sha1`, `sha256`). - `uuid`: Generate a UUID. Fields: `field`, `value` (seed), `namespace`. -- `graphBuild`: Convert messages into graph vertices and edges using schema definitions. +- `graphBuild`: Convert messages into graph vertices and edges using schema definitions. Fields: `schema`, `title`. - `tableWrite`: Write specific fields to a delimited output file. Fields: `output`, `columns`, `sep`, `header`, `skipColumnHeader`. - `split`: Split a single message into multiple based on a list field. diff --git a/docs/docs/transforms/fieldParse.md b/docs/docs/transforms/fieldParse.md index 476e44c..4be41ff 100644 --- a/docs/docs/transforms/fieldParse.md +++ b/docs/docs/transforms/fieldParse.md @@ -6,3 +6,21 @@ menu: weight: 100 --- +# fieldParse + +Parse a string field (e.g. `key1=val1;key2=val2`) into individual keys. + +## Parameters + +| Name | Type | Description | +| --- | --- | --- | +| field | string | The field containing the string to be parsed | +| sep | string | Separator character used to split the string | + +## Example + +```yaml + - fieldParse: + field: attributes + sep: ";" +``` diff --git a/docs/docs/transforms/from.md b/docs/docs/transforms/from.md index c6f5a80..da38940 100644 --- a/docs/docs/transforms/from.md +++ b/docs/docs/transforms/from.md @@ -8,22 +8,18 @@ menu: # from -## Parmeters +Start a pipeline from a named input or another pipeline. -Name of data source +## Parameters + +| Name | Type | Description | +| --- | --- | --- | +| source | string | Name of the input or pipeline to start from | ## Example ```yaml - - -inputs: - profileReader: - tableLoad: - input: "{{config.profiles}}" - pipelines: profileProcess: - from: profileReader - ``` \ No newline at end of file diff --git a/docs/docs/transforms/uuid.md b/docs/docs/transforms/uuid.md index 0127b15..d47dee1 100644 --- a/docs/docs/transforms/uuid.md +++ b/docs/docs/transforms/uuid.md @@ -5,3 +5,23 @@ menu: parent: transforms weight: 100 --- + +# uuid + +Generate a UUID for a field. + +## Parameters + +| Name | Type | Description | +| --- | --- | --- | +| field | string | Destination field name for the UUID | +| value | string | Seed value used to generate the UUID | +| namespace | string | UUID namespace (optional) | + +## Example + +```yaml + - uuid: + field: id + value: "{{row.name}}" +``` \ No newline at end of file diff --git a/extractors/glob_load.go b/extractors/glob_load.go index fc752ea..2f5d2eb 100644 --- a/extractors/glob_load.go +++ b/extractors/glob_load.go @@ -16,10 +16,10 @@ type GlobLoadStep struct { StoreFilepath string `json:"storeFilepath"` Path string `json:"path" jsonschema_description:"Path of avro object file to transform"` Parallelize bool `json:"parallelize"` - XMLLoad *XMLLoadStep `json:"xmlLoad"` - TableLoad *TableLoadStep `json:"tableLoad" jsonschema_description:"Run transform pipeline on a TSV or CSV"` - JSONLoad *JSONLoadStep `json:"jsonLoad" jsonschema_description:"Run a transform pipeline on a multi line json file"` - AvroLoad *AvroLoadStep `json:"avroLoad" jsonschema_description:"Load data from avro file"` + XMLLoad *XMLLoadStep `json:"xml"` + TableLoad *TableLoadStep `json:"table" jsonschema_description:"Run transform pipeline on a TSV or CSV"` + JSONLoad *JSONLoadStep `json:"json" jsonschema_description:"Run a transform pipeline on a multi line json file"` + AvroLoad *AvroLoadStep `json:"avro" jsonschema_description:"Load data from avro file"` } type fileSource struct { From 812e26293943da8872576e08e8ff051ed9015b1f Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Fri, 23 Jan 2026 08:33:32 -0800 Subject: [PATCH 11/19] Working on the docs --- docs/docs/.nav.yml | 3 +- docs/docs/config.md | 16 +- docs/docs/example.md | 61 +- docs/docs/index.md | 61 +- docs/docs/inputs/glob.md | 12 +- docs/docs/inputs/index.md | 49 +- docs/docs/inputs/json.md | 2 +- docs/docs/inputs/plugin.md | 2 +- docs/docs/inputs/sqldump.md | 2 +- docs/docs/inputs/sqlite.md | 2 +- docs/docs/inputs/table.md | 8 +- docs/docs/inputs/xml.md | 2 +- docs/docs/outputs/graphBuild.md | 2 +- docs/docs/playbook.md | 1216 ------------------------ docs/docs/schema.md | 25 +- docs/docs/transforms/lookup.md | 4 +- docs/docs/transforms/objectValidate.md | 2 +- 17 files changed, 163 insertions(+), 1306 deletions(-) delete mode 100644 docs/docs/playbook.md diff --git a/docs/docs/.nav.yml b/docs/docs/.nav.yml index f6aa456..1d7fa65 100644 --- a/docs/docs/.nav.yml +++ b/docs/docs/.nav.yml @@ -1,8 +1,9 @@ +title: Sifter Documentation + nav: - index.md - example.md - - playbook.md - schema.md - config.md - inputs diff --git a/docs/docs/config.md b/docs/docs/config.md index 7b79b91..38ab63d 100644 --- a/docs/docs/config.md +++ b/docs/docs/config.md @@ -1,16 +1,16 @@ --- -title: Configuration Reference +title: Paramaters --- -## Configuration Variables +## Paramaters Variables -Configuration variables allow playbooks to be parameterized. They are defined in the `config` section of the playbook YAML file. +Playbooks can be parameterized. They are defined in the `params` section of the playbook YAML file. ### Configuration Syntax ```yaml -config: +params: variableName: - type: File # or Dir + type: File # one of: File, Path, String, Number default: "path/to/default" ``` @@ -20,7 +20,7 @@ config: ### Example Configuration ```yaml -config: +params: inputDir: type: Dir default: "/data/input" @@ -32,7 +32,3 @@ config: default: "/config/schema.json" ``` -### Best Practices -1. Use descriptive names for configuration variables -2. Provide reasonable default values -3. Document all configuration variables in your playbook's documentation section \ No newline at end of file diff --git a/docs/docs/example.md b/docs/docs/example.md index 3ebeb56..d506f6b 100644 --- a/docs/docs/example.md +++ b/docs/docs/example.md @@ -5,7 +5,7 @@ entries. The input file looks like: -``` +```csv ZIP,COUNTYNAME,STATE,STCOUNTYFP,CLASSFP 36003,Autauga County,AL,01001,H1 36006,Autauga County,AL,01001,H1 @@ -25,38 +25,44 @@ outdir: ./ docs: Converts zipcode TSV into graph elements ``` -Next the configuration is declared. In this case the only input is the zipcode TSV. -There is a default value, so the pipeline can be invoked without passing in +Next the parameters are declared. In this case the only parameter is the path to the +zipcode TSV. There is a default value, so the pipeline can be invoked without passing in any parameters. However, to apply this pipeline to a new input file, the -input parameter `zipcode` could be used to define the source file. +input parameter `zipcode` could be used to define the source file. +Path and File Parameters can be relative to the directory that the playbook file is in. ```yaml -config: - schema: ../covid19_datadictionary/gdcdictionary/schemas/ - zipcode: ../data/ZIP-COUNTY-FIPS_2017-06.csv +params: + schema: + type: path + default: ../covid19_datadictionary/gdcdictionary/schemas/ + zipcode: + type: path + default: ../data/ZIP-COUNTY-FIPS_2017-06.csv ``` The `inputs` section declares data input sources. In this pipeline, there is only one input, which is to run the table loader. -``` +```yaml inputs: - tableLoad: - path: "{{config.zipcode}}" - sep: "," + zipcode: + table: + path: "{{params.zipcode}}" + sep: "," ``` Tableload operaters of the input file that was originally passed in using the `inputs` stanza. SIFTER string parsing is based on mustache template system. -To access the string passed in the template is `{{config.zipcode}}`. +To access the string passed in the template is `{{params.zipcode}}`. The seperator in the file input file is a `,` so that is also passed in as a parameter to the extractor. -The `tableLoad` extractor opens up the TSV and generates a one message for +The `table` extractor opens up the TSV and generates a one message for every row in the file. It uses the header of the file to map the column values into a dictionary. The first row would produce the message: -``` +```json { "ZIP" : "36003", "COUNTYNAME" : "Autauga County", @@ -77,14 +83,14 @@ and produces a single output message. The two messages: -``` +```json { "ZIP" : "36003", "COUNTYNAME" : "Autauga County", "STATE" : "AL", "STCOUNTYFP" : "01001", "CLASSFP" : "H1"} { "ZIP" : "36006", "COUNTYNAME" : "Autauga County", "STATE" : "AL", "STCOUNTYFP" : "01001", "CLASSFP" : "H1"} ``` Would be merged into the message: -``` +```json { "ZIP" : ["36003", "36006"], "COUNTYNAME" : "Autauga County", "STATE" : "AL", "STCOUNTYFP" : "01001", "CLASSFP" : "H1"} ``` @@ -92,7 +98,7 @@ The `reduce` transform step uses a block of python code to describe the function The `method` field names the function, in this case `merge` that will be used as the reduce function. -``` +```yaml zipReduce: - from: zipcode - reduce: @@ -114,7 +120,7 @@ to project data into new files in the message. The template engine has the curre message data in the value `row`. So the value `FIPS:{{row.STCOUNTYFP}}` is mapped into the field `id`. -``` +```yaml - project: mapping: id: "FIPS:{{row.STCOUNTYFP}}" @@ -128,7 +134,7 @@ message data in the value `row`. So the value Using this projection, the message: -``` +```json { "ZIP" : ["36003", "36006"], "COUNTYNAME" : "Autauga County", @@ -140,7 +146,7 @@ Using this projection, the message: would become -``` +```json { "id" : "FIPS:01001", "province_state" : "AL", @@ -157,13 +163,14 @@ would become } ``` -Now that the data has been remapped, we pass the data into the 'objectCreate' -transformation, which will read in the schema for `summary_location`, check the +Now that the data has been remapped, we pass the data into the 'objectValidate' +step, which will open the schema directory and find the class titled `summary_location`, check the message to make sure it matches and then output it. -``` - - objectCreate: - class: summary_location +```yaml + - objectValidate: + title: summary_location + schema: {{params.schema}} ``` @@ -174,11 +181,11 @@ To create an output table, with two columns connecting code, used by the census office. A single FIPS code my contain many ZIP codes, and we can use this table later for mapping ids when loading the data into a database. -``` +```yaml outputs: zip2fips: tableWrite: - from: + from: zipReduce path: zip2fips.tsv columns: - ZIP diff --git a/docs/docs/index.md b/docs/docs/index.md index 0dcdc28..2601927 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -1,9 +1,13 @@ +--- +title: Sifter +--- -# Sifter pipelines -Sifter pipelines process streams of nested JSON messages. Sifter comes with a number of +# Sifter + +Sifter is a stream based processing engine. It comes with a number of file extractors that operate as inputs to these pipelines. The pipeline engine -connects togeather arrays of transform steps into directed acylic graph that is processed +connects togeather several processing data into directed acylic graph that is processed in parallel. Example Message: @@ -35,7 +39,7 @@ be done in a transform pipeline these include: # Pipeline File An sifter pipeline file is in YAML format and describes an entire processing pipelines. -If is composed of the following sections: `config`, `inputs`, `pipelines`, `outputs`. In addition, +If is composed of the following sections: `params`, `inputs`, `pipelines`, `outputs`. In addition, for tracking, the file will also include `name` and `class` entries. ```yaml @@ -44,9 +48,12 @@ class: sifter name: