{"id":21126986,"url":"https://github.com/pyrustic/backstage","last_synced_at":"2026-04-02T01:17:31.222Z","repository":{"id":57413667,"uuid":"402199899","full_name":"pyrustic/backstage","owner":"pyrustic","description":"Three-speed scripting language and task automation tool ","archived":false,"fork":false,"pushed_at":"2023-06-09T10:10:09.000Z","size":264,"stargazers_count":17,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2026-01-02T19:47:22.789Z","etag":null,"topics":["automation","backstage","build-project","cli","command-line","lightweight","process","productivity","project-manager","pyrustic","python","release-project","script","scripting-language","spawn","task-runner","tasks"],"latest_commit_sha":null,"homepage":"https://pyrustic.github.io","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pyrustic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-09-01T20:52:09.000Z","updated_at":"2025-12-20T03:57:17.000Z","dependencies_parsed_at":"2025-07-08T23:42:58.975Z","dependency_job_id":null,"html_url":"https://github.com/pyrustic/backstage","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/pyrustic/backstage","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyrustic%2Fbackstage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyrustic%2Fbackstage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyrustic%2Fbackstage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyrustic%2Fbackstage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pyrustic","download_url":"https://codeload.github.com/pyrustic/backstage/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyrustic%2Fbackstage/sbom","scorecard":{"id":751913,"data":{"date":"2025-08-11","repo":{"name":"github.com/pyrustic/backstage","commit":"89dae4a1f441e8b2828594cd6fb1a0bb513a4af7"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-22T20:37:19.792Z","repository_id":57413667,"created_at":"2025-08-22T20:37:19.792Z","updated_at":"2025-08-22T20:37:19.792Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31293753,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T01:05:07.454Z","status":"ssl_error","status_checked_at":"2026-04-02T00:56:46.496Z","response_time":53,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automation","backstage","build-project","cli","command-line","lightweight","process","productivity","project-manager","pyrustic","python","release-project","script","scripting-language","spawn","task-runner","tasks"],"created_at":"2024-11-20T04:46:10.873Z","updated_at":"2026-04-02T01:17:31.149Z","avatar_url":"https://github.com/pyrustic.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- Cover --\u003e\n\u003cdiv align=\"center\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/pyrustic/misc/master/assets/backstage/cover.jpg\" alt=\"Demo\" width=\"640\"\u003e\n    \u003cp align=\"center\"\u003e\n    By © Jorge Royan\u0026nbsp;/\u0026nbsp;\u003ca rel=\"nofollow\" class=\"external free\" href=\"http://www.royan.com.ar\"\u003ehttp://www.royan.com.ar\u003c/a\u003e, \u003ca href=\"https://creativecommons.org/licenses/by-sa/3.0\" title=\"Creative Commons Attribution-Share Alike 3.0\"\u003eCC BY-SA 3.0\u003c/a\u003e, \u003ca href=\"https://commons.wikimedia.org/w/index.php?curid=23405928\"\u003eLink\u003c/a\u003e\n    \u003c/p\u003e\n\u003c/div\u003e\n\n\n\n\u003c!-- Intro Text --\u003e\n# Pyrustic Backstage\n\u003cb\u003e Three-speed scripting language and task automation tool \u003c/b\u003e\n    \nThis project is part of the [Pyrustic Open Ecosystem](https://pyrustic.github.io).\n\u003e [Installation](#installation) \u0026nbsp; \u0026nbsp; [Demo](#demo) \u0026nbsp; \u0026nbsp; [Latest](https://github.com/pyrustic/backstage/tags) \u0026nbsp; \u0026nbsp; [Modules](https://github.com/pyrustic/backstage/tree/master/docs/modules#readme)\n\n\n## Table of contents\n- [Overview](#overview)\n- [Structure of the script file](#structure-of-the-script-file)\n- [Spawning processes and branching subtasks](#spawning-processes-and-branching-subtasks)\n- [Data types and control flow](#data-types-and-control-flow)\n- [Namespaces and persistence](#namespaces-and-persistence)\n- [Variable interpolation and escaping](#variable-interpolation-and-escaping)\n- [Environment variables and language syntax](#environment-variables-and-language-syntax)\n- [File and directory manipulation](#file-and-directory-manipulation)\n- [Exception handling and tests](#exception-handling-and-tests)\n- [Interfacing with Python](#interfacing-with-python)\n- [Exception handling and tests](#exception-handling-and-tests)\n- [Command line interface and developer experience](#command-line-interface-and-developer-experience)\n- [Miscellaneous](#miscellaneous)\n- [Demo](#demo)\n- [Installation](#installation)\n\n# Overview\n**Backstage** is a cross-platform **automation tool** that looks for a `backstage.tasks` file in the current working directory to run a specific task defined in that file on demand. A task can be a sequence or pipeline of processes to be spawned, instructions for performing file and directory manipulation, or something more sophisticated.\n\nThe `backstage.tasks` file uses the [Jesth](https://github.com/pyrustic/jesth) (**J**ust **E**xtract **S**ections **T**hen **H**ack) file format that acts like a broken [INI file](https://en.wikipedia.org/wiki/INI_file) parser that only extract sections each made of a header and a body which is just a list of lines.\n\nUsing an eponymous **three-speed scripting language** designed for the automation tool, the programmer can, inside the `backstage.tasks` file, define, coordinate and use the various resources at his disposal to automate things.\n\nThe three-speed scripting language concept is inspired from the three forward gear ratios of early automobiles [transmission system](https://en.wikipedia.org/wiki/Manual_transmission). In the following subsections, we will explore each metaphorical gear of the **Backstage** scripting language, then we will briefly expose the automation tool itself.\n\n## First gear\nIn first gear, a `backstage.tasks` file is intended to store a list of tasks related to a specific project, each task exposing a list of commands or subtasks to be executed. Here, a command represents a process or pipeline of processes to be spawned. [Environment variables](#environment-variables-and-language-syntax) can be used in commands via [variable interpolation](https://en.wikipedia.org/wiki/String_interpolation). No other logic is involved.\n\n### Example\n```\n[task1]\n# three commands to run sequentially\n$ git commit -m 'Update'\n$ python -m my.package.module\n$ program1 arg1 | program2 {HOME}\n---\n# run the subtask 'task2'\n\u0026 task2\n---\n# run the subtask 'task3' in a new thread\n~ task3\n\n[task2]\n$ program val1 {CWD}\n$ git push origin master\n\n[task3]\n# some heavy computation\n$ engine -x 5000\n$ engine --cleanup\n\n[_task4]\n# This is a private task (with an underscore as prefix)\n$ clean dir\n```\n\n## Second gear\nIn second gear, the `backstage.tasks` file not only stores the tasks like in first gear, but here logic intervenes, variables are defined, control flow is used, built-in commands are called, et cetera. Basically, in second gear, **Backstage** unleashes its power and allows the programmer to anticipate problems, make sophisticated combinations of subtasks, in short to write a real **script to automate things**.\n\n### Example\n```\n[task1]\n# commit changes\n$ git commit -m 'Update'\n---\n# tell user if 'Commit' has been success\nif R == 0\n    # print 'Success !'\n    : Success !\nelse\n    : Failed to commit changes\n---\n# say hello ten times\nset age = 42\nset name = `John Doe`\nfrom 1 to 10\n    $ python -m say.hello {name} {age}\n---\n# create a file in user home\nset pathname = {HOME}/iliad.txt\ncreate file pathname\n---\n# append some data to a file\nset data = `\\nHello World !`\nappend data to pathname\n---\n# browse current working directory\nbrowse files and dirs in CWD\n    : Directory -\u003e {R}\n    : Files -\u003e\n    for item in files\n        : - {item}\n    : Dirs -\u003e\n    for item in dirs\n        : - {item}\n```\n\n## Third gear\nIn third gear, in addition with whatever can be done with previous gears, the programmer can directly from the `backstage.tasks` file, call [Python](https://www.python.org/about/) functions with arguments and get the return ! Thanks to this third gear, any too complex or overly verbose calculation can be written in **Python** and called from **Backstage**. This [functionality](#interfacing-with-python) alone proves that **Backstage** is all about making the programmer's life better, not pretending to replace existing mature solutions that actually work.\n\n### Example\n```\n[task1]\ninterface with package.coffeemaker alias coffeebro\n\nset sugar_cubes (int) = 1 + 1 + 1\nset extra = `milk`\n\ncall coffeebro.make(sugar_cubes, extra, 42)\n\nif R == 1\n    : Coffee successfully made !\nelse\n    : Oops, failed to make coffee...\n    : Exception -\u003e {EXCEPTION}\n    : Traceback -\u003e {TRACEBACK}\n```\n\n## Automation tool\nThe scripting language help to define tasks in the `backstage.tasks` file that is intended to be consumed by the automation tool. As an automation tool, **Backstage** exposes a [command line interface](#command-line-interface-and-developer-experience) that allows the user to **discover** available tasks, **run** a task with arguments, read a task [documentation](#embedded-documentation-and-tests), use a [glob](https://en.wikipedia.org/wiki/Glob_(programming))-like syntax to **search** for a task by its name or by a keyword (case-insensitive mode) that is part of the documentation of the task, et cetera.\n\n### Example 1\nLet's assume that there is a `backstage.tasks` file in the directory `/home/alex/project`. This `backstage.tasks` file contains three public tasks and one private tasks (prefixed with an underscore).\n\nThis example shows how one could use the automation tool to run a task defined in the `backstage.tasks` file:\n\n```bash\n$ cd /home/alex/project\n\n$ backstage -c\nAvailable tasks (3):\n    make_coffee  task1  task2\n\n$ backstage make_coffee\nMaking coffee...\n\n$ backstage mak*\nMaking coffee...\n\n$ backstage make_coffee sugar=3\nMaking coffee with 3 sugar cubes...\n```\n\n### Example 2\nThis is the contents of a `backstage.tasks` file located at `/home/alex/project`:\n\n```\n[task]\n# define 'x' as a variable with an 'int' assignment tag\nset x (int) = 1 + 1 + 1\n\n# default name (by default, the assignment tag is 'str')\nset default_name = `John Doe`\n\n# print some text\n: Hi and Welcome !\n: I can print your name {x} times in a row !\n\n# take user input (always a 'str')\n\u003e name : `What is your name ? `\n\n# control flow\nif name == EMPTY\n    set name = {default_name}\n\n# iteration\nfrom 1 to x\n    : {R} - Ave {name} !\n\n# branch subtask '_task2' and pass it an argument\n\u0026 _task2 {name}\n\n\n[_task2]\n# define the variable 'name' (first argument passed to this task)\nset name = {ARGS[0]}\n\n# Just say Goodbye !\n: Goodbye {name} !\n\n```\n\nLet's run the task named `task` from the command line:\n\n```bash\n$ cd /home/alex/project\n\n$ backstage task\nHi and Welcome !\nI can print your name 3 times in a row !\nWhat is your name ? Alex\n1 - Ave Alex !\n2 - Ave Alex !\n3 - Ave Alex !\nGoodbye Alex !\n```\n\n\u003e **Note:** You can reproduce this example as it. Just [install](#installation) **Backstage**, copy-paste the script in a `backstage.tasks` file, then run `backstage task` in the command line.\n\n## And more\nThere is more to talk about **Backstage**, like the ability to embed documentation and tests in the `backstage.tasks` file and access them from the command line. Backstage is enough versatile to do the job of a trivial task runner or to automate things with its scripting language that has a built-in bridge to the powerful Python ecosystem.\n\nIn the following sections, we will explore this project in depth. You can also jump to the [demo](#demo) to start playing with **Backstage** !\n\n# Structure of the script file\nAs stated in the [Overview](#overview) section, a `backstage.tasks` file is basically a [JesthFile](https://github.com/pyrustic/jesth).\n\nIn a `backstage.tasks` file, a section represents a task. The section title is the name of the task and the section body is made of commands to run and the constructs of the **Backstage** scripting language. A valid task name is an alphanumeric string that can contains an underscore.\n\n```\nYou can write here at the top of the script,\na description of the script,\nthe date of its creation,\nor any useful information.\n\n[task1]\n# body of task1\n...\n\n[task2]\n# body of task2\n...\n\n[_private]\n# prefix a task name with an underscore\n# to turn it into a private task that\n# won't appear in the list of available tasks\n# when you will type 'backstage --check' in the command line\n```\n\nAs you can guess, a line starting with `#` is a comment. But this is only true inside a task body, because in fact not all sections are tasks as described before: a section can also be an embedded test or documentation.\n\n\n## Embedded documentation\nYou can embed documentation inside the `backstage.tasks` file. To create a documentation for a task, create a section which name is postfixed with `.help`:\n\n```\n[task1]\npass\n\n\n[task1.help]\nThis is the description line.\n\nUsage:\n    backstage task \u003coption\u003e \u003cpath\u003e\n\nOptions:\n    -m, --msg       Show message\n    -x, --exit      Exit blah blah\n\n```\n\nFrom the **command line**, you can read the documentation of an arbitrary task:\n\n```bash\n$ backstage -h task1\nThis is the description line.\n\nUsage:\n    backstage task \u003coption\u003e \u003cpath\u003e\n\nOptions:\n    -m, --msg       Show message\n    -x, --exit      Exit blah blah\n```\n\n## Embedded tests\n\nYou can embed tests inside the `backstage.tasks` file. To create a test for a task, create a section which name is postfixed with `.test`:\n\n```\n[task1.test]\n# perform some test here\n\n# ...\n\nassert some_var == some_var\n```\n\nFrom the **command line**, you can run the test of an arbitrary task:\n\n```bash\n$ backstage --test task1\n```\n\nor the tests of a bunch of tasks:\n\n```bash\n$ backstage --test task1 task2 task3\n```\n\nor run all tests defined in the `backstage.tasks` file:\n\n```bash\n$ backstage --test\n```\n\n\n# Spawning processes and branching subtasks\nYou can write commands to spawn a process or a pipeline of processes:\n\n```\n[task]\n# spawn Git to perform a 'commit'\n$ git commit -m \"Update\"\n\n# spawn a pipeline of three processes\n$ program1 arg1 | program2 arg2 | program3\n```\n\nCommands to spawn processes support variable interpolation:\n```\n[task]\n# use HOME environment variable\n$ ls {HOME}\n\n# access the first index of the ARGS list\n$ program {ARGS[0]}\n```\n\nYou can **push** data to the **input** of a process:\n\n```\n[task]\n# define variables name and age\nset name = `John Doe`\nset age (int) = 40 + 2\n\n# name and age will be pushed to the input of the next spawned process\npush name age\n$ program1 {HOME}\n\n# from now you can access via the environment variable R,\n# the return code (exit status code)\n: The return code is {R}\n```\n\n\nYou can **capture** a process:\n\n```\n[task]\n# you can capture a process,\n# so you will get a direct access to the output and error\n($) program2\n\n# print the content of OUTPUT and ERROR\n: Output -\u003e {OUTPUT}\n: Error -\u003e {ERROR}\n```\n\n\nYou can redirect **STDOUT** and **STDERR**:\n\n```\n[task]\n\n# redirect STDOUT and STDERR\nset STDOUT = `/path/to/file_out`\nset STDERR = `/path/to/file_err`\n\n$ program1\n\n---\n\n# cross platform DEVNULL:\nset STDOUT = /dev/null\n\n$ program2\n```\n\n## Branching subtasks\nYou can branch a subtask and pass arguments to it:\n\n```\n[task1]\n# branch 'task2'\n\u0026 task2 {HOME}\n\n[task2]\n# from task2, we can access arguments passed to it.\n# ARGS (environment variable) is a list of arguments.\n: Arguments -\u003e {ARGS}\n```\n\nWhenever you branch a subtask, a new instance of this subtask is created, with its own variables. You can share data with a subtask with one of these three ways:\n- pass arguments to the subtask while branching it;\n- use the global [namespace](#namespaces-and-persistence);\n- use the database [namespace](#namespaces-and-persistence).\n\nA subtask can also return data that is cached in the `R` environment variable:\n\n```\n[task1]\n\u0026 task2 \"John Doe\"\n# from now, R contains 'Hello John Doe !'\n\n[task2]\nset result = `Hello {ARGS} !`\nreturn result\n```\n\n### Multithreading\nBranching a subtask is done in the main thread. But one can create a new thread for a subtask:\n\n```\n[task1]\n# run an instance of task2 in a new thread\n~ task2\n\n# the next command won't wait 'task2' to complete\n$ git commit -m \"Update\"\n\n\n[task2]\n# this task sleeps for 5 seconds\nsleep 5\n```\n\n\n# Data types and control flow\nIn the next subsections we will talk about data types then control flow.\n\n## Data types\n**Backstage** supports [variables](https://en.wikipedia.org/wiki/Variable_(computer_science)) and let the programmer set, use, clear, and drop variables.\n\nUnder the hood, **Backstage** works with five **Python** data types: `str`, `list` (one-dimensional), `dict` (one-dimensional), `int`, and `float`. But these data types aren't intended to be directly used by the programmer. Instead, `assignment tags` are used to tell the interpreter how to treat a variable.\n\nThese `assignment tags` are: `raw`, `str`, `list`, `dict`, `int`, `float`, `date`, `time`, `dtime`, and `tstamp`.\n\n```\n[task]\n# this is a string\nset var = 42\n\n# this is another string\nset var (str) = `Hello World`\n\n# this is a list\nset var (list) = `reg green blue`\n\n# this is the same list but edited\nset var[0] = `yellow`\n# var -\u003e yellow green blue\n\n# this is a dict\nset var (dict) = `user=\"John Doe\" age=42 location=Kernel`\n\n# this is the same dict but edited\nset var.user = `Jane Doe`\n# var -\u003e user='Jane Doe' age=42 location=Kernel\n\n# this is an integer\nset var (int) = 40 + 2\n# var -\u003e 42\n\n# this is a raw string\nset regex (raw) = `[\\S\\s]*?`\n\n# get the current timestamp\nset now (int) = {NOW}\n# now -\u003e 1662569326\n\n# convert it into datetime\nset var (dtime) = {now}\n# var -\u003e 2022-09-07 17:48:46\n\n# go from a datetime to timestamp\nset var (tstamp) = `2022-09-07 17:48:46`\n# var -\u003e 1662569326\n\n# extract the time part of a timestamp\nset var (time) = 1662569326\n# var -\u003e 17:48:46\n```\n\n\u003e Note that all variables have a **string** representation and [backticks](https://en.wikipedia.org/wiki/Backtick) are used optionally as delimiters that will be ignored by the interpreter. So you can put backticks around an integer, and you can insert a list in a string.\n\n### Here document\n**Backstage** supports [here document](https://en.wikipedia.org/wiki/Here_document) for strings inside the `backstage.tasks` file:\n```\n[task]\n# this is a text with two lines\nset text (str) = `First line\\nSecond line`\n\n# this is another text with three lines\nset text = `January\\nFebruary\\nMarch`\n\n# this is not a text with two lines\nset var (raw) = `Hello\\nWorld`\n\n# this is not a text with two lines\nset var (str) = `Hello\\\\nWorld`\n```\n\n### Variable interpolation\n[Variable interpolation](https://en.wikipedia.org/wiki/String_interpolation) is supported with the ability to access from a list or a string, the value at an arbitrary index, or from a dictionary, the value of a key.\n\n```\n[task]\n# let's play with 'str' variables\nset x = `red`\nset var = `{x} green blue`\n# var -\u003e red green blue\n\n# get the value of element at index 0\nset value = {var[0]}\n# value -\u003e r\n\n---\n\n# let's play with a list\nset x = `red`\nset var (list) = `{x} green blue`\n\n# get the value of element at index 0\nset value = {var[0]}\n# value -\u003e red\n\n# get the value of elements from index 1 to the end\nset value = {var[1:]}\n# value -\u003e green blue\n\n---\n\n# let's play with a dict\nset x (int) = 40 + 2\nset var (dict) = `name=\"John Doe\" age={x}`\n\n# get the value of key 'name'\nset value = {var.name}\n# value -\u003e John Doe\n\n# get the value of key 'age'\nset value = {var.age}\n# value -\u003e 42\n\n---\n\n# cancel the variable interpolation\nset var1 = `Hello` \nset var2 = `{{var1}} World`\n# var2 -\u003e {var1} World\n```\n\n## Control flow\n**Backstage** implements conditionals and loops. A wide range of operators are available to compare values.\n### Conditionals\n```\n[task]\nset var1 = 1\nset var2 = 1\nset x = 2\nset regex (raw) = `[\\S\\s]*?`\nset text = `Hello world`\nset y = `Hello`\n\n# conditionals support the classic\n# operators: == != \u003e \u003c \u003e= \u003c= \nif var1 == 1\n    $ program1\nelif var2 == x\n    $ program2\nelse\n    $ program3\n   \n# Backstage supports logical and/or \nif var1 == var2 and var1 \u003e= 3\n    $ program1\n\n# use the 'matches' operator\n# or the negated one: !matches\nif regex matches text\n    : Matched !\nelif regex !matches text\n    : Mismatched !\n    \n# you can use the in operator\n# and also the negated one: !in\nif y in text\n    pass\nelif y !in text\n    pass\n\n```\n\n\n### Loops\n```\n[task]\n\n# From To loop\nfrom 10 to 0\n    : {R}\n    \n# For loop - iterate over a string\nset text = `Hello World`\nfor char in text\n    # N is an environment variable that serves as counter\n    # for all loops\n    : {N}- {char}\n\n# For loop - iterate over a list\nset data (list) = `red green blue`\nfor item in data\n    : {item}\n    \n# For loop - iterate over a dict\nset data (dict) = `name=\"John Doe\" age=42`\nfor item in data\n    : key -\u003e {item[0]}  value -\u003e {item[1]}\n    \n# For loop - iterate over a file\nset path = `/home/alex/iliad.txt`\nfor line in path (file)\n    : Line {N}\n    : {line}\n    :\n    \n# while loop\nset var = 1\nwhile var == 1\n    : One Time Hello\n    break\n    \n# browse loop\nset path = `/home/alex`\nbrowse files and dirs in path\n    : Directory -\u003e {R}\n    for item in files\n        : {item}\n    for item in dirs\n        : {item} \n```\n\n\n\n# Namespaces and persistence\n[Namespaces](https://en.wikipedia.org/wiki/Namespace) are implemented in **Backstage** to provide convenient management of variables by defining three [scopes](https://en.wikipedia.org/wiki/Scope_(computer_science)): \n- `L` for **Local** scope;\n- `G` for **Global** scope;\n- `D` for **Database** scope.\n\nBy default, variables exist in the **Local** namespace and are only accessible to the running task.\n\nTo share data with a subtask, one can expose arbitrary variables that will be copied into the **Global** namespace which is readable and writable (thread-safe) by all subtasks.\n\n```\n[task]\n# by default, variables are defined in Local,\n# i.e. they are only visible in the scope of the current task\nset var = 42\n: Var contains {var}\n: Var still contains {L:var}\n\n# you can make local variables public\nexpose var\n# from now, you can get a thread-safe access to var \n# from any running task:\n: Global var contains {G:var}\n\n# branch _task2\n\u0026 _task2\n\n[_task2]\n: I got {G:var} !\n\n```\n\nData can also be **persisted**:\n\n```\n[task]\nset var = 42\nstore var\n\n# from now, 'var' can be accessed by all tasks\n# in this runtime but also in future runtimes\n: Var contains {D:var}\n\n# if you aren't sure about the existence\n# of a variable, just do this:\ndefault var\n\n# it also works with a bunch of variables:\ndefault var1 L:var2 D:var3 G:var4\n\n# from now, if 'var1' hasn't been manually defined\n# by the programmer, it will be\n# automatically initialized and\n# its value will be an empty string\n```\n\nPersisted variables are stored in `.backstage/database.json`.\n\n\n# Variable interpolation and escaping\nDuring the string interpolation of a command that spawn processes or branch a subtask, variables that are of the `str` type are automatically [shell-escaped](https://en.wikipedia.org/wiki/Escape_character).\n\n```\n[task]\n# This is a 'str' variable (backticks aren't quotes, by the way!)\nset name = `John Doe` \nset colors (list) = `red green blue`\n\n: Welcome {name} !\n# Welcome John Doe\n\n$ program name={name} -c {colors}\n# program name='John Doe' -c red green blue\n\n# Notice the quotes automatically added around the name John Doe\n```\n\n# Environment variables and language syntax\nEnvironment variables are local to each instance of task. They are defined as uppercase strings. One can edit their contents but can't create new environment variables.\n\nThis is the exhaustive list of environment variables:\n\n|Variables|Description|\n|---|---|\n|`ARGS`|List of arguments passed to this task from the command line|\n|`CWD`|Current working directory|\n|`DATE`|The current date in the **YYYY-MM-DD** format|\n|`EMPTY`|Just an empty string|\n|`ERROR`|Error string from a process previously spawned|\n|`EXCEPTION`|Name of the last exception raised|\n|`FALSE`|The integer **0**|\n|`HOME`|The path to `$HOME`. Example: `/home/alex`|\n|`LINE`|The current line (1-based numbering) of execution in the task body|\n|`N`|Counter for `while`, `for`, `from`, and `browse` loops|\n|`NOW`|Current timestamp in seconds|\n|`ONE`|The integer **1**|\n|`OS`|The running operating system: `aix`, `linux`, `win32`, `cygwin`, `darwin`|\n|`OUTPUT`|Output string from a process previously spawned|\n|`R`|The **return** of Python functions, built-in commands, statements, constructs, or process exit status codes|\n|`RANDOM`|Random integer between **0** and **255** (closed interval)|\n|`SPACE`|One space ` ` character|\n|`STDERR`|Use this variable to perform **STDERR** redirection|\n|`STDIN`|Use this variable to perform **STDIN** redirection|\n|`STDOUT`|Use this variable to perform **STDOUT** redirection|\n|`TASK`|The name of the currently running task|\n|`TIME`|The current time in the **HH:MM:SS** format|\n|`TIMEOUT`|Timeout in seconds for commands that spawn processes. Default value: **30** seconds|\n|`TMP`|Temporary directory. **Attention**, this directory will automatically disappear at the end of the runtime ! So think twice before moving files inside|\n|`TRACEBACK`|[Traceback](https://en.wikipedia.org/wiki/Stack_trace#Python) of the last exception raised|\n|`TRASH`|Path to the trash: `$HOME/PyrusticData/trash`|\n|`TRUE`|The integer **1**|\n|`ZERO`|The integer **0**|\n\n\u003e Note that the `TRACEBACK` and `EXCEPTION` variables are cleared after the next successful command. **Backstage** also generates for convenience, `ARG0`, `ARG1`, `ARGx`, according to the contents of `ARGS`. For example, if `ARGS`contains two arguments, you can expect that `ARG0` and `ARG1` exist.\n\n## Language syntax\nIn this section we will explore the built-in commands, statements, keywords, symbols, and language constructs that make **Backstage**.\n\n\u003e Note that wherever a built-in command or statement expects a **variable** that will be **read**, for convenience you can instead of supplying a variable name, define an **inline** `int` or `float` literal.\n\n\u003e Also, consider that the `R` environment variable is your friend, since it is used to cache the data returned by a statement, a construct, or a command.\n\n### APPEND\nAppend data to a file.\n\n**Usage:** `append \u003cvar\u003e to \u003cfilename_var\u003e`\n\n\n### ASSERT\nTest is a condition is true.\n\n**Usage:** `assert (\u003cvar1\u003e|\u003cregex_var\u003e) (==|!=|\u003c=|\u003e=|\u003c|\u003e|in|!in|rin|!rin|matches|!matches) \u003cvar2\u003e [and|or] ...`\n\n**Example:** `assert regex_var matches text_var and var1 in list`\n\nNote that `!` is used to express negation and `rin` is a Regex-based `in`. A regexly-in operator ;)\n\n\n### BRANCH\nBranch a subtask. The syntax is similar to the one to spawn processes, i.e., a string of words. The syntax supports variable interpolation.\n\n**Usage:** `\u0026 \u003csubtask\u003e [\u003cargument\u003e ...]`\n\n**Example:** `\u0026 subtask1 name=\"John Doe\" age=42 city={city}`\n\n\u003e In this example, the `city` variable will be automatically shell-escaped during its interpolation.\n\n\n### BREAK\nBreak a loop.\n\n**Usage:** `break`\n\n### BROWSE\nLoop construct to browse a directory.\n\n**Usage:** `browse [files] [and] [dirs] in \u003cdirname_var\u003e`\n\n**Example:**\n```\n[task]\nbrowse files and dirs in dirname\n    : Root -\u003e {R}\n    for item in files\n        : {item}\n    for item in dirs\n        : {item}\n        \nbrowse files in dirname\n    pass\n    \nbrowse dirs in dirname\n    pass\n```\n\n### CALL\nCall a **Python** function from **Backstage** with arguments, then get the return !\n\n**Usage:** `call \u003cmodule\u003e.\u003cfunction\u003e[(\u003cargument_var\u003e, ...)]`\n\n**Example:** \n```\n[task]\n# interface with the Python module\ninterface with package.coffee_module alias coffeemaker\n\n# call the 'make' function with arguments then get the return\ncall coffeemaker.make(sugar_cube, extra, 42)\n: Result -\u003e {R}\n```\n\n### CD\nChange directory.\n\n**Usage:** `cd \u003cdirname_var\u003e`\n\n\n### CHECK\nReturn the data type (`str`, `list`, `dict`, `int`, `float`) of a variable if it exists, else return an empty string.\n\n**Usage:** `check \u003cvar\u003e`\n\n**Example:**\n```\n[task]\ncheck myvar\nif R == EMPTY\n    : This variable doesn't exist at all !\nelse\n    : 'myvar' exists, its data type is {R}\n```\n\n### CLEAR\nClear the content of a variable or a list of variables.\n\n**Usage:** `clear \u003cvar\u003e ...`\n\n**Example:** `clear var1 var2 var3`\n\n### COMMENT\nComment.\n\n**Usage:** `# \u003ccomment\u003e`\n\n\n### CONFIG\nRead and write configuration options (`FailFast`, `ReportException`, `ShowTraceback`, `TestMode`, `AutoLineBreak`).\n\n**Usage:** `config \u003coption\u003e ...`\n\n**Example:**\n```\n[task]\nconfig FailFast=1 AutoLineBreak=0\nconfig TestMode\nif R == 1\n    : Test Mode On\nelif R == 0\n    : Test Mode Off\n```\n\n\n### COPY\nCopy a file or a directory tree to a new destination.\n\n**Usage:** `copy \u003csrc_path_var\u003e to \u003cdest_path_var\u003e`\n\n### COUNT\nCount `chars`, `items`, and `lines` in the content of a variable or inside a file (if the `(file)` tag is applied).\n\n**Usage:** `count (chars|items|lines) in (\u003cvar\u003e|\u003cfilename_var\u003e) [(file)]`\n\n**Example:**\n```\n[task]\nset path = /home/alex/iliad.txt\ncount chars in path (file)\nif R == 0\n    : The file is empty !\n```\n\n### CREATE\nCreate a new file or directory.\n\n**Usage:** `create (dir|file) \u003cpath_var\u003e`\n\n### DEFAULT\nDefine an empty variable (or a bunch of variables) if it doesn't exist yet in the namespace.\n\n**Usage:** `default \u003cvar\u003e ...`\n\n**Example:**\n\n```\n[task]\n# default two variables in the Local namespace\ndefault var1 L:var2\n\n# default one variable in the Database namespace\ndefault D:name\n\n# from now, 'D:name' can be safely accessed\n# even though the 'database.json' file supposed\n# to contain the 'name' value was inadvertently deleted.\n```\n\n### DROP\nDestroy a variable (or a bunch of variables). \n\n**Usage:** `drop \u003cvar\u003e ...`\n\n### ELIF\nPart of the `if` conditional construct.\n\n**Usage:** `elif (\u003cvar1\u003e|\u003cregex_var\u003e) (==|!=|\u003c=|\u003e=|\u003c|\u003e|in|!in|rin|!rin|matches|!matches) \u003cvar2\u003e [and|or] ...`\n\n### ELSE\nPart of the `if` conditional construct.\n\n**Usage:** `else`\n\n### ENTER\nInvite user to submit data.\n\n**Usage:** `\u003e [\u003cvar\u003e [: \u003ctext\u003e]]`\n\n**Example:**\n```\n[task]\n\n\u003e name : Please enter your name \n# have you spotted the space at the end the line above ?\n\n# the same line can be rewritten like this:\n\u003e name : `Please enter your name `\n# backticks serve as delimiters that will be ignored\n\n# this one is also possible:\nset msg = `Please enter your name `\n\u003e name : {msg}\n\n# even this:\nset info (dict) = name=\"John Doe\" age=42\n\u003e info.name : `Please enter your name`\n```\n\n### EXIT\nExit.\n\n**Usage:** `exit`\n\n### EXPOSE\nCopy a variable (or a bunch of variables) into the **Global** namespace.\n\n**Usage:** `expose \u003cvar\u003e ...`\n\n### FAIL\nDeliberately fail. It breaks the running task and mark it as a failure.\n\n**Usage:** `fail`\n\n### FIND\nFind files and or directories paths.\n\n**Usage 1:** `find [all] (paths|files|dirs) in \u003cdirname_var\u003e`\n\n**Usage 2:** `find ... matching \u003cregex_var\u003e`\n\n**Usage 3:** `find ... [and] (accessed|modified|created) (at|after|before|between) \u003ctimestamp_var\u003e [and \u003ctimestamp_var\u003e]`\n\n**Example:** `find files in dirname matching regex and accessed between timestamp1 and timestamp2`\n\n### FOR\nA `for` loop to iterate the content of a variable or the content of a file (if you apply the `(file)` tag).\n\n**Usage:** `for (char|item|line) in (\u003cvar\u003e|\u003cfilename_var\u003e) [(file)]`\n\n**Example:**\n```\n[task]\n# this code iterates over each character of the Iliad,\n# and outputs it as it,\n# with one twist: each line starts with its index (0-based)\n\nset path = `/home/alex/iliad.txt`\n\n# the print statement (:) won't anymore\n# automatically add a line break !\nconfig AutoLineBreak=0\n\nfor line in path (file)\n    : `{N} `\n    for char in line\n        : {char}\n    : \\n\n```\n\n\n### FROM\nA loop to go from an integer to another one. If the `start` integer is superior to the `end` integer, the count will decrease.\n\n**Usage:** `from \u003cstart\u003e to \u003cend\u003e`\n\n**Example:**\n```\n[task]\nfrom 10 to 0\n    # here, N will go from 0 to 10\n    # but R will go from 10 to 0\n    # because N is a counter for all loops\n    # while R is a cache for whatever is returned\n    # by a command, a statement, or a construct\n    : {N}\\t{R}\n\n# As you can see, I can add a Tab \\t since\n# Backstage supports natively here document ;)\n\n# To get a simple backslash followed by a 't':  \\\\t\n```\n\n### GET\nGet a `char`, an `item`, or a `line` at index `x` (including negative index) from a target. The target can be the content of a variable or a file (if you apply the `(file)` tag).\n\n**Usage:** `get (char|item|line) \u003cindex\u003e from (\u003cvar\u003e|\u003cfilename_var\u003e) [(file)]`\n\n### IF\nConditional construct.\n\n**Usage:** `if (\u003cvar1\u003e|\u003cregex_var\u003e) (==|!=|\u003c=|\u003e=|\u003c|\u003e|in|!in|rin|!rin|matches|!matches) \u003cvar2\u003e [and|or] ...`\n\n**Example:**\n```\n[task]\ndefault var1 var2 var3 var4\nif var1 == var2 or var3 == var4\n    pass\nelif EMPTY == EMPTY\n    pass\nelse\n    pass\n```\n\n### INTERFACE\nInterface with a **Python** module.\n\n**Usage:** `interface with [\u003cpackage\u003e.]\u003cmodule\u003e [alias \u003cname\u003e]`\n\n**Example:** \n\n```\n[task]\ninterface with package.mymodule alias module\ndefault var1 var2\ncall module.function(var1, var2)\n```\n\n### LINE\nDraw a line.\n\n**Usage:** `(=|-) ...`\n\n**Example:** `----------` or `==========`\n\n\n### MOVE\nMove a file or a directory tree to a new destination.\n\n**Usage:** `move \u003csrc_path_var\u003e to \u003cdest_path_var\u003e`\n\n### PASS\nPlaceholder for the code that you might write in the future. This statement does nothing. It is the same as the eponymous one in **Python**.\n\n**Usage:** `pass`\n\n### POKE\nPoke a file or directory to get access to a `dict` of properties if this path exists. Available properties: `size` `mtime` `ctime` `atime` `nlink` `uid` `gid` `mode` `ino` `dev`.\n\n**Usage:** `poke \u003cpath_var\u003e`\n\n**Example:**\n```\n[task]\nset path = /home/alex/iliad.txt\npoke path\nif R == EMPTY\n    : Oops ! This file doesn't exist\nelse\n    : File size -\u003e {R.size}\n```\n\n### PREPEND\nPrepend data to a file\n\n**Usage:** `prepend \u003cvar\u003e to \u003cfilename_var\u003e`\n\n### PRINT\nPrint data. You can use backquotes as delimiters. This statement supports variable interpolation.\n\n**Usage:** `: \u003ctext\u003e`\n\n**Example:**\n```\n[task]\n:  Hello World ! \n# Have you spotted the two extra spaces characters ?\n\n: ` Hello World ! `\n# hehehe, got you ! ;)\n```\n\n### PUSH\nPush variables into the input of the next spawned process.\n\n**Usage:** `push \u003cvar\u003e ...`\n\n### READ\nRead all or a specific line index (including negative index) from a file.\n\n**Usage:** `read (*|\u003cindex\u003e) from \u003cfilename_var\u003e`\n\n### REPLACE\nReplace some pattern in a text with a replacement value.\n\n**Usage:** `replace \u003cregex_var\u003e in \u003ctext_var\u003e with \u003creplacement_var\u003e`\n\n### RETURN\nReturn from a task with a value.\n \n**Usage:** `return [\u003cvar\u003e]`\n\n### SET\nDefine a new variable or update the content of an existing variable. You don't can't specify a data type, but instead you can apply an assigment tag that is one of: `(raw)` `(str)` `(list)` `(dict)` `(int)` `(float)` `(date)` `(time)` `(dtime)` `(tstamp)`. Note that backquotes can be used as delimiters for the value (right side of the equal sign). These delimiters will be ignored. Backticks aren't quotes. This statement supports variable interpolation.\n\n**Usage:** `set \u003cvar\u003e [(raw)|(str)|(list)|(dict)|(int)|(float)|(date)|(time)|(dtime)|(tstamp))] = \u003cvalue\u003e`\n\n**Example:** `set var (int) = 1 + 2`\n\n\n### SLEEP\nSleep for `x` seconds.\n\n**Usage:** `sleep \u003cseconds\u003e`\n\n### SPAWN\nSpawn a new process.\n\n**Usage:** `$ \u003cprogram\u003e [\u003cargument\u003e ...]`\n\n**Example:** `$ program1 arg {var} | program2 `\n\n### SPLIT\nSplit with a regex pattern a text into a list.\n\n**Usage:** `split \u003ctext_var\u003e with \u003cregex_var\u003e`\n\n### SPOT\nCount the number of occurrences of a regex pattern inside a text.\n\n**Usage:** `spot \u003cregex_var\u003e in \u003ctext_var\u003e`\n\n### STORE\nStore a variable (or a bunch of variables) in the **Database** namespace. A stored variable can be accessed like this: `D:var`\n\n**Usage:** `store \u003cvar\u003e ...`\n\n### THREAD\nBranch a subtask... but in a new thread. \n\n**Usage:** `~ \u003csubtask\u003e [\u003cargument\u003e ...]`\n\n### WHILE\nThe `while` loop. Use `break` to break it, and check `N` if you need a counter. \n\n**Usage:** `while (\u003cvar1\u003e|\u003cregex_var\u003e) (==|!=|\u003c=|\u003e=|\u003c|\u003e|in|!in|rin|!rin|matches|!matches) \u003cvar2\u003e [and|or] ...`\n\n### WRITE\nErase the content of a file to write some data inside.\n\n**Usage:** `write \u003cvar\u003e to \u003cfilename_var\u003e`\n\n\n# File and directory manipulation\nLet's explore how file and directory manipulatin is performed with **Backstage**.\n\n## Resource creation\n\nCreate a file:\n```\n[task]\n# create a file\nset path = /home/alex/iliad.txt\ncreate file path\n```\n\nCreate a directory:\n\n```\n[task]\n# create a directory\nset path = /home/alex/new/directory\ncreate dir path\n```\n\n## File edition\n\n```\n[task]\nset path = /home/alex/iliad.txt\nset var = Hello World\n\n# write data\nwrite var to path\n\n# append data to a file\nappend var to path\n\n# prepend data to a file\nprepend var to path\n```\n\n## Read the content of a file\n```\n[task]\nset path = /home/alex/iliad.txt\n\n# read all from 'iliad.txt'\nread * from path\n: {R}\n\n# read the line at index 3\nset index (int) = 1 + 1 + 1\nread index from path\n: {R}\n\n# just want to read the last line ?\nread -1 from path \n: {R}\n```\n\n## Iterating the content of a file\n```\n[task]\nset path = /home/alex/iliad.txt\n\n# iterate over the characters in a file\nfor char in path (file)\n    : Character -\u003e char\n\n# iterate over the lines in a file\nfor line in path (file)\n    : {line}\n```\n\n## Browse a folder\n\n```\n[task]\nset folder = /home/alex\n\nbrowse files and dirs in folder\n    : Directory -\u003e {R}\n    for item in files\n        : {item}\n    for item in dirs\n        : {item}\n```\n\n## Find resources\nThe `find` statement is like Glob but on steroid:\n```\n[task]\nset folder = /home/alex\nset regex (raw) = `[\\S\\s]*?`\nset timestamp1 = 1223322233\n\nfind files in folder matching regex and accessed between timestamp1 and NOW\n: Results -\u003e {R}\n\n```\n\n## Read resource properties\nYou can get from **Backstage** the properties of an arbitrary resource, like its size:\n\n```\n[task]\nset path = /home/alex/iliad.txt\n\n# poke a file\npoke path\n: Creation timestamp -\u003e {R.ctime}\n: Size -\u003e {R.size}\n\n```\n\n\n# Interfacing with Python\nInterfacing with **Python** is as simple as this:\n```\n[task]\ninterface with python.module as my_module\nset name = `John Doe`\nset age (int) = 40 + 2\ncall my_module.function(name, age)\n: Return -\u003e {R}\n```\n\n\u003e **Allowed return data types:** `str`, `list` (one-dimensional),`tuple` (one-dimensional), `dict` (one-dimensional), `int`, and `float`. **Python** functions can also return `True`, `False`, and `None`, which will be converted to **1**, **0** and an **empty string**, respectively.\n\n# Exception handling and tests\nWhenever an exception is raised, the variables `EXCEPTION` and `TRACEBACK` are updated and **Backstage** continues calmly its execution.\n\nNote that the variables `TRACEBACK` and `EXCEPTION` are cleared after the next successful command.\n\nIf you want the execution to stop whenever an exception is raised, just set `1` to the `FailFast` configuration option.\n\n```\n[task]\nconfig FailFast=1\n```\n\nIf you want to read a report of an exception when it's raised, just set `1` to the `ReportException` configuration option.\n\n```\n[task]\nconfig ReportException=1\n```\n\nIf you want to read the verbose [traceback](https://en.wikipedia.org/wiki/Stack_trace#Python) of an exception when it's raised, just set `1` to the `ShowTraceback` configuration option.\n\n```\n[task]\nconfig ShowTraceback=1\n```\n\nYou can edit these configuration options in the same command:\n\n```\n[task]\nconfig FailFast=1 ReportException=1 ShowTraceback=0\n```\nYou can read the current value of an arbitrary configuration option:\n\n```\n[task]\nconfig FailFast\n: FailFast -\u003e {R}\n```\n\n## Debug mode\nInstead of manually setting the `ReportException` configuration option to `1`, you can simply run a task in debug mode:\n\n```bash\n$ backstage -d task arg\n```\n\n## Tests\nTo create a test, just postfix `.test` to the name of a task. Then from the command line, just run the test `backstage --test task`.\n\n### Example\n```\n[task]\nset val (int) = {ARGS[0]} + {ARGS[1]}\nreturn val\n\n[task.test]\n# here we branch the task with the arguments 40 and 2\n\u0026 task 40 2\n# we expect 42 as return \nassert R == 42\n```\n\n# Command line interface and developer experience\n\n```bash\n$ backstage --help\nWelcome to Pyrustic Backstage !\nUltimate task automation tool for hackers.\n\nUsage:\n    backstage\n    backstage \u003ctask\u003e [\u003carg\u003e ...]\n    backstage \u003coption\u003e [\u003carg\u003e ...]\n    \nOptions:\n    -i, --intro                     Show file introductory text\n    -c, --check                     Show the list of tasks\n    -C, --Check                     Show the descriptive list of tasks\n    -d, --debug \u003ctask\u003e [\u003carg\u003e ...]  Run task in debug mode\n    -t, --test [\u003ctask\u003e ...]         Run tests\n    -T, --Test [\u003ctask\u003e ...]         Run tests in debug mode\n    -s, --search \u003ctask\u003e             Search for a task by its name\n    -S, --Search \u003ctask\u003e             Search for a task by keyword\n    -h, --help [\u003ctask\u003e]             Show help text\n\n    The \u003ctask\u003e string can use a glob-like syntax that allows \n    wildcards '*' and '?'. Therefore, 'task1' is identical to 'task*'.\n    \nVisit the webpage: https://github.com/pyrustic/backstage\n```\n\n## Developer experience\n**Backstage** will do its best to help you understand raised exceptions:\n\n```bash\n$ backstage task1\nZeroDivisionError at line 3 of [task1] !\ndivision by zero\n\n$ backstage task2\nInterpretationError at line 7 of [task2] !\nUsage: sleep \u003cseconds\u003e\n```\n\nWhen you run **Backstage** in the loop mode, you can enjoy the autocomplete functionality (use the Tab key to complete your input) and also the history functionality (use Up and Down arrows).\n\n```bash\n$ backstage\nWelcome to Pyrustic Backstage !\nUltimate task automation tool for hackers.\nPress 'Ctrl-c' or 'Ctrl-d' to quit.\nType '--help' or '-h' to show more information.\n\n(backstage) task(Tab Tab)\ntask    task1   task2   task3\n\n(backstage) --h(Tab)\n\n...\n\n```\n\n# Miscellaneous\nIn the following subsections, we will explore some miscellaneous information.\n\n## Dogfooding\n**Backstage** itself as a project relies on a `backstage.tasks` file (check the root of this repository). You are reading a document about **Backstage** that has been updated with **Backstage** !\n\n## Dependencies\n**Backstage** relies on these **Python** packages:\n- [Subrun](https://github.com/pyrustic/subrun) to spawn new processes;\n- [Shared](https://github.com/pyrustic/shared) to store data;\n- [Jesth](https://github.com/pyrustic/jesth) to parse `backstage.tasks` files;\n- [Oscan](https://github.com/pyrustic/jesth) to extract tokens from the script.\n\n## Indentation\nFour (4) spaces by [indent](https://en.wikipedia.org/wiki/Indentation_(typesetting)#Indentation_in_programming). Period.\n\n## Python 3\nInside the script file, you don't have to type `python3` in a command to spawn the **Python** interpreter. Just type `python` to spawn the same interpreter that is running **Backstage**:\n```\n[task]\n$ python -m my.package.module\n```\n\n## Shell\n**Backstage** doesn't rely on any **Shell**. But you can still pipe commands !\n\nExample, if you want to change the current working directory:\n```\n[task]\n# instead of doing this\n$ cd {HOME}\n\n# use the built-in 'cd'\ncd HOME\n\n# you can still spawn programs commonly used in the shell\n$ ls\n\n# or make this complex stuff (successfully performed on Ubuntu)\n$ python -m this | tail --lines=+3 | sort\n```\n\n## Data cache\n**Backstage** stores data in an automatically created directory `.backstage` located in the current working directory. Inside this directory you can find the `execution.log` and `database.json` files.\n\n## Automatic line break\nIf you don't want anymore an extra line break at the end of printed strings, you can turn off this functionality:\n\n```\n[task]\n# turn off auto line break\nconfig AutoLineBreak=0\n: `Hello `\n: `World`\n# turn on auto line break\nconfig AutoLineBreak=1\n: Hello World\n```\n\n\n## Lines\nYou can draw lines with the characters `=` or `-`. If you pick one, only this one is allowed to appear on the same line.\n```\n[task1]\n\n$ program1\n$ program2\n\n-----------------\n\n[task2]\npass\n\n=================\n```\n\n\n# Demo\nThe demo is a [repository](https://github.com/pyrustic/project) that contains a [backstage.tasks](https://github.com/pyrustic/project/blob/master/backstage.tasks#L1) file similar to the one used to build, package and publish my projects. Your mission, if you accept it, is to clone the demo repository and run the `backstage.tasks` which contains tasks to create a new **Python** `Hello Friend !` project, build it, perform versioning, init **Git** , perform **Git** Commit and **Git** Push, and even push the latest built package to [PyPI](https://pypi.org) !\n\n```bash\n# 1- clone the repository\n$ git clone https://github.com/pyrustic/project\n$ cd project\n\n# 2- install backstage\n$ pip install backstage\n\n# 3- install buildver\n$ pip install buildver\n\n# 4- install setupinit\n$ pip install setupinit\n\n# 5- list the tasks available in the `backstage.tasks` file\n$ backstage -c\nAvailable tasks (11):\n    build  check  clean  gendoc  gitcommit  gitinit  gitpush  init\n    release  test  upload2pypi\n\n# 6- descriptive list of tasks\n$ backstage -C\n\n# 7- initialize the project\n$ backstage init\nSuccessfully initialized !\n\n# 8- run the project\n$ python3 -m project\nHello Friend !\n\n# 9- build the project\n$ backstage build\nbuilding v0.0.1 ...\nSuccessfully built 'project' v0.0.1 !\nVERSION file updated from 0.0.1 to 0.0.2\n\n# 10- check the project\n$ backstage check\nproject v0.0.2 (source)\n.whl v0.0.1 (package) built 28 secs ago\n\n# 11- initialize Git\n$ backstage gitinit\nOrigin: https://github.com/pyrustic/project.git\n\n# 12- perform a Git Commit\n$ backstage gitcommit\n\n# 13- perform a Git Push\n$ backstage gitpush\n\n# 14- upload to PyPI\n$ backstage upload2pypi\n```\n\n\u003e **Note:** Commands `9`, `12`, `13`, and `14` can be replaced with one command: `backstage release`\n\n\n\n# Installation\n**Backstage** is **cross platform** and versions under **1.0.0** will be considered **Beta** at best. It is built on [Ubuntu](https://ubuntu.com/download/desktop) with [Python 3.8](https://www.python.org/downloads/) and should work on **Python 3.5** or **newer**.\n\n## For the first time\n\n```bash\n$ pip install backstage\n```\n\n## Upgrade\n```bash\n$ pip install backstage --upgrade --upgrade-strategy eager\n\n```\n\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\n[Back to top](#readme)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpyrustic%2Fbackstage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpyrustic%2Fbackstage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpyrustic%2Fbackstage/lists"}