Package: runem
Run 'emReducing the wall-clock time running your developer-local tasksrunem(run 'em) is an unopinionated way to declare and run the many command-line tools developers use regularly.1. OverviewThe core objective of Run'em's is to minimize the wall-clock time required for executing whatever command-line tools a developer uses (orshoulduse). Overall it is designed to enhance iteration speed and boost developer productivity.runemis also designed to be easy to learn and simple to use, butrunemalso has many powerful tools for advanced users.Job definitions are declarative and simple.The in-built reports show how long each job took and how much timerunemsaved you.Jobs can be filtered in or out very easily.Multiple projects can be supported in a single.runem.ymlconfig, support workspaces and mono-repos. Also multiple task types, working on multiple file-types can be supported.Finally, because of how it's built, job definitions are auto-documented viarunem --help. This help onboarding new developers by making tool-discovery easier. Therefore it also helps maintenance of developer tools.1.1. Why is it calledrunem?Primarilyrunem, as a command line tool, is quick to type and tries to just get out of the way when running your developer-local tools.The name "runem" is a portmanteau of "run" and "them", encapsulating that runem "runs them", but slightly faster.2. ContentsRun 'emReducing the wall-clock time running your developer-local tasks1. Overview1.1. Why is it calledrunem?2. Contents3. Feature overview4. Installation5. Quick-start5.1. Basic quick-start5.2. A more complete quick-start5.2.1. A simple.runem.yml5.2.2. A simple python task6. Basic usage6.1. Tag filters6.1.1. Run jobs only with the 'lint' tag:6.1.2. If you want to lint all codeexceptnodejs code (and you have the appropriate tags):6.1.3. Run fast checks onpre-commit6.2. Phase filters6.2.1. Focus on a phase6.2.2. Exclude slow phases temporarily7. Reports on your tasks7.1. Task timings report7.2. Bar-graphs withtermplotlib8. Using `--help`` to get an overview of your Jobs9. Configuration9.1.config- Run 'em global config9.2.job- Job config10. Troubleshooting & Known issues9.1. I don't see bar graph timing reports!Solution:10.2. I can't specify a dependency!Solution10.3. Why is there so much output on errors, it looks duplicated?Solution10.4. When errors happen I don't see reports for jobs!Solution10.5. I want to see log output for tasks in real-time, as they're happening!SolutionContributing to and supporting runemDevelopmentSponsor3. Feature overviewDeclarative TasksDescribe all your tasks once, and optionally describe how and when to run them.Tagged Jobs:Use tagging to define which type of jobs you want to run, be itpre-commit,lint,testor in multi-project codebases to split between runningpython,node.jsorc++jobs, depending on the context you are working in!Multiprocess Execution:Leverage the power of multiprocessing for concurrent test job execution, optimizing efficiency and reducing runtime.Data-Driven Test Management:Drive your tests with data, making it easy to adapt and scale your testing suite to various scenarios, allowing you to execute, track, and analyze your dev-ops suite with ease.4. Installationpipinstallrunem5. Quick-start5.1. Basic quick-startCreate the following.runem.ymlfile at the root of your project:- job:
    command: echo "hello world!"Then anywhere in your project runrunemto see how and when that task is run, and how long it took:runemTo see the actual log output you will need to use--verboseasrunemhides anything that isn't important. Only failures and reports are considered important.# Or, to see "hello world!", use --verboserunem--verbose# add --verbose to see the actual outputTo see how you can control your job use--help:runem--help5.2. A more complete quick-startHere's a simple setup for a python project.5.2.1. A simple.runem.yml- config:
    phases:
      - edit
      - analysis
    files:
      - filter:
          tag: py
          regex: ".*\\.py$"
    options:
      - option:
          name: black
          default: true
          type: bool
          desc: allow/disallows py-black from running
      - option:
          name: docformatter
          default: true
          type: bool
          desc: formats docs and comments in whatever job can do so
      - option:
          name: check_only
          alias: check
          default: false
          type: bool
          desc: runs in check-mode, erroring if isort, black or any text-edits would occur
- job:
    command: pytest tests/
    when:
      phase: analysis
      tags:
        - py
- job:
    command: mypy my_project/ tests/
    when:
      phase: analysis
      tags:
        - py
- job:
    addr:
      file: runem_hooks/py.py
      function: _job_py_code_reformat
    label: reformat py
    when:
      phase: edit
      tags:
        - py
        - format
        - py formatNotice that this specifies:Thephasesto use, and their order:This shows how we can control tasks that edit the files can go before analysisThis reduces any false-negatives the jobs may generate from running multiple jobs that may contend for write-access on the same filesNOTE:phasesare an early-stage way:To implement dependency chainingThere is no dependency linking between jobs, yet.To manage resourcesFor example, if you have a task which is threaded and/or memory heavy, you may want to put that into its own phase to get faster output.tagsto allow control over which jobs to run.Also which files to pass to jobs.File-filters to detect which files the job operate on.NOTE: this is a WIP featureUses job-options:Allowing a python-function-job to control it's sub-task/processes.If you use--helpyou will see a summary of all controls available.5.2.2. A simple python taskHere's a simple python file describing a job. This accompanies the above.runem.yml.# runem_hooks/py.py# A file to do more advanced and conditional checking using the `runem` infrastructure.importtypingfromrunem.logimportlogfromrunem.run_commandimportRunCommandUnhandledError,run_commandfromrunem.typesimportFilePathList,JobName,JobReturnData,Optionsdef_job_py_code_reformat(**kwargs:typing.Any,)->None:"""Runs python formatting code in serial order as one influences the other."""label:JobName=kwargs["label"]options:Options=kwargs["options"]python_files:FilePathList=kwargs["file_list"]# put into 'check' mode if requested on the command lineextra_args=[]docformatter_extra_args=["--in-place",]if"check_only"inoptionsandoptions["check_only"]:extra_args.append("--check")docformatter_extra_args=[]# --inplace is not compatible with --checkif"black"inoptionsandoptions["black"]:black_cmd=["python3","-m","black",*extra_args,*python_files,]kwargs["label"]=f"{label}black"run_command(cmd=black_cmd,**kwargs)if"docformatter"inoptionsandoptions["docformatter"]:docformatter_cmd=["python3","-m","docformatter","--wrap-summaries","88","--wrap-descriptions","88",*docformatter_extra_args,*extra_args,*python_files,]allowed_exits:typing.Tuple[int,...]=(0,# no work/change required3,# no errors, but code was reformatted)if"check_only"inoptionsandoptions["check_only"]:# in check it is ONLY ok if no work/change was requiredallowed_exits=(0,)kwargs["label"]=f"{label}docformatter"run_command(cmd=docformatter_cmd,ignore_fails=False,valid_exit_ids=allowed_exits,**kwargs,)The above python file accompanies the above.runem.ymlconfiguration and does slightly more advanced work. The file contains:a single job.the job itself linearises edit tasks that would otherwise contend for write-access to the files they operate on.formatting and doc-generation both edit files, conforming them to the coding standard.usesoptions(see the config section) to control whether to:usecheck-onlymode for CiCd, modifying the command-line switched passed down to the sub-commands.control whetherpython-blackand/or/nordocformatteris run.modifies the allowed-exit codes fordocformatterto be0or3, matching the designed behaviour of that tool.6. Basic usage# run all configured default jobsrunem# ---- or ----# apply filters and controlsrunem[--tagstag1,tag2,tag3][--not-tagstag1,tag2,tag3]\[--phasesphaseX,phaseY]\[--MY-OPTION][--not-MY-OPTION]# ---- or ----# run as a modulepython3-mrunem[--tagstag1,tag2,tag3][--not-tagstag1,tag2,tag3]\[--phasesphaseX,phaseY]\[--MY-OPTION][--not-MY-OPTION]6.1. Tag filtersJobs are tagged in the .runem.yml config file. Each unique tags is made available on the command-line. To see which tags are available use--help. To add a new tag extend thetagsfield in thejobconfig.You can control which types of jobs to run via tags. Just tag the job in the config and then from the command-line you can add--tagsor--not-tagsto refine exactly which jobs will be run.To debug why a job is not selected pass--verbose.For example, if you have apythontagged job or jobs, to run only run those jobs you would do the following:runem--tagspython--tagsare exclusive filter in, that is the tags passed in replace are the only tags that are run. This allows one to focus on running just a subset of tags.--not-tagsare subtractive filter out, that is any job with these tags are not run, even if they have tags set via the--tagsswitch. Meaning you can choose to runpythontagged job but not run thelintjobs with--tags python --not-tags lint, and so on.6.1.1. Run jobs only with the 'lint' tag:runem--tagslint6.1.2. If you want to lint all codeexceptnodejs code (and you have the appropriate tags):runem--tagslint--not-tagsdeprecated6.1.3. Run fast checks onpre-commitIf you have fast jobs that tagged as appropriate for pre-commit hooks.mkdirscripts/git-hooksecho"runem --tags pre-commit">scripts/git-hooks/pre-commit# add the following to .git/config# [core]#   # ... existing config ...#	  hooksPath = ./scripts/git-hooks/husky/6.2. Phase filtersSometimes just want to run a specific phase, so you can focus on it and iterate quickly, within that context.6.2.1. Focus on a phaseFor example, if you have areformatphase, you might want to run justreformatjobs phase whilst preparing a commit and are just preparing cosmetic changes e.g. updating comments, syntax, or docs.runem--phasereformat6.2.2. Exclude slow phases temporarilyIf you have 4 stagesbootstrap,pre-run,reformat,testandverifyphase, and are tightly iterating and focusing on the 'test-coverage' aspect of the test-phase, then you do not care about formatting as long as you can see your coverage results ASAP. However if your test-coverage starts passing then you will care about subsequent stages, so you can exclude the slower reformat-stage with the following and everything else will run.runem--not-phasepre-runreformatNote:The--tagsand--not-tagsoptions can be used in combination to further refine task execution based on your requirements.7. Reports on your tasksRunem has a built-in support for reporting on tasks7.1. Task timings reportRunem will run the task and report how long the task took and whether it saved you any time, for example:# output from runem when run on runem's project, without `termplotlib`
runem: Running 'pre-run' with 2 workers (of 8 max) processing 2 jobs
runem: Running 'edit' with 1 workers (of 8 max) processing 1 jobs
runem: Running 'analysis' with 7 workers (of 8 max) processing 7 jobs
runem: reports:
runem: runem: 8.820488s
runem: ├runem.pre-build: 0.019031s
runem: ├runem.run-phases: 8.801317s
runem: ├pre-run (total): 0.00498s
runem: │├pre-run.install python requirements: 2.6e-05s
runem: │├pre-run.ls -alh runem: 0.004954s
runem: ├edit (total): 0.557559s
runem: │├edit.reformat py: 0.557559s
runem: ├analysis (total): 21.526145s
runem: │├analysis.pylint py: 7.457029s
runem: │├analysis.flake8 py: 0.693754s
runem: │├analysis.mypy py: 1.071956s
runem: │├analysis.pytest: 6.780303s
runem: │├analysis.json validate: 0.035359s
runem: │├analysis.yarn run spellCheck: 4.482992s
runem: │├analysis.prettier: 1.004752s
runem: report: coverage html: ./reports/coverage_python/index.html
runem: report: coverage cobertura: ./reports/coverage_python/cobertura.xml
runem: DONE: runem took: 8.820488s, saving you 13.268196s7.2. Bar-graphs withtermplotlibIf you havetermplotlibinstalled you will see something like:runem: Running 'pre-run' with 2 workers (of 8 max) processing 2 jobs
runem: Running 'edit' with 1 workers (of 8 max) processing 1 jobs
runem: Running 'analysis' with 7 workers (of 8 max) processing 7 jobs
runem: reports:
runem                                  [14.174612]  ███████████████▋
├runem.pre-build                       [ 0.025858]
├runem.run-phases                      [14.148587]  ███████████████▋
├pre-run (total)                       [ 0.005825]
│├pre-run.install python requirements  [ 0.000028]
│├pre-run.ls -alh runem                [ 0.005797]
├edit (total)                          [ 0.579153]  ▋
│├edit.reformat py                     [ 0.579153]  ▋
├analysis (total)                      [36.231034]  ████████████████████████████████████████
│├analysis.pylint py                   [12.738303]  ██████████████▏
│├analysis.flake8 py                   [ 0.798575]  ▉
│├analysis.mypy py                     [ 0.335984]  ▍
│├analysis.pytest                      [11.996717]  █████████████▎
│├analysis.json validate               [ 0.050847]
│├analysis.yarn run spellCheck         [ 8.809372]  █████████▊
│├analysis.prettier                    [ 1.501236]  █▋
runem: report: coverage html: ./reports/coverage_python/index.html
runem: report: coverage cobertura: ./reports/coverage_python/cobertura.xml
runem: DONE: runem took: 14.174612s, saving you 22.6414sNOTE: each phase's total system-time is reported above the timing for the individual jobs ran in that phase. This is NOT wall-clock time.8. Using `--help`` to get an overview of your JobsThe--helpswitch will show you a full list of all the configured job-tasks, the tags, and the override options.--helpdescribes how to configure a specific run foryour.runem.ymlsetup, and does NOT just documentrunemitself; it documentsyourworkflow.$python-mrunem--help#or$runem--helpFor exampleusage: runem.py [-h] [--jobs JOBS [JOBS ...]] [--not-jobs JOBS_EXCLUDED [JOBS_EXCLUDED ...]] [--phases PHASES [PHASES ...]]
                [--not-phases PHASES_EXCLUDED [PHASES_EXCLUDED ...]] [--tags TAGS [TAGS ...]] [--not-tags TAGS_EXCLUDED [TAGS_EXCLUDED ...]]
                [--black] [--no-black] [--check-only] [--no-check-only] [--coverage] [--no-coverage] [--docformatter] [--no-docformatter]
                [--generate-call-graphs] [--no-generate-call-graphs] [--install-deps] [--no-install-deps] [--isort] [--no-isort] [--profile]
                [--no-profile] [--update-snapshots] [--no-update-snapshots] [--unit-test] [--no-unit-test] [--unit-test-firebase-data]
                [--no-unit-test-firebase-data] [--unit-test-python] [--no-unit-test-python] [--call-graphs | --no-call-graphs]
                [--procs PROCS] [--root ROOT_DIR] [--verbose | --no-verbose | -v]

Runs the Lursight Lang test-suite

options:
  -h, --help            show this help message and exit
  --call-graphs, --no-call-graphs
  --procs PROCS, -j PROCS
                        the number of concurrent test jobs to run, -1 runs all test jobs at the same time (8 cores available)
  --root ROOT_DIR       which dir to use as the base-dir for testing, defaults to checkout root
  --verbose, --no-verbose, -v

jobs:
  --jobs JOBS [JOBS ...]
                        List of job-names to run the given jobs. Other filters will modify this list. Defaults to '['flake8 py', 'install
                        python requirements', 'json validate', 'mypy py', 'pylint py', 'reformat py', 'spell check']'
  --not-jobs JOBS_EXCLUDED [JOBS_EXCLUDED ...]
                        List of job-names to NOT run. Defaults to empty. Available options are: '['flake8 py', 'install python requirements',
                        'json validate', 'mypy py', 'pylint py', 'reformat py', 'spell check']'

phases:
  --phases PHASES [PHASES ...]
                        Run only the phases passed in, and can be used to change the phase order. Phases are run in the order given. Defaults
                        to '{'edit', 'pre-run', 'analysis'}'.
  --not-phases PHASES_EXCLUDED [PHASES_EXCLUDED ...]
                        List of phases to NOT run. This option does not change the phase run order. Options are '['analysis', 'edit', 'pre-
                        run']'.

tags:
  --tags TAGS [TAGS ...]
                        Only jobs with the given tags. Defaults to '['json', 'lint', 'py', 'spell', 'type']'.
  --not-tags TAGS_EXCLUDED [TAGS_EXCLUDED ...]
                        Removes one or more tags from the list of job tags to be run. Options are '['json', 'lint', 'py', 'spell', 'type']'.

job-param overrides:
  --black               allow/disallows py-black from running
  --no-black            turn off allow/disallows py-black from running
  --check-only          runs in check-mode, erroring if isort, black or any text-edits would occur
  --no-check-only       turn off runs in check-mode, erroring if isort, black or any text-edits would occur
  --coverage            generates coverage reports for whatever can generate coverage info when added
  --no-coverage         turn off generates coverage reports for whatever can generate coverage info when added
  --docformatter        formats docs and comments in whatever job can do so
  --no-docformatter     turn off formats docs and comments in whatever job can do so
  --generate-call-graphs
                        Generates call-graphs in jobs that can
  --no-generate-call-graphs
                        turn off Generates call-graphs in jobs that can
  --install-deps        gets dep-installing job to run
  --no-install-deps     turn off gets dep-installing job to run
  --isort               allow/disallows isort from running on python files
  --no-isort            turn off allow/disallows isort from running on python files
  --profile             generate profile information in jobs that can
  --no-profile          turn off generate profile information in jobs that can
  --update-snapshots    update snapshots in jobs that can update data snapshots
  --no-update-snapshots
                        turn off update snapshots in jobs that can update data snapshots
  --unit-test           run unit tests
  --no-unit-test        turn off run unit tests
  --unit-test-firebase-data
                        run unit tests for the firebase function's data
  --no-unit-test-firebase-data
                        turn off run unit tests for the firebase function's data
  --unit-test-python    run unit tests for the python code
  --no-unit-test-python
                        turn off run unit tests for the python code9. Configurationrunemsearches for.runem.ymland will pre-load the command-line options withConfiguration is Yaml and consists of two main configurations,configandjob:configdescribes how the jobs should be run.eachjobentry describe a job-task, such and running unit-tests, linting or running any other type of command.9.1.config- Run 'em global configphases:Description:Specifies the different phases of the testing process, in the order they are to be run. Each job will be run under a specific phase.Values:A list of strings representing "phases" such as pre-run (e.g. bootstrapping), edit (running py-black or prettifier or clang-tools), and analysis (unit-tests, coverage, linting).files:Description:Defines filters for categorizing files based on tags and regular expressions. Maps tags to files-to-be tested. If a job has one or more tags that map to file-filters that job will receive all files that match those filters.Values:A list of dictionaries, each containing a 'filter' key with 'tag' and 'regex' subkeys.options:Description:Configures various option-overrides for the job-tasks. Overrides can be set on the command line and accessed by jobs to turn on or off features such as 'check-only' or to opt out of sub-tasks.Values:A list of dictionaries, each containing an 'option' key with 'default' boolean value, a 'name', a 'type', a 'desc', and optional 'alias' subkeys. NOTE: only 'bool' types are currently supported.default:Specifies the default value of the option.name:Represents the name of the option.type:Indicates the data type of the option (e.g., bool for boolean).desc:Provides a description of the option.alias:(Optional) Provides an alias for the option if specified.9.2.job- Job configjob:Description:Represents a specific job task that is to be run asynchronously.Fields:command:Description:a simple command line to be run. Commands will be run by the system exactly as they are written. Useaddrfor more complicated jobs. Commands are run in their associatecontext.*Example:command:yarn run prettycommand:python3 -m pylint **/*.pycommand:bash scripts/build_wrapper.shcommand:clang-tidy -checks='*' -fix -header-filter='.*' your_file.cppaddr:Description:Specifies where a python function can be found. The python function will be loaded at runtime byrunemand called with thecontext. The function receives all information needed to run the job, includinglabel,JobConfig,verbose,optionsand other run-time context.Subkeys:file:Indicates the file path of the job.function:Indicates the function within the file that represents the job.Example:file:scripts/test-hooks/rust_wrappers.pyfunction:_job_rust_code_reformatctx:Description:Provides the execution context for the job, including the working directory and parameters.Subkeys:cwd:Specifies the working directory for the job.params:Specifies parameters for the job.Example:cwd:.params:limitFilesToGroup:truelabel:Description:Assigns a label to the job for identification.Example:label:reformat pywhen:Description:Defines the conditions under which the job should run.Subkeys:phase:Specifies the testing phase in which the job should run.tags:Specifies the tags associated with the job.Example:when:phase:edittags:-py-formatExample:-job:addr:file:scripts/test-hooks/nodejs.pyfunction:_job_js_code_reformatctx:cwd:src/subproject_4params:limitFilesToGroup:truelabel:reformatwhen:phase:edittags:-js-subproject4-pretty10. Troubleshooting & Known issues9.1. I don't see bar graph timing reports!We don't specify it in the output to reduce dependency installation for ci/cd. We may change this decision, especially ashalois a first-order dependency now.Solution:installtermplotlib,10.2. I can't specify a dependency!Outside ofphaseswe don't support direct dependency-chaining between tasks. We would like to do this at some point. PRs gladly accepted for this.SolutionUsephasesinstead, or contribute a PR.10.3. Why is there so much output on errors, it looks duplicated?We haven't looked at how we manage exception-handling with the pythonmultiprocessinglibrary, yet. Errors inmultiprocessingprocs tend to be re-reported in the calling process. PRs welcome.SolutionJust look at one of the outputs.10.4. When errors happen I don't see reports for jobs!We try to show reports for completed tasks. If the task you're interested in doesn't show, it probably hasn't been executed yet. Otherwise scroll up and you should see the reports and timings ofcompletedtasks, if not, you may need to increase your terminal's view-buffer size.SolutionIf you are focusing on one task and are only interested in how that task is performing/operating, use one of the many filtering options e.g.--jobs "your job name" "another job name"or--tags unittest.10.5. I want to see log output for tasks in real-time, as they're happening!We don't stream stdout/stderr direct to console, or provide functionality for doing so, yet. However, we also believe that it would be a nice feature and welcome PRs.SolutionOn failure and with--verbosemode, the exact command is logged to console along with the environment that job was run in. You can copy/paste that command line to a terminal and run the command manually. The stdout/stderr will then be as you would get for that command. Refer to the documentation for that command.Contributing to and supporting runemAwesome runem created by lursightDevelopmentRead theCONTRIBUTING.mdfile.Sponsor❤️ Sponsor this project