cmra 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
cmra-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,173 @@
1
+ Metadata-Version: 2.4
2
+ Name: cmra
3
+ Version: 0.1.0
4
+ Summary: CMRA โ€” Creative Minimal Recursive Automaton language runtime
5
+ Author: TeapotChimera418
6
+ License: MIT
7
+ Project-URL: Homepage, https://cmra-esolang.vercel.app
8
+ Project-URL: Repository, https://github.com/TeapotChimera418/cmra
9
+ Project-URL: Changelog, https://github.com/TeapotChimera418/cmra/blob/main/CHANGELOG.md
10
+ Keywords: esolang,esoteric,programming,language,interpreter
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Topic :: Software Development :: Interpreters
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+
18
+ # ๐Ÿ‰ CMRA Language (Chimera)
19
+
20
+ > **"Where code breathes fire and flows like flame..."**
21
+
22
+ **CMRA** (Chimera) is an esoteric programming language exploring reversible execution and direction control through a dragon-inspired syntax. Programs can soar *up* or dive *down* through code at runtime.
23
+
24
+ ๐Ÿ”ฅ **GitHub:** [TeapotChimera418/cmra](https://github.com/TeapotChimera418/cmra)
25
+ ๐ŸŒ **Playground:** [cmra-esolang.vercel.app/playground.html](https://cmra-esolang.vercel.app/playground.html)
26
+ ๐Ÿ“š **Docs:** [cmra-esolang.vercel.app](https://cmra-esolang.vercel.app/index.html)
27
+ ๐Ÿ“– **User Guide:** [GUIDE.md](GUIDE.md)
28
+
29
+ ---
30
+
31
+ ## ๐Ÿ™Œ Origin
32
+
33
+ CMRA began as a workshop project I attended by
34
+ [Tushar Sadhwani](https://github.com/tusharsadhwani) during a college multifest. The
35
+ prototype we built is in `prototype.py`. For a nonโ€‘aliased baseline, see Tushar's
36
+ [esolangs](https://github.com/tusharsadhwani/esolangs) repository. You can also read his
37
+ blog at [tush.ar](https://tush.ar).
38
+
39
+ This is a tiny side project made for fun and learning. If you build something cool or want
40
+ to improve the interpreter, feel free to reach out on
41
+ [GitHub (@TeapotChimera418)](https://github.com/TeapotChimera418) or Discord [`gamingchimera`](https://discord.com/users/736465046317563915).
42
+
43
+ ## โœจ Try it Online
44
+
45
+ **๐Ÿ”ฅ [Launch the Interactive Playground](https://cmra-esolang.vercel.app/playground.html)**
46
+
47
+ Write and run CMRA code directly in your browser โ€” no installation needed. Powered by Pyodide.
48
+
49
+ ---
50
+
51
+ ## โšก Installation & Quick Start
52
+
53
+ ### pip install (recommended)
54
+
55
+ ```powershell
56
+ # Install from PyPI:
57
+ pip install cmra
58
+
59
+ # Or install from source (development):
60
+ pip install -e .
61
+
62
+ # Now use `cmra` and `cmrash` from anywhere inside the venv:
63
+ cmra "test cases\test.cmra"
64
+ cmrash "test cases\test.cmrash"
65
+ ```
66
+
67
+ ### No-install PowerShell wrapper
68
+
69
+ ```powershell
70
+ .\cmra.ps1 "test cases\test.cmra"
71
+ ```
72
+
73
+ ### Run with Python directly
74
+
75
+ ```powershell
76
+ python -m cmra.cli "test cases\test.cmra"
77
+ python cmra.py "test cases cmra\test.cmra" # legacy single-file
78
+ python cmra_simplified.py "test cases cmra_simplified\test.cmrasim"
79
+ ```
80
+
81
+ ---
82
+
83
+ ## ๐Ÿ”ฑ The Twin Dragons
84
+
85
+ | Dragon | Entry point | Keywords |
86
+ |--------|-------------|----------|
87
+ | ๐Ÿฒ **Fire Dragon** | `cmra.py` / `src/cmra/fire.py` | `bind` `roar` `sniff` `dive` `soar` `murmur` |
88
+ | ๐ŸŒ‘ **Shadow Dragon** | `cmra_simplified.py` / `src/cmra/shadow.py` | `=` `print` `check` `reverse` `;` |
89
+ | ๐Ÿ‰ **Ancient Wyrm** | `prototype.py` | Compact baseline, inline `sniff` only |
90
+
91
+ ---
92
+
93
+ ## ๐Ÿฒ Language at a Glance
94
+
95
+ ```
96
+ murmur Fire Dragon โ€” count 0 to 5
97
+ i bind 0
98
+ flag bind 1
99
+ sniff i <= 5 : sniff flag == 0 : dive
100
+ flag bind 0
101
+ sniff flag == 0 : roar i
102
+ sniff flag == 0 : i bind i + 1
103
+ flag bind 1
104
+ sniff i <= 5 : sniff flag == 1 : soar
105
+ ```
106
+
107
+ - `bind` โ€” assign variable
108
+ - `roar` โ€” print
109
+ - `sniff โ€ฆ : action` โ€” conditional (inline or block)
110
+ - `dive` / `soar` โ€” set execution direction forward / reverse
111
+ - No loop keyword โ€” direction reversal + flag guards make loops
112
+
113
+ **โ†’ See [GUIDE.md](GUIDE.md) for the full walkthrough.**
114
+
115
+ ---
116
+
117
+ ## โšก Execution Model
118
+
119
+ A global `DIRECTION` (`1` or `-1`) steps through lines. `soar` reverses it; `dive` restores forward. When direction goes past the first or last line, the program ends. Block `sniff/check` enters at the first (forward) or last (reverse) line of the block.
120
+
121
+ ---
122
+
123
+ ## ๐ŸŽฎ Test Cases & Projects
124
+
125
+ ```powershell
126
+ # Test cases (pip-installed cmra)
127
+ cmra "test cases\test.cmra"
128
+ cmra "test cases\test_strings.cmra"
129
+ cmra "test cases\test_arith.cmra"
130
+
131
+ # Projects
132
+ cmra projects\calculator.cmra
133
+ cmra projects\countdown.cmra
134
+ cmra projects\fizzbuzz.cmra
135
+ cmra projects\story_adventure.cmra
136
+ ```
137
+
138
+ **Test cases** live in `test cases/` (`.cmra`) and `test cases cmra_simplified/` (`.cmrasim`).
139
+ **Projects** live in `projects/`: calculator, countdown, fizzbuzz, story_adventure.
140
+
141
+ ---
142
+
143
+ ## ๐Ÿ“ฆ File Inventory
144
+
145
+ | Path | Purpose |
146
+ |------|---------|
147
+ | `src/cmra/` | Installable Python package |
148
+ | `src/cmra/cli.py` | Entry point for the `cmra` command |
149
+ | `src/cmra/fire.py` | Fire Dragon interpreter |
150
+ | `src/cmra/shadow.py` | Shadow Dragon interpreter |
151
+ | `src/cmra/runner.py` | Shared dispatch logic |
152
+ | `cmra.py` | Legacy Fire Dragon single-file |
153
+ | `cmra_simplified.py` | Legacy Shadow Dragon single-file |
154
+ | `prototype.py` | Original workshop prototype |
155
+ | `cmra.ps1` / `cmra.bat` | No-install launchers |
156
+ | `pyproject.toml` | Package metadata (pip install) |
157
+ | `index.html` + `styles.css` | Static docs site |
158
+ | `playground.html` | Browser REPL (Pyodide) |
159
+ | `GUIDE.md` | Full user guide |
160
+ | `keybind.txt` | Fire โ†” Shadow keyword cheatsheet |
161
+ | `projects/` | Showcase programs |
162
+ | `test cases/` | Regression tests (`.cmra`) |
163
+ | `test cases cmra_simplified/` | Regression tests (`.cmrasim`) |
164
+
165
+ ---
166
+
167
+ ## ๐Ÿ“œ License
168
+
169
+ CMRA is a learning project. Use freely, learn deeply, code fiercely! ๐Ÿ”ฅ
170
+
171
+ ---
172
+
173
+ **May your code burn bright and your loops reverse true!** ๐Ÿ‰๐Ÿ”ฅ
cmra-0.1.0/README.md ADDED
@@ -0,0 +1,156 @@
1
+ # ๐Ÿ‰ CMRA Language (Chimera)
2
+
3
+ > **"Where code breathes fire and flows like flame..."**
4
+
5
+ **CMRA** (Chimera) is an esoteric programming language exploring reversible execution and direction control through a dragon-inspired syntax. Programs can soar *up* or dive *down* through code at runtime.
6
+
7
+ ๐Ÿ”ฅ **GitHub:** [TeapotChimera418/cmra](https://github.com/TeapotChimera418/cmra)
8
+ ๐ŸŒ **Playground:** [cmra-esolang.vercel.app/playground.html](https://cmra-esolang.vercel.app/playground.html)
9
+ ๐Ÿ“š **Docs:** [cmra-esolang.vercel.app](https://cmra-esolang.vercel.app/index.html)
10
+ ๐Ÿ“– **User Guide:** [GUIDE.md](GUIDE.md)
11
+
12
+ ---
13
+
14
+ ## ๐Ÿ™Œ Origin
15
+
16
+ CMRA began as a workshop project I attended by
17
+ [Tushar Sadhwani](https://github.com/tusharsadhwani) during a college multifest. The
18
+ prototype we built is in `prototype.py`. For a nonโ€‘aliased baseline, see Tushar's
19
+ [esolangs](https://github.com/tusharsadhwani/esolangs) repository. You can also read his
20
+ blog at [tush.ar](https://tush.ar).
21
+
22
+ This is a tiny side project made for fun and learning. If you build something cool or want
23
+ to improve the interpreter, feel free to reach out on
24
+ [GitHub (@TeapotChimera418)](https://github.com/TeapotChimera418) or Discord [`gamingchimera`](https://discord.com/users/736465046317563915).
25
+
26
+ ## โœจ Try it Online
27
+
28
+ **๐Ÿ”ฅ [Launch the Interactive Playground](https://cmra-esolang.vercel.app/playground.html)**
29
+
30
+ Write and run CMRA code directly in your browser โ€” no installation needed. Powered by Pyodide.
31
+
32
+ ---
33
+
34
+ ## โšก Installation & Quick Start
35
+
36
+ ### pip install (recommended)
37
+
38
+ ```powershell
39
+ # Install from PyPI:
40
+ pip install cmra
41
+
42
+ # Or install from source (development):
43
+ pip install -e .
44
+
45
+ # Now use `cmra` and `cmrash` from anywhere inside the venv:
46
+ cmra "test cases\test.cmra"
47
+ cmrash "test cases\test.cmrash"
48
+ ```
49
+
50
+ ### No-install PowerShell wrapper
51
+
52
+ ```powershell
53
+ .\cmra.ps1 "test cases\test.cmra"
54
+ ```
55
+
56
+ ### Run with Python directly
57
+
58
+ ```powershell
59
+ python -m cmra.cli "test cases\test.cmra"
60
+ python cmra.py "test cases cmra\test.cmra" # legacy single-file
61
+ python cmra_simplified.py "test cases cmra_simplified\test.cmrasim"
62
+ ```
63
+
64
+ ---
65
+
66
+ ## ๐Ÿ”ฑ The Twin Dragons
67
+
68
+ | Dragon | Entry point | Keywords |
69
+ |--------|-------------|----------|
70
+ | ๐Ÿฒ **Fire Dragon** | `cmra.py` / `src/cmra/fire.py` | `bind` `roar` `sniff` `dive` `soar` `murmur` |
71
+ | ๐ŸŒ‘ **Shadow Dragon** | `cmra_simplified.py` / `src/cmra/shadow.py` | `=` `print` `check` `reverse` `;` |
72
+ | ๐Ÿ‰ **Ancient Wyrm** | `prototype.py` | Compact baseline, inline `sniff` only |
73
+
74
+ ---
75
+
76
+ ## ๐Ÿฒ Language at a Glance
77
+
78
+ ```
79
+ murmur Fire Dragon โ€” count 0 to 5
80
+ i bind 0
81
+ flag bind 1
82
+ sniff i <= 5 : sniff flag == 0 : dive
83
+ flag bind 0
84
+ sniff flag == 0 : roar i
85
+ sniff flag == 0 : i bind i + 1
86
+ flag bind 1
87
+ sniff i <= 5 : sniff flag == 1 : soar
88
+ ```
89
+
90
+ - `bind` โ€” assign variable
91
+ - `roar` โ€” print
92
+ - `sniff โ€ฆ : action` โ€” conditional (inline or block)
93
+ - `dive` / `soar` โ€” set execution direction forward / reverse
94
+ - No loop keyword โ€” direction reversal + flag guards make loops
95
+
96
+ **โ†’ See [GUIDE.md](GUIDE.md) for the full walkthrough.**
97
+
98
+ ---
99
+
100
+ ## โšก Execution Model
101
+
102
+ A global `DIRECTION` (`1` or `-1`) steps through lines. `soar` reverses it; `dive` restores forward. When direction goes past the first or last line, the program ends. Block `sniff/check` enters at the first (forward) or last (reverse) line of the block.
103
+
104
+ ---
105
+
106
+ ## ๐ŸŽฎ Test Cases & Projects
107
+
108
+ ```powershell
109
+ # Test cases (pip-installed cmra)
110
+ cmra "test cases\test.cmra"
111
+ cmra "test cases\test_strings.cmra"
112
+ cmra "test cases\test_arith.cmra"
113
+
114
+ # Projects
115
+ cmra projects\calculator.cmra
116
+ cmra projects\countdown.cmra
117
+ cmra projects\fizzbuzz.cmra
118
+ cmra projects\story_adventure.cmra
119
+ ```
120
+
121
+ **Test cases** live in `test cases/` (`.cmra`) and `test cases cmra_simplified/` (`.cmrasim`).
122
+ **Projects** live in `projects/`: calculator, countdown, fizzbuzz, story_adventure.
123
+
124
+ ---
125
+
126
+ ## ๐Ÿ“ฆ File Inventory
127
+
128
+ | Path | Purpose |
129
+ |------|---------|
130
+ | `src/cmra/` | Installable Python package |
131
+ | `src/cmra/cli.py` | Entry point for the `cmra` command |
132
+ | `src/cmra/fire.py` | Fire Dragon interpreter |
133
+ | `src/cmra/shadow.py` | Shadow Dragon interpreter |
134
+ | `src/cmra/runner.py` | Shared dispatch logic |
135
+ | `cmra.py` | Legacy Fire Dragon single-file |
136
+ | `cmra_simplified.py` | Legacy Shadow Dragon single-file |
137
+ | `prototype.py` | Original workshop prototype |
138
+ | `cmra.ps1` / `cmra.bat` | No-install launchers |
139
+ | `pyproject.toml` | Package metadata (pip install) |
140
+ | `index.html` + `styles.css` | Static docs site |
141
+ | `playground.html` | Browser REPL (Pyodide) |
142
+ | `GUIDE.md` | Full user guide |
143
+ | `keybind.txt` | Fire โ†” Shadow keyword cheatsheet |
144
+ | `projects/` | Showcase programs |
145
+ | `test cases/` | Regression tests (`.cmra`) |
146
+ | `test cases cmra_simplified/` | Regression tests (`.cmrasim`) |
147
+
148
+ ---
149
+
150
+ ## ๐Ÿ“œ License
151
+
152
+ CMRA is a learning project. Use freely, learn deeply, code fiercely! ๐Ÿ”ฅ
153
+
154
+ ---
155
+
156
+ **May your code burn bright and your loops reverse true!** ๐Ÿ‰๐Ÿ”ฅ
@@ -0,0 +1,31 @@
1
+ [project]
2
+ name = "cmra"
3
+ version = "0.1.0"
4
+ description = "CMRA โ€” Creative Minimal Recursive Automaton language runtime"
5
+ readme = "README.md"
6
+ requires-python = ">=3.8"
7
+ license = { text = "MIT" }
8
+ authors = [{ name = "TeapotChimera418" }]
9
+ keywords = ["esolang", "esoteric", "programming", "language", "interpreter"]
10
+ classifiers = [
11
+ "Programming Language :: Python :: 3",
12
+ "License :: OSI Approved :: MIT License",
13
+ "Operating System :: OS Independent",
14
+ "Topic :: Software Development :: Interpreters",
15
+ ]
16
+
17
+ [project.urls]
18
+ Homepage = "https://cmra-esolang.vercel.app"
19
+ Repository = "https://github.com/TeapotChimera418/cmra"
20
+ Changelog = "https://github.com/TeapotChimera418/cmra/blob/main/CHANGELOG.md"
21
+
22
+ [project.scripts]
23
+ cmra = "cmra.cli:main"
24
+ cmrash = "cmra.shadow_cli:main"
25
+
26
+ [tool.setuptools.packages.find]
27
+ where = ["src"]
28
+
29
+ [build-system]
30
+ requires = ["setuptools>=61", "wheel"]
31
+ build-backend = "setuptools.build_meta"
cmra-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
cmra-0.1.0/setup.py ADDED
@@ -0,0 +1,2 @@
1
+ from setuptools import setup
2
+ setup()
@@ -0,0 +1,4 @@
1
+ """CMRA โ€” Creative Minimal Recursive Automaton language runtime."""
2
+
3
+ __version__ = "0.1.0"
4
+ __all__ = ["fire", "shadow", "runner", "repl", "cli"]
@@ -0,0 +1,83 @@
1
+ """
2
+ cli.py โ€” Command-line entry point for CMRA
3
+
4
+ Usage:
5
+ cmra <file> # auto-dispatch by extension
6
+ cmra run <file> [--debug] # explicit run
7
+ cmra repl [--mode fire|shadow] # interactive REPL
8
+ """
9
+
10
+ import argparse
11
+ import sys
12
+ import os as _os
13
+
14
+ # โ”€โ”€ Bootstrap: make sure `cmra` package is importable when cli.py is run
15
+ # directly (e.g. `python path/to/cli.py`) rather than via `python -m cmra.cli`
16
+ if "cmra" not in sys.modules:
17
+ _here = _os.path.dirname(_os.path.abspath(__file__)) # โ€ฆ/src/cmra
18
+ _src = _os.path.dirname(_here) # โ€ฆ/src
19
+ if _src not in sys.path:
20
+ sys.path.insert(0, _src)
21
+
22
+ from cmra.runner import dispatch
23
+ from cmra.repl import start_repl
24
+
25
+
26
+ def build_parser() -> argparse.ArgumentParser:
27
+ parser = argparse.ArgumentParser(
28
+ prog="cmra",
29
+ description="CMRA โ€” Creative Minimal Recursive Automaton language runtime",
30
+ )
31
+ subparsers = parser.add_subparsers(dest="command")
32
+
33
+ # -- cmra run <file> --------------------------------------------------
34
+ run_parser = subparsers.add_parser("run", help="Run a CMRA source file")
35
+ run_parser.add_argument("file", help="Path to .cmra or .cmrash source file")
36
+ run_parser.add_argument(
37
+ "--debug", action="store_true", help="Print interpreter debug info"
38
+ )
39
+
40
+ # -- cmra repl --------------------------------------------------------
41
+ repl_parser = subparsers.add_parser("repl", help="Start interactive REPL")
42
+ repl_parser.add_argument(
43
+ "--mode",
44
+ choices=["fire", "shadow"],
45
+ default="fire",
46
+ help="Interpreter mode (default: fire)",
47
+ )
48
+
49
+ return parser
50
+
51
+
52
+ def main() -> None:
53
+ # Allow `cmra <file>` as shorthand (no subcommand)
54
+ if len(sys.argv) == 2 and not sys.argv[1].startswith("-") \
55
+ and sys.argv[1] not in ("run", "repl"):
56
+ # Treat as: cmra run <file>
57
+ try:
58
+ dispatch(sys.argv[1])
59
+ except (FileNotFoundError, ValueError) as e:
60
+ print(f"Error: {e}", file=sys.stderr)
61
+ sys.exit(1)
62
+ return
63
+
64
+ parser = build_parser()
65
+ args = parser.parse_args()
66
+
67
+ if args.command == "run":
68
+ try:
69
+ dispatch(args.file, debug=args.debug)
70
+ except (FileNotFoundError, ValueError) as e:
71
+ print(f"Error: {e}", file=sys.stderr)
72
+ sys.exit(1)
73
+
74
+ elif args.command == "repl":
75
+ start_repl(mode=args.mode)
76
+
77
+ else:
78
+ parser.print_help()
79
+ sys.exit(0)
80
+
81
+
82
+ if __name__ == "__main__":
83
+ main()
@@ -0,0 +1,198 @@
1
+ """
2
+ fire.py โ€” Fire Dragon Interpreter (alias-based syntax)
3
+ Original: cmra.py
4
+
5
+ Exposed API:
6
+ FireInterpreter().run(file_path)
7
+ FireInterpreter().run_source(source)
8
+ run(file_path) # convenience wrapper
9
+ """
10
+
11
+ import re
12
+
13
+ PRECEDENCE = {"<": 0, ">": 0, "<=": 0, ">=": 0, "==": 0, "+": 1, "-": 1, "*": 2, "/": 2}
14
+
15
+
16
+ class FireInterpreter:
17
+ """Stateful Fire Dragon interpreter instance.
18
+
19
+ Each instance has isolated VARIABLES and DIRECTION so multiple
20
+ runs or REPL sessions don't bleed state into each other.
21
+ """
22
+
23
+ def __init__(self):
24
+ self.variables: dict = {}
25
+ self.direction: int = 1
26
+
27
+ # ------------------------------------------------------------------
28
+ # Public API
29
+ # ------------------------------------------------------------------
30
+
31
+ def run(self, file_path: str) -> None:
32
+ """Execute a .cmra file."""
33
+ with open(file_path, encoding="utf-8") as f:
34
+ source = f.read()
35
+ self.run_source(source)
36
+
37
+ def run_source(self, source: str) -> None:
38
+ """Execute CMRA source code given as a string."""
39
+ lines = [self._tokenize(line) for line in source.splitlines()]
40
+ line_index = 0
41
+ while 0 <= line_index < len(lines):
42
+ line = lines[line_index]
43
+ new_index = self._run_line(line, lines, line_index)
44
+ if new_index is not None:
45
+ line_index = new_index + self.direction
46
+ else:
47
+ line_index += self.direction
48
+
49
+ # ------------------------------------------------------------------
50
+ # Internal helpers
51
+ # ------------------------------------------------------------------
52
+
53
+ def _tokenize(self, line: str) -> list:
54
+ parts = re.findall(r'".*?"|\'.*?\'|\S+', line)
55
+ tokens = []
56
+ for item in parts:
57
+ # comment
58
+ if item == "murmur":
59
+ break
60
+ if (item.startswith('"') and item.endswith('"')) or \
61
+ (item.startswith("'") and item.endswith("'")):
62
+ tokens.append(("__str__", item[1:-1]))
63
+ continue
64
+ try:
65
+ tokens.append(float(item))
66
+ except ValueError:
67
+ tokens.append(item)
68
+ return tokens
69
+
70
+ def _evaluate(self, token_list: list):
71
+ values = []
72
+ operators = []
73
+
74
+ def apply_op(v1, op, v2):
75
+ if op == "+" and (isinstance(v1, str) or isinstance(v2, str)):
76
+ return str(v1) + str(v2)
77
+ return eval(f"{repr(v1)} {op} {repr(v2)}")
78
+
79
+ for token in token_list:
80
+ if type(token) == float:
81
+ values.append(token)
82
+ elif isinstance(token, tuple) and token[0] == "__str__":
83
+ values.append(token[1])
84
+ elif token in PRECEDENCE:
85
+ if not operators:
86
+ operators.append(token)
87
+ continue
88
+ prev = operators[-1]
89
+ if PRECEDENCE[prev] >= PRECEDENCE[token]:
90
+ op = operators.pop()
91
+ v2 = values.pop()
92
+ v1 = values.pop()
93
+ values.append(apply_op(v1, op, v2))
94
+ operators.append(token)
95
+ else:
96
+ values.append(self.variables[token])
97
+
98
+ while operators:
99
+ op = operators.pop()
100
+ v2 = values.pop()
101
+ v1 = values.pop()
102
+ values.append(apply_op(v1, op, v2))
103
+
104
+ return values[0]
105
+
106
+ def _run_line(self, line: list, lines: list, line_index: int):
107
+ if not line:
108
+ return None
109
+ if line[0] == "murmur":
110
+ return None
111
+
112
+ # roar <expr> โ†’ print
113
+ if line[0] == "roar":
114
+ print(self._evaluate(line[1:]))
115
+ return None
116
+
117
+ # <var> bind <expr> โ†’ assignment
118
+ if len(line) >= 2 and line[1] == "bind":
119
+ self.variables[line[0]] = self._evaluate(line[2:])
120
+ return None
121
+
122
+ # sniff <condition> [: <action>] โ†’ conditional
123
+ if line[0] == "sniff":
124
+ return self._handle_sniff(line, lines, line_index)
125
+
126
+ # dive / soar โ†’ direction control
127
+ if line[0] == "dive":
128
+ self.direction = 1
129
+ return None
130
+ if line[0] == "soar":
131
+ self.direction = -1
132
+ return None
133
+
134
+ return None
135
+
136
+ def _handle_sniff(self, line: list, lines: list, line_index: int):
137
+ if ":" in line:
138
+ colon_index = line.index(":")
139
+ condition = line[1:colon_index]
140
+ else:
141
+ condition = line[1:]
142
+
143
+ # Inline: sniff <cond> : <action>
144
+ if ":" in line and colon_index < len(line) - 1:
145
+ action = line[colon_index + 1:]
146
+ if self._evaluate(condition):
147
+ self._execute_inline(action)
148
+ return None
149
+
150
+ # Block: sniff <cond>\n{\n ...\n}
151
+ block_start = line_index + 1
152
+ while block_start < len(lines) and "{" not in lines[block_start]:
153
+ block_start += 1
154
+
155
+ block_end = block_start + 1
156
+ brace_count = 1
157
+ while block_end < len(lines) and brace_count > 0:
158
+ if "{" in lines[block_end]:
159
+ brace_count += 1
160
+ elif "}" in lines[block_end]:
161
+ brace_count -= 1
162
+ block_end += 1
163
+
164
+ if self._evaluate(condition):
165
+ first = block_start + 1
166
+ last = block_end - 2
167
+ return first if self.direction >= 0 else last
168
+
169
+ return block_end - 1
170
+
171
+ def _execute_inline(self, tokens: list) -> None:
172
+ if not tokens:
173
+ return
174
+ if tokens[0] == "sniff":
175
+ if ":" in tokens:
176
+ ci = tokens.index(":")
177
+ cond = tokens[1:ci]
178
+ nested = tokens[ci + 1:]
179
+ if self._evaluate(cond):
180
+ self._execute_inline(nested)
181
+ return
182
+ if len(tokens) == 1 and tokens[0] == "dive":
183
+ self.direction = 1
184
+ elif len(tokens) == 1 and tokens[0] == "soar":
185
+ self.direction = -1
186
+ elif len(tokens) >= 2 and tokens[1] == "bind":
187
+ self.variables[tokens[0]] = self._evaluate(tokens[2:])
188
+ elif tokens[0] == "roar":
189
+ print(self._evaluate(tokens[1:]))
190
+
191
+
192
+ # ------------------------------------------------------------------
193
+ # Convenience module-level function
194
+ # ------------------------------------------------------------------
195
+
196
+ def run(file_path: str) -> None:
197
+ """Run a .cmra file with a fresh Fire interpreter."""
198
+ FireInterpreter().run(file_path)
@@ -0,0 +1,59 @@
1
+ """
2
+ repl.py โ€” Interactive REPL for CMRA
3
+
4
+ Usage:
5
+ cmra repl (defaults to Fire Dragon)
6
+ cmra repl --mode fire
7
+ cmra repl --mode shadow
8
+ """
9
+
10
+ from cmra.fire import FireInterpreter
11
+ from cmra.shadow import ShadowInterpreter
12
+
13
+ DRAGON_NAMES = {
14
+ "fire": "Fire Dragon (.cmra syntax)",
15
+ "shadow": "Shadow Dragon (.cmrash syntax)",
16
+ }
17
+
18
+ PROMPTS = {
19
+ "fire": "fire> ",
20
+ "shadow": "shadow> ",
21
+ }
22
+
23
+
24
+ def start_repl(mode: str = "fire") -> None:
25
+ """Start an interactive REPL session.
26
+
27
+ The interpreter instance persists across lines, so variables
28
+ set in one line are accessible in the next.
29
+ """
30
+ mode = mode.lower()
31
+ if mode not in ("fire", "shadow"):
32
+ raise ValueError(f"Unknown mode '{mode}'. Choose 'fire' or 'shadow'.")
33
+
34
+ interpreter = FireInterpreter() if mode == "fire" else ShadowInterpreter()
35
+ prompt = PROMPTS[mode]
36
+
37
+ print(f"CMRA REPL โ€” {DRAGON_NAMES[mode]}")
38
+ print("Type 'exit' or 'quit' to leave, Ctrl-C to abort.\n")
39
+
40
+ while True:
41
+ try:
42
+ line = input(prompt)
43
+ except (EOFError, KeyboardInterrupt):
44
+ print("\nBye!")
45
+ break
46
+
47
+ stripped = line.strip()
48
+ if stripped in ("exit", "quit"):
49
+ print("Bye!")
50
+ break
51
+ if not stripped:
52
+ continue
53
+
54
+ try:
55
+ interpreter.run_source(stripped)
56
+ except KeyError as e:
57
+ print(f" Error: undefined variable {e}")
58
+ except Exception as e:
59
+ print(f" Error: {e}")
@@ -0,0 +1,40 @@
1
+ """
2
+ runner.py โ€” File-type dispatcher for CMRA
3
+
4
+ Detects interpreter based on file extension:
5
+ .cmra โ†’ Fire Dragon
6
+ .cmrash โ†’ Shadow Dragon
7
+ """
8
+
9
+ from pathlib import Path
10
+ from cmra.fire import FireInterpreter
11
+ from cmra.shadow import ShadowInterpreter
12
+
13
+ EXTENSION_MAP = {
14
+ ".cmra": FireInterpreter,
15
+ ".cmrash": ShadowInterpreter,
16
+ }
17
+
18
+
19
+ def dispatch(file_path: str, debug: bool = False) -> None:
20
+ """Run a CMRA source file, choosing the interpreter by extension."""
21
+ path = Path(file_path)
22
+
23
+ if not path.exists():
24
+ raise FileNotFoundError(f"File not found: '{file_path}'")
25
+
26
+ ext = path.suffix.lower()
27
+ interpreter_cls = EXTENSION_MAP.get(ext)
28
+
29
+ if interpreter_cls is None:
30
+ supported = ", ".join(EXTENSION_MAP.keys())
31
+ raise ValueError(
32
+ f"Unknown file type '{ext}'. Expected one of: {supported}"
33
+ )
34
+
35
+ if debug:
36
+ name = "Fire Dragon" if ext == ".cmra" else "Shadow Dragon"
37
+ print(f"[debug] Running with {name} interpreter: {path}")
38
+
39
+ interpreter = interpreter_cls()
40
+ interpreter.run(str(path))
@@ -0,0 +1,183 @@
1
+ """
2
+ shadow.py โ€” Shadow Dragon Interpreter (minimal syntax)
3
+ Original: cmra_simplified.py
4
+
5
+ Exposed API:
6
+ ShadowInterpreter().run(file_path)
7
+ ShadowInterpreter().run_source(source)
8
+ run(file_path) # convenience wrapper
9
+ """
10
+
11
+ import re
12
+
13
+ PRECEDENCE = {"<": 0, ">": 0, "<=": 0, ">=": 0, "==": 0, "+": 1, "-": 1, "*": 2, "/": 2}
14
+
15
+
16
+ class ShadowInterpreter:
17
+ """Stateful Shadow Dragon interpreter instance.
18
+
19
+ Each instance has isolated VARIABLES and DIRECTION so multiple
20
+ runs or REPL sessions don't bleed state into each other.
21
+ """
22
+
23
+ def __init__(self):
24
+ self.variables: dict = {}
25
+ self.direction: int = 1
26
+
27
+ # ------------------------------------------------------------------
28
+ # Public API
29
+ # ------------------------------------------------------------------
30
+
31
+ def run(self, file_path: str) -> None:
32
+ """Execute a .cmrash file."""
33
+ with open(file_path, encoding="utf-8") as f:
34
+ source = f.read()
35
+ self.run_source(source)
36
+
37
+ def run_source(self, source: str) -> None:
38
+ """Execute Shadow source code given as a string."""
39
+ lines = [self._tokenize(line) for line in source.splitlines()]
40
+ line_index = 0
41
+ while 0 <= line_index < len(lines):
42
+ line = lines[line_index]
43
+ new_index = self._run_line(line, lines, line_index)
44
+ if new_index is not None:
45
+ line_index = new_index + self.direction
46
+ else:
47
+ line_index += self.direction
48
+
49
+ # ------------------------------------------------------------------
50
+ # Internal helpers
51
+ # ------------------------------------------------------------------
52
+
53
+ def _tokenize(self, line: str) -> list:
54
+ parts = re.findall(r'".*?"|\'.*?\'|\S+', line)
55
+ tokens = []
56
+ for item in parts:
57
+ if (item.startswith('"') and item.endswith('"')) or \
58
+ (item.startswith("'") and item.endswith("'")):
59
+ tokens.append(("__str__", item[1:-1]))
60
+ continue
61
+ try:
62
+ tokens.append(float(item))
63
+ except ValueError:
64
+ tokens.append(item)
65
+ return tokens
66
+
67
+ def _evaluate(self, token_list: list):
68
+ values = []
69
+ operators = []
70
+
71
+ for token in token_list:
72
+ if type(token) == float:
73
+ values.append(token)
74
+ elif isinstance(token, tuple) and token[0] == "__str__":
75
+ values.append(token[1])
76
+ elif token in PRECEDENCE:
77
+ if not operators:
78
+ operators.append(token)
79
+ continue
80
+ prev = operators[-1]
81
+ if PRECEDENCE[prev] >= PRECEDENCE[token]:
82
+ op = operators.pop()
83
+ v2 = values.pop()
84
+ v1 = values.pop()
85
+ values.append(eval(f"{repr(v1)} {op} {repr(v2)}"))
86
+ operators.append(token)
87
+ else:
88
+ values.append(self.variables[token])
89
+
90
+ while operators:
91
+ op = operators.pop()
92
+ v2 = values.pop()
93
+ v1 = values.pop()
94
+ values.append(eval(f"{repr(v1)} {op} {repr(v2)}"))
95
+
96
+ return values[0]
97
+
98
+ def _run_line(self, line: list, lines: list, line_index: int):
99
+ if not line:
100
+ return None
101
+
102
+ # print <expr>
103
+ if line[0] == "print":
104
+ print(self._evaluate(line[1:]))
105
+ return None
106
+
107
+ # <var> = <expr> โ†’ assignment
108
+ if len(line) >= 2 and line[1] == "=":
109
+ self.variables[line[0]] = self._evaluate(line[2:])
110
+ return None
111
+
112
+ # check <condition> [: <action>] โ†’ conditional
113
+ if line[0] == "check":
114
+ return self._handle_check(line, lines, line_index)
115
+
116
+ # reverse โ†’ flip direction
117
+ if line[0] == "reverse":
118
+ self.direction *= -1
119
+ return None
120
+
121
+ return None
122
+
123
+ def _handle_check(self, line: list, lines: list, line_index: int):
124
+ if ":" in line:
125
+ colon_index = line.index(":")
126
+ condition = line[1:colon_index]
127
+ else:
128
+ condition = line[1:]
129
+
130
+ # Inline: check <cond> : <action>
131
+ if ":" in line and colon_index < len(line) - 1:
132
+ action = line[colon_index + 1:]
133
+ if self._evaluate(condition):
134
+ self._execute_inline(action)
135
+ return None
136
+
137
+ # Block: check <cond>\n{\n ...\n}
138
+ block_start = line_index + 1
139
+ while block_start < len(lines) and "{" not in lines[block_start]:
140
+ block_start += 1
141
+
142
+ block_end = block_start + 1
143
+ brace_count = 1
144
+ while block_end < len(lines) and brace_count > 0:
145
+ if "{" in lines[block_end]:
146
+ brace_count += 1
147
+ elif "}" in lines[block_end]:
148
+ brace_count -= 1
149
+ block_end += 1
150
+
151
+ if self._evaluate(condition):
152
+ first = block_start + 1
153
+ last = block_end - 2
154
+ return first if self.direction >= 0 else last
155
+
156
+ return block_end - 1
157
+
158
+ def _execute_inline(self, tokens: list) -> None:
159
+ if not tokens:
160
+ return
161
+ if tokens[0] == "check":
162
+ if ":" in tokens:
163
+ ci = tokens.index(":")
164
+ cond = tokens[1:ci]
165
+ nested = tokens[ci + 1:]
166
+ if self._evaluate(cond):
167
+ self._execute_inline(nested)
168
+ return
169
+ if len(tokens) == 1 and tokens[0] == "reverse":
170
+ self.direction *= -1
171
+ elif len(tokens) >= 2 and tokens[1] == "=":
172
+ self.variables[tokens[0]] = self._evaluate(tokens[2:])
173
+ elif tokens[0] == "print":
174
+ print(self._evaluate(tokens[1:]))
175
+
176
+
177
+ # ------------------------------------------------------------------
178
+ # Convenience module-level function
179
+ # ------------------------------------------------------------------
180
+
181
+ def run(file_path: str) -> None:
182
+ """Run a .cmrash file with a fresh Shadow interpreter."""
183
+ ShadowInterpreter().run(file_path)
@@ -0,0 +1,68 @@
1
+ """
2
+ shadow_cli.py โ€” Entry point for the `cmrash` command.
3
+
4
+ `cmrash <file>` is a convenience alias that always uses the Shadow Dragon
5
+ interpreter, allowing users to omit the .cmrash extension check.
6
+
7
+ Usage:
8
+ cmrash <file.cmrash>
9
+ cmrash repl
10
+ """
11
+
12
+ import sys
13
+ import os as _os
14
+
15
+ # Bootstrap: allow `python shadow_cli.py` invocation without install
16
+ if "cmra" not in sys.modules:
17
+ _here = _os.path.dirname(_os.path.abspath(__file__))
18
+ _src = _os.path.dirname(_here)
19
+ if _src not in sys.path:
20
+ sys.path.insert(0, _src)
21
+
22
+ import argparse
23
+ from cmra.shadow import ShadowInterpreter
24
+ from cmra.repl import start_repl
25
+
26
+
27
+ def main() -> None:
28
+ # Allow bare: cmrash <file>
29
+ if len(sys.argv) == 2 and not sys.argv[1].startswith("-") \
30
+ and sys.argv[1] != "repl":
31
+ path = sys.argv[1]
32
+ try:
33
+ interp = ShadowInterpreter()
34
+ interp.run(path)
35
+ except FileNotFoundError:
36
+ print(f"Error: File not found: '{path}'", file=sys.stderr)
37
+ sys.exit(1)
38
+ return
39
+
40
+ parser = argparse.ArgumentParser(
41
+ prog="cmrash",
42
+ description="CMRA Shadow Dragon โ€” run .cmrash files",
43
+ )
44
+ subparsers = parser.add_subparsers(dest="command")
45
+
46
+ run_p = subparsers.add_parser("run", help="Run a .cmrash source file")
47
+ run_p.add_argument("file", help="Path to .cmrash source file")
48
+
49
+ subparsers.add_parser("repl", help="Start Shadow Dragon REPL")
50
+
51
+ args = parser.parse_args()
52
+
53
+ if args.command == "run":
54
+ try:
55
+ interp = ShadowInterpreter()
56
+ interp.run(args.file)
57
+ except FileNotFoundError:
58
+ print(f"Error: File not found: '{args.file}'", file=sys.stderr)
59
+ sys.exit(1)
60
+ elif args.command == "repl":
61
+ start_repl(mode="shadow")
62
+ else:
63
+ parser.print_help()
64
+ sys.exit(0)
65
+
66
+
67
+ if __name__ == "__main__":
68
+ main()
@@ -0,0 +1,173 @@
1
+ Metadata-Version: 2.4
2
+ Name: cmra
3
+ Version: 0.1.0
4
+ Summary: CMRA โ€” Creative Minimal Recursive Automaton language runtime
5
+ Author: TeapotChimera418
6
+ License: MIT
7
+ Project-URL: Homepage, https://cmra-esolang.vercel.app
8
+ Project-URL: Repository, https://github.com/TeapotChimera418/cmra
9
+ Project-URL: Changelog, https://github.com/TeapotChimera418/cmra/blob/main/CHANGELOG.md
10
+ Keywords: esolang,esoteric,programming,language,interpreter
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Topic :: Software Development :: Interpreters
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+
18
+ # ๐Ÿ‰ CMRA Language (Chimera)
19
+
20
+ > **"Where code breathes fire and flows like flame..."**
21
+
22
+ **CMRA** (Chimera) is an esoteric programming language exploring reversible execution and direction control through a dragon-inspired syntax. Programs can soar *up* or dive *down* through code at runtime.
23
+
24
+ ๐Ÿ”ฅ **GitHub:** [TeapotChimera418/cmra](https://github.com/TeapotChimera418/cmra)
25
+ ๐ŸŒ **Playground:** [cmra-esolang.vercel.app/playground.html](https://cmra-esolang.vercel.app/playground.html)
26
+ ๐Ÿ“š **Docs:** [cmra-esolang.vercel.app](https://cmra-esolang.vercel.app/index.html)
27
+ ๐Ÿ“– **User Guide:** [GUIDE.md](GUIDE.md)
28
+
29
+ ---
30
+
31
+ ## ๐Ÿ™Œ Origin
32
+
33
+ CMRA began as a workshop project I attended by
34
+ [Tushar Sadhwani](https://github.com/tusharsadhwani) during a college multifest. The
35
+ prototype we built is in `prototype.py`. For a nonโ€‘aliased baseline, see Tushar's
36
+ [esolangs](https://github.com/tusharsadhwani/esolangs) repository. You can also read his
37
+ blog at [tush.ar](https://tush.ar).
38
+
39
+ This is a tiny side project made for fun and learning. If you build something cool or want
40
+ to improve the interpreter, feel free to reach out on
41
+ [GitHub (@TeapotChimera418)](https://github.com/TeapotChimera418) or Discord [`gamingchimera`](https://discord.com/users/736465046317563915).
42
+
43
+ ## โœจ Try it Online
44
+
45
+ **๐Ÿ”ฅ [Launch the Interactive Playground](https://cmra-esolang.vercel.app/playground.html)**
46
+
47
+ Write and run CMRA code directly in your browser โ€” no installation needed. Powered by Pyodide.
48
+
49
+ ---
50
+
51
+ ## โšก Installation & Quick Start
52
+
53
+ ### pip install (recommended)
54
+
55
+ ```powershell
56
+ # Install from PyPI:
57
+ pip install cmra
58
+
59
+ # Or install from source (development):
60
+ pip install -e .
61
+
62
+ # Now use `cmra` and `cmrash` from anywhere inside the venv:
63
+ cmra "test cases\test.cmra"
64
+ cmrash "test cases\test.cmrash"
65
+ ```
66
+
67
+ ### No-install PowerShell wrapper
68
+
69
+ ```powershell
70
+ .\cmra.ps1 "test cases\test.cmra"
71
+ ```
72
+
73
+ ### Run with Python directly
74
+
75
+ ```powershell
76
+ python -m cmra.cli "test cases\test.cmra"
77
+ python cmra.py "test cases cmra\test.cmra" # legacy single-file
78
+ python cmra_simplified.py "test cases cmra_simplified\test.cmrasim"
79
+ ```
80
+
81
+ ---
82
+
83
+ ## ๐Ÿ”ฑ The Twin Dragons
84
+
85
+ | Dragon | Entry point | Keywords |
86
+ |--------|-------------|----------|
87
+ | ๐Ÿฒ **Fire Dragon** | `cmra.py` / `src/cmra/fire.py` | `bind` `roar` `sniff` `dive` `soar` `murmur` |
88
+ | ๐ŸŒ‘ **Shadow Dragon** | `cmra_simplified.py` / `src/cmra/shadow.py` | `=` `print` `check` `reverse` `;` |
89
+ | ๐Ÿ‰ **Ancient Wyrm** | `prototype.py` | Compact baseline, inline `sniff` only |
90
+
91
+ ---
92
+
93
+ ## ๐Ÿฒ Language at a Glance
94
+
95
+ ```
96
+ murmur Fire Dragon โ€” count 0 to 5
97
+ i bind 0
98
+ flag bind 1
99
+ sniff i <= 5 : sniff flag == 0 : dive
100
+ flag bind 0
101
+ sniff flag == 0 : roar i
102
+ sniff flag == 0 : i bind i + 1
103
+ flag bind 1
104
+ sniff i <= 5 : sniff flag == 1 : soar
105
+ ```
106
+
107
+ - `bind` โ€” assign variable
108
+ - `roar` โ€” print
109
+ - `sniff โ€ฆ : action` โ€” conditional (inline or block)
110
+ - `dive` / `soar` โ€” set execution direction forward / reverse
111
+ - No loop keyword โ€” direction reversal + flag guards make loops
112
+
113
+ **โ†’ See [GUIDE.md](GUIDE.md) for the full walkthrough.**
114
+
115
+ ---
116
+
117
+ ## โšก Execution Model
118
+
119
+ A global `DIRECTION` (`1` or `-1`) steps through lines. `soar` reverses it; `dive` restores forward. When direction goes past the first or last line, the program ends. Block `sniff/check` enters at the first (forward) or last (reverse) line of the block.
120
+
121
+ ---
122
+
123
+ ## ๐ŸŽฎ Test Cases & Projects
124
+
125
+ ```powershell
126
+ # Test cases (pip-installed cmra)
127
+ cmra "test cases\test.cmra"
128
+ cmra "test cases\test_strings.cmra"
129
+ cmra "test cases\test_arith.cmra"
130
+
131
+ # Projects
132
+ cmra projects\calculator.cmra
133
+ cmra projects\countdown.cmra
134
+ cmra projects\fizzbuzz.cmra
135
+ cmra projects\story_adventure.cmra
136
+ ```
137
+
138
+ **Test cases** live in `test cases/` (`.cmra`) and `test cases cmra_simplified/` (`.cmrasim`).
139
+ **Projects** live in `projects/`: calculator, countdown, fizzbuzz, story_adventure.
140
+
141
+ ---
142
+
143
+ ## ๐Ÿ“ฆ File Inventory
144
+
145
+ | Path | Purpose |
146
+ |------|---------|
147
+ | `src/cmra/` | Installable Python package |
148
+ | `src/cmra/cli.py` | Entry point for the `cmra` command |
149
+ | `src/cmra/fire.py` | Fire Dragon interpreter |
150
+ | `src/cmra/shadow.py` | Shadow Dragon interpreter |
151
+ | `src/cmra/runner.py` | Shared dispatch logic |
152
+ | `cmra.py` | Legacy Fire Dragon single-file |
153
+ | `cmra_simplified.py` | Legacy Shadow Dragon single-file |
154
+ | `prototype.py` | Original workshop prototype |
155
+ | `cmra.ps1` / `cmra.bat` | No-install launchers |
156
+ | `pyproject.toml` | Package metadata (pip install) |
157
+ | `index.html` + `styles.css` | Static docs site |
158
+ | `playground.html` | Browser REPL (Pyodide) |
159
+ | `GUIDE.md` | Full user guide |
160
+ | `keybind.txt` | Fire โ†” Shadow keyword cheatsheet |
161
+ | `projects/` | Showcase programs |
162
+ | `test cases/` | Regression tests (`.cmra`) |
163
+ | `test cases cmra_simplified/` | Regression tests (`.cmrasim`) |
164
+
165
+ ---
166
+
167
+ ## ๐Ÿ“œ License
168
+
169
+ CMRA is a learning project. Use freely, learn deeply, code fiercely! ๐Ÿ”ฅ
170
+
171
+ ---
172
+
173
+ **May your code burn bright and your loops reverse true!** ๐Ÿ‰๐Ÿ”ฅ
@@ -0,0 +1,15 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ src/cmra/__init__.py
5
+ src/cmra/cli.py
6
+ src/cmra/fire.py
7
+ src/cmra/repl.py
8
+ src/cmra/runner.py
9
+ src/cmra/shadow.py
10
+ src/cmra/shadow_cli.py
11
+ src/cmra.egg-info/PKG-INFO
12
+ src/cmra.egg-info/SOURCES.txt
13
+ src/cmra.egg-info/dependency_links.txt
14
+ src/cmra.egg-info/entry_points.txt
15
+ src/cmra.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ cmra = cmra.cli:main
3
+ cmrash = cmra.shadow_cli:main
@@ -0,0 +1 @@
1
+ cmra