{"id":31747324,"url":"https://github.com/entangled/repl-session","last_synced_at":"2026-01-20T16:59:58.258Z","repository":{"id":315864237,"uuid":"1060726714","full_name":"entangled/repl-session","owner":"entangled","description":"Python tool to run a given REPL session and evaluate the outcomes","archived":false,"fork":false,"pushed_at":"2025-09-29T06:56:57.000Z","size":64,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-29T07:23:53.831Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/entangled.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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-20T13:21:43.000Z","updated_at":"2025-09-29T06:48:08.000Z","dependencies_parsed_at":"2025-09-21T09:24:13.263Z","dependency_job_id":"91b86540-a388-4ef1-82d9-b8a7f36210e5","html_url":"https://github.com/entangled/repl-session","commit_stats":null,"previous_names":["entangled/repl-session"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/entangled/repl-session","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/entangled%2Frepl-session","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/entangled%2Frepl-session/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/entangled%2Frepl-session/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/entangled%2Frepl-session/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/entangled","download_url":"https://codeload.github.com/entangled/repl-session/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/entangled%2Frepl-session/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279001485,"owners_count":26083102,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-09T02:00:07.460Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2025-10-09T13:29:44.944Z","updated_at":"2026-01-20T16:59:58.249Z","avatar_url":"https://github.com/entangled.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# REPL Session\n\n[![docs](https://github.com/entangled/repl-session/actions/workflows/pages.yml/badge.svg)](https://github.com/entangled/repl-session/actions/workflows/pages.yml)\n[![Static Badge](https://img.shields.io/badge/docs-latest-blue)](https://entangled.github.io/repl-session)\n\nThis is script that runs a session on any REPL following a description  in a JSON file. The output contains the commands entered and the results given. This can be useful to drive documentation tests or literate programming tasks. This way we decouple running the commands from rendering or presenting the corresponding results, leading  to better reproducibility, caching output and modularity w.r.t. any other tools that you may use.\n\nThis is very similar to running a Jupyter notebook from the console, with the benefit that you don't need a Jupyter kernel available for the language you're using. The downside is that REPLs can be messy to interact with.\n\n## Is this for you?\n\nThis is only really useful if you're hacking together a literate programming environment similar to [Entangled](https://entangled.github.io). Suppose you have your documentation written in Markdown, ready for rendering with MkDocs or Pandoc. You want to automatically evaluate some expressions in this document as if they're entered in a REPL and process the results for inclusion in the document generator. Here `repl-session` is a nicely confined command-line tool, so its easy to integrate into a build pipeline.\n\n## How does it work?\n\nThe REPL of your choice is started and interacted with through the `pexpect` library. All I/O is dealt with through `msgspec`.\n\nThe preferred way to solve this is by using `jupyter-client` in combination with an existing Jupyter kernel. However, not all languages have a Jupyter kernel available, and developing one takes a bit more than configuring `pexpect` for an existing REPL. I may still include Jupyter support in this application at a later stage.\n\n## Install\n\nInstall with,\n\n```bash\npip install repl-session\n```\n\nOr equivalent Poetry, Astral Uv, Hatch or Conda commands.\n\n## Documentation\n\nThe full documentation is available at [entangled.github.io/repl-session](https://entangled.github.io/repl-session).\n\n## Examples\n\nHere are some examples where we could interact with a REPL successfully. In general, the less intricate the REPL the better the results.\n\n### Chez Scheme\n\nI like to work with [Chez Scheme](https://cisco.github.io/ChezScheme/). Suppose I want to document an interactive session. This can be done:\n\n```yaml\n#| file: test/scheme.yml\n#| file: test/scheme.yml\n#| file: test/scheme.yml\n#| file: test/scheme.yml\n#| file: test/scheme.yml\nconfig:\n  command: \"scheme --eedisable\"\n  first_prompt: \"\u003e \"\n  change_prompt: '(waiter-prompt-string \"{key}\u003e\")'\n  prompt: \"{key}\u003e \"\n  # continuation_prompt: \"\"\ncommands:\n  - command: (* 6 7)\n  - command: |\n      (define (fac n init)\n        (if (zero? n)\n          init\n          (fac (- n 1) (* init n)))))\n  - command: (fac 10 1)\n```\n\nPassing this to `repl-session`, it will start the Chez Scheme interpreter, waiting for the `\u003e` prompt to appear. It then changes the prompt to a generated `uuid4` code, for instance `27e87a8a-742c-4501-b05d-b05814f5a010\u003e `. This will make sure that we can't accidentally match something else for an interactive prompt (imagine we're generating some XML!). Since commands are also echoed to standard out, we need to strip them from the resulting output. Running this should give:\n\n```bash\nrepl-session \u003c test/scheme.yml | jq '.commands.[].output'\n```\n\n```\n\"42\"\n\"(define (fac n init)\\n  (if (zero? n)\\n    init\\n    (fac (- n 1) (* init n)))))\"\n\"3628800\"\n```\n\n### Lua\n\nThis looks very similar to the previous example:\n\n```yaml\n#| file: test/lua.yml\n#| file: test/lua.yml\n#| file: test/lua.yml\n#| file: test/lua.yml\n#| file: test/lua.yml\nconfig:\n  command: \"lua\"\n  first_prompt: \"\u003e \"\n  change_prompt: '_PROMPT = \"{key}\u003e \"; _PROMPT2 = \"{key}+ \"'\n  prompt: \"{key}\u003e \"\n  continuation_prompt: \"{key}\\\\+ \"\n  strip_ansi: true\ncommands:\n  - command: 6 * 7\n  - command: '\"Hello\" .. \", \" .. \"World!\"'\n  - command: |\n      function fac(n, m)\n          if m == nil then\n              return fac(n, 1)\n          end\n          if n == 0 then\n              return m\n          else\n              return fac(n-1, m*n)\n          end\n      end\n  - command: fac(10)\n```\n\nThe Lua REPL is not so nice. It sends ANSI escape codes and those need to be filtered out.\n\n```bash\nrepl-session \u003c test/lua.yml | jq '.commands.[].output'\n```\n\n```\n\"42\"\n\"Hello, World!\"\n```\n\n### Python\n\nThe Python REPL got a revision in version 3.13, with lots of colour and ANSI codes.\n\n```yaml\n#| file: test/python.yml\n#| file: test/python.yml\n#| file: test/python.yml\n#| file: test/python.yml\n#| file: test/python.yml\nconfig:\n  command: python -q\n  first_prompt: \"\u003e\u003e\u003e\"\n  change_prompt: 'import sys; sys.ps1 = \"{key}\u003e\u003e\u003e \"; sys.ps2 = \"{key}+++ \"'\n  prompt: \"{key}\u003e\u003e\u003e \"\n  continuation_prompt: \"{key}\\\\+\\\\+\\\\+ \"\n  environment:\n    NO_COLOR: \"1\"\n    PYTHON_BASIC_REPL: \"1\"\ncommands:\n  - command: print(\"Hello, World!\")\n  - command: 6 * 7\n  - command: |\n      def fac(n):\n          for i in range(1, n):\n              n *= i\n          return n\n  - command: fac(10)\n```\n\n## Input/Output structure\n\nThe user can configure how the REPL is called and interpreted.\n\n```python\n#| id: input-data\n#| id: input-data\n#| id: input-data\n#| id: input-data\n#| id: input-data\nclass ReplConfig(msgspec.Struct):\n    \"\"\"Configuration\n\n    Attributes:\n        command (str): Command to start the REPL\n        first_prompt (str): Regex to match the first prompt\n        change_prompt (str): Command to change prompt; should contain '{key}' as an\n            argument.\n        next_prompt (str): Regex to match the changed prompts; should contain '{key}'\n            as an argument.\n        append_newline (bool): Whether to append a newline to given commands.\n        strip_command (bool): Whether to strip the original command from the gotten\n            output; useful if the REPL echoes your input before answering.\n        timeout (float): Command timeout for this session in seconds.\n    \"\"\"\n\n    command: str\n    first_prompt: str\n    change_prompt: str\n    prompt: str\n    continuation_prompt: str | None = None\n    strip_ansi: bool = False\n    environment: dict[str, str] = msgspec.field(default_factory=dict)\n    timeout: float = 5.0\n\n\n```\n\nThen, a session is a list of commands. Each command should be a UTF-8 string, and we allow to attach some meta-data like expected MIME type for the output. We can also pass an expected output in the case of a documentation test. If `output` was already given on the input, it is  moved to `expected`. This way it becomes really easy to setup regression tests on your documentation. Just rerun on the generated output file.\n\n```python\n#| id: input-data\n#| id: input-data\n#| id: input-data\n#| id: input-data\n#| id: input-data\nclass ReplCommand(msgspec.Struct):\n    \"\"\"A command to be sent to the REPL.\n\n    Attributes:\n        command (str): the command.\n        output_type (str): MIME type of expected output.\n        output (str | None): evaluated output.\n        expected (str | None): expected output.\n    \"\"\"\n\n    command: str\n    output_type: str = \"text/plain\"\n    output: str | None = None\n    expected: str | None = None\n\n\nclass ReplSession(msgspec.Struct):\n    \"\"\"A REPL session.\n\n    Attributes:\n        config (ReplConfig): Config for setting up a REPL session.\n        commands (list[ReplCommand]): List of commands in the session.\n    \"\"\"\n\n    config: ReplConfig\n    commands: list[ReplCommand]\n\n\n```\n\n## License and contribution\n\nLicensed under the Apache 2.0 license. Contributions are welcome: if you've succesfully applied `repl-session` to a REPL not listed in the documentation, consider contributing your configuration to the documentation. If your contribution fixes a bug, please first file an issue.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fentangled%2Frepl-session","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fentangled%2Frepl-session","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fentangled%2Frepl-session/lists"}