FruitSalad 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.
- fruitsalad-0.1.0/LICENSE +21 -0
- fruitsalad-0.1.0/MANIFEST.in +15 -0
- fruitsalad-0.1.0/PKG-INFO +78 -0
- fruitsalad-0.1.0/README.md +55 -0
- fruitsalad-0.1.0/pyproject.toml +45 -0
- fruitsalad-0.1.0/setup.cfg +4 -0
- fruitsalad-0.1.0/src/FruitSalad/.vscode-extension/fruit-salad-0.1.0.vsix +0 -0
- fruitsalad-0.1.0/src/FruitSalad/.vscode-extension/icons/cucumber.svg +15 -0
- fruitsalad-0.1.0/src/FruitSalad/.vscode-extension/icons/melon.svg +10 -0
- fruitsalad-0.1.0/src/FruitSalad/.vscode-extension/language-configuration.json +8 -0
- fruitsalad-0.1.0/src/FruitSalad/.vscode-extension/package.json +75 -0
- fruitsalad-0.1.0/src/FruitSalad/.vscode-extension/snippets/cucumber.json +61 -0
- fruitsalad-0.1.0/src/FruitSalad/.vscode-extension/syntaxes/cucumber.tmLanguage.json +43 -0
- fruitsalad-0.1.0/src/FruitSalad/__init__.py +3 -0
- fruitsalad-0.1.0/src/FruitSalad/__main__.py +3 -0
- fruitsalad-0.1.0/src/FruitSalad/cli.py +192 -0
- fruitsalad-0.1.0/src/FruitSalad/cucumber.py +104 -0
- fruitsalad-0.1.0/src/FruitSalad/melon.py +74 -0
- fruitsalad-0.1.0/src/FruitSalad/utils.py +90 -0
- fruitsalad-0.1.0/src/FruitSalad.egg-info/PKG-INFO +78 -0
- fruitsalad-0.1.0/src/FruitSalad.egg-info/SOURCES.txt +24 -0
- fruitsalad-0.1.0/src/FruitSalad.egg-info/dependency_links.txt +1 -0
- fruitsalad-0.1.0/src/FruitSalad.egg-info/entry_points.txt +2 -0
- fruitsalad-0.1.0/src/FruitSalad.egg-info/requires.txt +2 -0
- fruitsalad-0.1.0/src/FruitSalad.egg-info/top_level.txt +1 -0
- fruitsalad-0.1.0/tests/test_parsing.py +0 -0
fruitsalad-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
global-exclude *.ts
|
|
2
|
+
global-exclude *.tsbuildinfo
|
|
3
|
+
global-exclude *.map
|
|
4
|
+
prune src/FruitSalad/.vscode-extension/node_modules
|
|
5
|
+
prune src/FruitSalad/.vscode-extension/src
|
|
6
|
+
prune src/FruitSalad/.vscode-extension/out
|
|
7
|
+
recursive-exclude src/FruitSalad/.vscode-extension *.js
|
|
8
|
+
recursive-exclude src/FruitSalad/.vscode-extension *.d.ts
|
|
9
|
+
recursive-exclude src/FruitSalad/.vscode-extension package-lock.json
|
|
10
|
+
recursive-exclude src/FruitSalad/.vscode-extension tsconfig.json
|
|
11
|
+
include src/FruitSalad/.vscode-extension/package.json
|
|
12
|
+
include src/FruitSalad/.vscode-extension/icons/*
|
|
13
|
+
include src/FruitSalad/.vscode-extension/snippets/*
|
|
14
|
+
include src/FruitSalad/.vscode-extension/syntaxes/*
|
|
15
|
+
include src/FruitSalad/.vscode-extension/language-configuration.json
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: FruitSalad
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A fruit salad project
|
|
5
|
+
Author: FruitSalad Contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/your-username/FruitSalad
|
|
8
|
+
Project-URL: Repository, https://github.com/your-username/FruitSalad
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Classifier: Topic :: Utilities
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: blake3
|
|
21
|
+
Requires-Dist: pyyaml
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# FruitSalad
|
|
25
|
+
|
|
26
|
+
A Python toolkit for working with `.cucumber` and `.melon` file formats, with VS Code extension support for syntax highlighting and file icons.
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- **Cucumber format** — Human-readable config files with BLAKE3 integrity verification
|
|
31
|
+
- **Melon format** — Binary serialization using TLV (Type-Length-Value) pattern with BLAKE3 checksums
|
|
32
|
+
- **Conversion** — Convert between `.cucumber`, `.melon`, `.json`, and `.yaml` formats
|
|
33
|
+
- **VS Code extension** — Syntax highlighting, snippets, and file icons for `.cucumber` and `.melon` files
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install FruitSalad
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## CLI Usage
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Convert between formats
|
|
45
|
+
fruitsalad convert input.cucumber output.json
|
|
46
|
+
|
|
47
|
+
# Install VS Code extension and configure file icons
|
|
48
|
+
fruitsalad vscode
|
|
49
|
+
|
|
50
|
+
# Convert specific formats
|
|
51
|
+
fruitsalad to-json input.cucumber
|
|
52
|
+
fruitsalad to-yaml input.cucumber
|
|
53
|
+
fruitsalad to-cucumber input.json
|
|
54
|
+
fruitsalad to-melon input.json
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Python API
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from FruitSalad import load, save, reseal, test_file
|
|
61
|
+
|
|
62
|
+
# Load a .cucumber file
|
|
63
|
+
data = load("config.cucumber")
|
|
64
|
+
print(data)
|
|
65
|
+
|
|
66
|
+
# Save data
|
|
67
|
+
save("output.cucumber", data)
|
|
68
|
+
|
|
69
|
+
# Re-seal after manual edits
|
|
70
|
+
reseal("config.cucumber")
|
|
71
|
+
|
|
72
|
+
# Generate a test file
|
|
73
|
+
test_file("test.cucumber")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
MIT
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# FruitSalad
|
|
2
|
+
|
|
3
|
+
A Python toolkit for working with `.cucumber` and `.melon` file formats, with VS Code extension support for syntax highlighting and file icons.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Cucumber format** — Human-readable config files with BLAKE3 integrity verification
|
|
8
|
+
- **Melon format** — Binary serialization using TLV (Type-Length-Value) pattern with BLAKE3 checksums
|
|
9
|
+
- **Conversion** — Convert between `.cucumber`, `.melon`, `.json`, and `.yaml` formats
|
|
10
|
+
- **VS Code extension** — Syntax highlighting, snippets, and file icons for `.cucumber` and `.melon` files
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install FruitSalad
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## CLI Usage
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Convert between formats
|
|
22
|
+
fruitsalad convert input.cucumber output.json
|
|
23
|
+
|
|
24
|
+
# Install VS Code extension and configure file icons
|
|
25
|
+
fruitsalad vscode
|
|
26
|
+
|
|
27
|
+
# Convert specific formats
|
|
28
|
+
fruitsalad to-json input.cucumber
|
|
29
|
+
fruitsalad to-yaml input.cucumber
|
|
30
|
+
fruitsalad to-cucumber input.json
|
|
31
|
+
fruitsalad to-melon input.json
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Python API
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from FruitSalad import load, save, reseal, test_file
|
|
38
|
+
|
|
39
|
+
# Load a .cucumber file
|
|
40
|
+
data = load("config.cucumber")
|
|
41
|
+
print(data)
|
|
42
|
+
|
|
43
|
+
# Save data
|
|
44
|
+
save("output.cucumber", data)
|
|
45
|
+
|
|
46
|
+
# Re-seal after manual edits
|
|
47
|
+
reseal("config.cucumber")
|
|
48
|
+
|
|
49
|
+
# Generate a test file
|
|
50
|
+
test_file("test.cucumber")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
MIT
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta:__legacy__"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "FruitSalad"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A fruit salad project"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "FruitSalad Contributors"},
|
|
14
|
+
]
|
|
15
|
+
requires-python = ">=3.10"
|
|
16
|
+
dependencies = [
|
|
17
|
+
"blake3",
|
|
18
|
+
"pyyaml",
|
|
19
|
+
]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
28
|
+
"Topic :: Utilities",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/your-username/FruitSalad"
|
|
33
|
+
Repository = "https://github.com/your-username/FruitSalad"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
fruitsalad = "FruitSalad.cli:main"
|
|
37
|
+
|
|
38
|
+
[tool.setuptools]
|
|
39
|
+
include-package-data = true
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.packages.find]
|
|
42
|
+
where = ["src"]
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.package-data]
|
|
45
|
+
FruitSalad = [".vscode-extension/**"]
|
|
Binary file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
|
2
|
+
<g transform="scale(1.7) translate(-21 -21)">
|
|
3
|
+
<circle cx="50" cy="50" r="40" fill="#388e3c" />
|
|
4
|
+
<circle cx="50" cy="50" r="32" fill="#a5d6a7" />
|
|
5
|
+
|
|
6
|
+
<g fill="#1b5e20" opacity="0.8">
|
|
7
|
+
<ellipse cx="50" cy="35" rx="4" ry="7" />
|
|
8
|
+
<ellipse cx="35" cy="50" rx="7" ry="4" />
|
|
9
|
+
<ellipse cx="65" cy="50" rx="7" ry="4" />
|
|
10
|
+
<ellipse cx="50" cy="65" rx="4" ry="7" />
|
|
11
|
+
</g>
|
|
12
|
+
|
|
13
|
+
<circle cx="50" cy="50" r="5" fill="#ffffff" opacity="0.15" />
|
|
14
|
+
</g>
|
|
15
|
+
</svg>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="32 34 36 26">
|
|
2
|
+
<g transform="translate(50, 42) scale(1.7)">
|
|
3
|
+
<path d="M -18 -8 A 22 22 0 0 0 18 -8 L 0 18 Z" fill="#1b5e20"/>
|
|
4
|
+
<path d="M -15 -8 A 19 19 0 0 0 15 -8 L 0 15 Z" fill="#a5d6a7"/>
|
|
5
|
+
<path d="M -12 -8 A 16 16 0 0 0 12 -8 L 0 12 Z" fill="#e53935"/>
|
|
6
|
+
<circle cx="-4" cy="-1" r="1.8" fill="#1a1a1a"/>
|
|
7
|
+
<circle cx="4" cy="-1" r="1.8" fill="#1a1a1a"/>
|
|
8
|
+
<circle cx="0" cy="5" r="1.8" fill="#1a1a1a"/>
|
|
9
|
+
</g>
|
|
10
|
+
</svg>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "FruitSalad",
|
|
3
|
+
"displayName": "FruitSalad",
|
|
4
|
+
"description": "Language support for .cucumber and .melon files — syntax highlighting, auto-complete, BLAKE3 integrity validation, and file icons.",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"publisher": "fruit-salad",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"engines": {
|
|
9
|
+
"vscode": "^1.125.0"
|
|
10
|
+
},
|
|
11
|
+
"categories": [
|
|
12
|
+
"Programming Languages",
|
|
13
|
+
"Snippets",
|
|
14
|
+
"Other"
|
|
15
|
+
],
|
|
16
|
+
"activationEvents": [
|
|
17
|
+
"onLanguage:cucumber"
|
|
18
|
+
],
|
|
19
|
+
"main": "./out/extension.js",
|
|
20
|
+
"contributes": {
|
|
21
|
+
"languages": [
|
|
22
|
+
{
|
|
23
|
+
"id": "cucumber",
|
|
24
|
+
"aliases": [
|
|
25
|
+
"Cucumber",
|
|
26
|
+
"cucumber"
|
|
27
|
+
],
|
|
28
|
+
"extensions": [
|
|
29
|
+
".cucumber"
|
|
30
|
+
],
|
|
31
|
+
"icon": "./icons/cucumber.svg",
|
|
32
|
+
"configuration": "./language-configuration.json"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "melon",
|
|
36
|
+
"aliases": [
|
|
37
|
+
"Melon",
|
|
38
|
+
"melon"
|
|
39
|
+
],
|
|
40
|
+
"extensions": [
|
|
41
|
+
".melon"
|
|
42
|
+
],
|
|
43
|
+
"icon": "./icons/melon.svg"
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"grammars": [
|
|
47
|
+
{
|
|
48
|
+
"language": "cucumber",
|
|
49
|
+
"scopeName": "source.cucumber",
|
|
50
|
+
"path": "./syntaxes/cucumber.tmLanguage.json"
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"snippets": [
|
|
54
|
+
{
|
|
55
|
+
"language": "cucumber",
|
|
56
|
+
"path": "./snippets/cucumber.json"
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"commands": [
|
|
60
|
+
{
|
|
61
|
+
"command": "fruit-salad.verify",
|
|
62
|
+
"title": "FruitSalad: Verify hash"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"command": "fruit-salad.reseal",
|
|
66
|
+
"title": "FruitSalad: Reseal (update hash)"
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@types/node": "^26.0.1",
|
|
72
|
+
"@types/vscode": "^1.125.0",
|
|
73
|
+
"typescript": "^6.0.3"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Header block": {
|
|
3
|
+
"prefix": ["header", "hdr"],
|
|
4
|
+
"body": [
|
|
5
|
+
"--h-- Auto-Generated by FruitSalad --h--",
|
|
6
|
+
"--h-- BLAKE3: $1 --h--",
|
|
7
|
+
"$0"
|
|
8
|
+
],
|
|
9
|
+
"description": "Insert the standard .cucumber header with BLAKE3 hash placeholder"
|
|
10
|
+
},
|
|
11
|
+
"Section block": {
|
|
12
|
+
"prefix": ["section", "sec"],
|
|
13
|
+
"body": [
|
|
14
|
+
"${1:section_name}",
|
|
15
|
+
"\t${2:key} | ${3:value}",
|
|
16
|
+
"$0"
|
|
17
|
+
],
|
|
18
|
+
"description": "Insert a new section block with a key-value pair"
|
|
19
|
+
},
|
|
20
|
+
"Key-value pair": {
|
|
21
|
+
"prefix": ["kv", "keyvalue"],
|
|
22
|
+
"body": [
|
|
23
|
+
"${1:key} | ${2:value}",
|
|
24
|
+
"$0"
|
|
25
|
+
],
|
|
26
|
+
"description": "Insert a key-value pair"
|
|
27
|
+
},
|
|
28
|
+
"Test metadata section": {
|
|
29
|
+
"prefix": ["test_meta", "metadata"],
|
|
30
|
+
"body": [
|
|
31
|
+
"test_meta",
|
|
32
|
+
"\tdate | $CURRENT_YEAR-$CURRENT_MONTH-$CURRENT_DATE",
|
|
33
|
+
"\ttime | $CURRENT_HOUR:$CURRENT_MINUTE:$CURRENT_SECOND",
|
|
34
|
+
"\tdevice | ${1:hostname}",
|
|
35
|
+
"$0"
|
|
36
|
+
],
|
|
37
|
+
"description": "Insert a standard test_meta section"
|
|
38
|
+
},
|
|
39
|
+
"Server config section": {
|
|
40
|
+
"prefix": ["server", "servers"],
|
|
41
|
+
"body": [
|
|
42
|
+
"servers",
|
|
43
|
+
"\t${1:host} | ${2:10.0.0.1}",
|
|
44
|
+
"\t${3:port} | ${4:8080}",
|
|
45
|
+
"\t${5:ssl} | ${6:true}",
|
|
46
|
+
"$0"
|
|
47
|
+
],
|
|
48
|
+
"description": "Insert a server configuration section"
|
|
49
|
+
},
|
|
50
|
+
"Database config section": {
|
|
51
|
+
"prefix": ["database", "db"],
|
|
52
|
+
"body": [
|
|
53
|
+
"database",
|
|
54
|
+
"\t${1:host} | ${2:localhost}",
|
|
55
|
+
"\t${3:port} | ${4:5432}",
|
|
56
|
+
"\t${5:name} | ${6:mydb}",
|
|
57
|
+
"$0"
|
|
58
|
+
],
|
|
59
|
+
"description": "Insert a database configuration section"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scopeName": "source.cucumber",
|
|
3
|
+
"name": "Cucumber",
|
|
4
|
+
"fileTypes": [".cucumber"],
|
|
5
|
+
"patterns": [
|
|
6
|
+
{ "include": "#header" },
|
|
7
|
+
{ "include": "#comments" },
|
|
8
|
+
{ "include": "#key-value" },
|
|
9
|
+
{ "include": "#section" }
|
|
10
|
+
],
|
|
11
|
+
"repository": {
|
|
12
|
+
"header": {
|
|
13
|
+
"match": "^--h--.*--h--$",
|
|
14
|
+
"name": "comment.block.header.cucumber"
|
|
15
|
+
},
|
|
16
|
+
"comments": {
|
|
17
|
+
"match": "^#.*$",
|
|
18
|
+
"name": "comment.line.number-sign.cucumber"
|
|
19
|
+
},
|
|
20
|
+
"section": {
|
|
21
|
+
"match": "^[a-zA-Z_][a-zA-Z0-9_]*$",
|
|
22
|
+
"name": "entity.name.section.cucumber"
|
|
23
|
+
},
|
|
24
|
+
"key-value": {
|
|
25
|
+
"begin": "^([ \\t]*)([a-zA-Z_][a-zA-Z0-9_ ]*)\\s*(\\|)",
|
|
26
|
+
"beginCaptures": {
|
|
27
|
+
"2": { "name": "variable.other.key.cucumber" },
|
|
28
|
+
"3": { "name": "punctuation.separator.key-value.cucumber" }
|
|
29
|
+
},
|
|
30
|
+
"end": "$",
|
|
31
|
+
"patterns": [
|
|
32
|
+
{
|
|
33
|
+
"match": "\\b(true|false)\\b",
|
|
34
|
+
"name": "constant.language.boolean.cucumber"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"match": "\\b\\d+(\\.\\d+)?\\b",
|
|
38
|
+
"name": "constant.numeric.cucumber"
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def find_extension_dir():
|
|
10
|
+
pkg_dir = os.path.dirname(os.path.abspath(__file__))
|
|
11
|
+
ext_dir = os.path.join(pkg_dir, ".vscode-extension")
|
|
12
|
+
if os.path.isdir(ext_dir):
|
|
13
|
+
return ext_dir
|
|
14
|
+
return None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def find_vsix(ext_dir):
|
|
18
|
+
for f in os.listdir(ext_dir):
|
|
19
|
+
if f.endswith(".vsix"):
|
|
20
|
+
return os.path.join(ext_dir, f)
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_user_vscode_settings_path():
|
|
25
|
+
if sys.platform == "win32":
|
|
26
|
+
base = os.environ.get("APPDATA", os.path.join(os.path.expanduser("~"), "AppData", "Roaming"))
|
|
27
|
+
elif sys.platform == "darwin":
|
|
28
|
+
base = os.path.join(os.path.expanduser("~"), "Library", "Application Support")
|
|
29
|
+
else:
|
|
30
|
+
base = os.path.join(os.path.expanduser("~"), ".config")
|
|
31
|
+
return os.path.join(base, "Code", "User", "settings.json")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def install_icons(ext_dir):
|
|
35
|
+
"""Copy icon SVGs to the VS Code extensions icons directory."""
|
|
36
|
+
icons_src = os.path.join(ext_dir, "icons")
|
|
37
|
+
if not os.path.isdir(icons_src):
|
|
38
|
+
print("Warning: icons directory not found in extension", file=sys.stderr)
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
# Target: C:\Users\User\.vscode\extensions\icons\
|
|
42
|
+
vscode_ext_icons = os.path.join(os.path.expanduser("~"), ".vscode", "extensions", "icons")
|
|
43
|
+
os.makedirs(vscode_ext_icons, exist_ok=True)
|
|
44
|
+
|
|
45
|
+
icon_mappings = {
|
|
46
|
+
"cucumber.svg": "fruit-cucumber.svg",
|
|
47
|
+
"melon.svg": "fruit-melon.svg",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for src_name, dst_name in icon_mappings.items():
|
|
51
|
+
src_path = os.path.join(icons_src, src_name)
|
|
52
|
+
dst_path = os.path.join(vscode_ext_icons, dst_name)
|
|
53
|
+
if os.path.exists(src_path):
|
|
54
|
+
shutil.copy2(src_path, dst_path)
|
|
55
|
+
print(f"Installed icon: {dst_path}")
|
|
56
|
+
else:
|
|
57
|
+
print(f"Warning: source icon not found: {src_path}", file=sys.stderr)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def update_user_settings():
|
|
61
|
+
"""Append material-icon-theme.files.associations to the global VS Code user settings."""
|
|
62
|
+
settings_path = get_user_vscode_settings_path()
|
|
63
|
+
if not os.path.exists(settings_path):
|
|
64
|
+
print(f"Warning: user settings not found at {settings_path}", file=sys.stderr)
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
with open(settings_path, "r") as f:
|
|
68
|
+
settings = json.load(f)
|
|
69
|
+
|
|
70
|
+
settings.setdefault("material-icon-theme.files.associations", {}).update({
|
|
71
|
+
"*.cucumber": "../../icons/fruit-cucumber",
|
|
72
|
+
"*.melon": "../../icons/fruit-melon",
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
with open(settings_path, "w") as f:
|
|
76
|
+
json.dump(settings, f, indent=4)
|
|
77
|
+
f.write("\n")
|
|
78
|
+
|
|
79
|
+
print(f"Updated icon settings in global user settings: {settings_path}")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def cmd_vscode(args):
|
|
83
|
+
ext_dir = find_extension_dir()
|
|
84
|
+
if ext_dir is None:
|
|
85
|
+
print("Error: .vscode-extension directory not found", file=sys.stderr)
|
|
86
|
+
sys.exit(1)
|
|
87
|
+
|
|
88
|
+
vsix = find_vsix(ext_dir)
|
|
89
|
+
if vsix is None:
|
|
90
|
+
print("Error: No .vsix file found in .vscode-extension", file=sys.stderr)
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
|
|
93
|
+
print(f"Installing VS Code extension: {vsix}")
|
|
94
|
+
try:
|
|
95
|
+
result = subprocess.run(
|
|
96
|
+
["code", "--install-extension", vsix],
|
|
97
|
+
capture_output=True, text=True, timeout=30
|
|
98
|
+
)
|
|
99
|
+
if result.returncode == 0:
|
|
100
|
+
print(result.stdout.strip() or "Extension installed successfully")
|
|
101
|
+
else:
|
|
102
|
+
print(f"Warning: extension install may have failed:\n{result.stderr}", file=sys.stderr)
|
|
103
|
+
except FileNotFoundError:
|
|
104
|
+
print("Warning: 'code' command not found. Install VS Code and add it to PATH.", file=sys.stderr)
|
|
105
|
+
except subprocess.TimeoutExpired:
|
|
106
|
+
print("Warning: extension install timed out.", file=sys.stderr)
|
|
107
|
+
|
|
108
|
+
# Install icon files to the correct location
|
|
109
|
+
install_icons(ext_dir)
|
|
110
|
+
|
|
111
|
+
# Update global VS Code user settings
|
|
112
|
+
update_user_settings()
|
|
113
|
+
|
|
114
|
+
# Update workspace .vscode/settings.json
|
|
115
|
+
vscode_dir = os.path.join(os.getcwd(), ".vscode")
|
|
116
|
+
os.makedirs(vscode_dir, exist_ok=True)
|
|
117
|
+
|
|
118
|
+
settings_path = os.path.join(vscode_dir, "settings.json")
|
|
119
|
+
settings = {}
|
|
120
|
+
if os.path.exists(settings_path):
|
|
121
|
+
with open(settings_path, "r") as f:
|
|
122
|
+
settings = json.load(f)
|
|
123
|
+
|
|
124
|
+
settings.setdefault("files.associations", {}).update({
|
|
125
|
+
"*.cucumber": "cucumber",
|
|
126
|
+
"*.melon": "melon",
|
|
127
|
+
})
|
|
128
|
+
settings.setdefault("material-icon-theme.files.associations", {}).update({
|
|
129
|
+
"*.cucumber": "../../icons/fruit-cucumber",
|
|
130
|
+
"*.melon": "../../icons/fruit-melon",
|
|
131
|
+
})
|
|
132
|
+
settings.setdefault("material-icon-theme.languages.associations", {}).update({
|
|
133
|
+
"cucumber": "../../icons/fruit-cucumber",
|
|
134
|
+
"melon": "../../icons/fruit-melon",
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
with open(settings_path, "w") as f:
|
|
138
|
+
json.dump(settings, f, indent=2)
|
|
139
|
+
f.write("\n")
|
|
140
|
+
|
|
141
|
+
print(f"Updated icon settings in {settings_path}")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def main():
|
|
145
|
+
parser = argparse.ArgumentParser(prog="fruitsalad", description="FruitSalad CLI")
|
|
146
|
+
sub = parser.add_subparsers(dest="command")
|
|
147
|
+
|
|
148
|
+
vscode_parser = sub.add_parser("vscode", help="Install VS Code extension and set icons")
|
|
149
|
+
vscode_parser.set_defaults(func=cmd_vscode)
|
|
150
|
+
|
|
151
|
+
convert_parser = sub.add_parser("convert", help="Convert between file formats")
|
|
152
|
+
convert_parser.add_argument("in_path", help="Input file path")
|
|
153
|
+
convert_parser.add_argument("out_path", help="Output file path")
|
|
154
|
+
convert_parser.set_defaults(func=lambda a: from_utils("convert", a.in_path, a.out_path))
|
|
155
|
+
|
|
156
|
+
for name, help_text in [
|
|
157
|
+
("to-json", "Convert to JSON"),
|
|
158
|
+
("to-yaml", "Convert to YAML"),
|
|
159
|
+
("to-cucumber", "Convert to .cucumber"),
|
|
160
|
+
("to-melon", "Convert to .melon"),
|
|
161
|
+
]:
|
|
162
|
+
p = sub.add_parser(name, help=help_text)
|
|
163
|
+
p.add_argument("in_path", help="Input file path")
|
|
164
|
+
p.add_argument("out_path", nargs="?", default=None, help="Output file path (optional)")
|
|
165
|
+
p.set_defaults(func=lambda a, _name=name.replace("-", "_"): from_utils(_name, a.in_path, a.out_path))
|
|
166
|
+
|
|
167
|
+
args = parser.parse_args()
|
|
168
|
+
if args.command is None:
|
|
169
|
+
parser.print_help()
|
|
170
|
+
sys.exit(1)
|
|
171
|
+
args.func(args)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def from_utils(func_name, in_path, out_path=None):
|
|
175
|
+
"""Bridge to call utility functions from CLI."""
|
|
176
|
+
from .utils import convert, to_json, to_yaml, to_cucumber, to_melon
|
|
177
|
+
mapping = {
|
|
178
|
+
"convert": convert,
|
|
179
|
+
"to_json": to_json,
|
|
180
|
+
"to_yaml": to_yaml,
|
|
181
|
+
"to_cucumber": to_cucumber,
|
|
182
|
+
"to_melon": to_melon,
|
|
183
|
+
}
|
|
184
|
+
fn = mapping[func_name]
|
|
185
|
+
if out_path is not None:
|
|
186
|
+
fn(in_path, out_path)
|
|
187
|
+
else:
|
|
188
|
+
fn(in_path)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
if __name__ == "__main__":
|
|
192
|
+
main()
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import blake3
|
|
2
|
+
import os
|
|
3
|
+
import datetime
|
|
4
|
+
import platform
|
|
5
|
+
|
|
6
|
+
class CucumberParser:
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self.data = {}
|
|
9
|
+
|
|
10
|
+
def _verify_hash(self, lines):
|
|
11
|
+
header_line = lines[1]
|
|
12
|
+
if "BLAKE3:" not in header_line:
|
|
13
|
+
raise ValueError("Invalid format: Missing BLAKE3 header line.")
|
|
14
|
+
expected_hash = header_line.split("BLAKE3:")[1].split("--h--")[0].strip()
|
|
15
|
+
|
|
16
|
+
content = "".join(lines[2:]).encode('utf-8')
|
|
17
|
+
actual_hash = blake3.blake3(content).hexdigest()
|
|
18
|
+
|
|
19
|
+
if actual_hash != expected_hash:
|
|
20
|
+
raise ValueError(f"Integrity Check Failed! Expected {expected_hash}, got {actual_hash}")
|
|
21
|
+
|
|
22
|
+
def _cast_value(self, val):
|
|
23
|
+
val = val.strip()
|
|
24
|
+
if val.lower() == 'true': return True
|
|
25
|
+
if val.lower() == 'false': return False
|
|
26
|
+
try:
|
|
27
|
+
if '.' in val: return float(val)
|
|
28
|
+
return int(val)
|
|
29
|
+
except ValueError:
|
|
30
|
+
return val
|
|
31
|
+
|
|
32
|
+
def load(self, filepath):
|
|
33
|
+
if not os.path.exists(filepath):
|
|
34
|
+
raise FileNotFoundError(f"Config file {filepath} not found.")
|
|
35
|
+
|
|
36
|
+
with open(filepath, 'r') as f:
|
|
37
|
+
lines = f.readlines()
|
|
38
|
+
|
|
39
|
+
self._verify_hash(lines)
|
|
40
|
+
|
|
41
|
+
current_section = self.data
|
|
42
|
+
for line in lines[2:]:
|
|
43
|
+
line = line.rstrip()
|
|
44
|
+
if not line or line.startswith('#'):
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
if '|' not in line:
|
|
48
|
+
self.data[line.strip()] = {}
|
|
49
|
+
current_section = self.data[line.strip()]
|
|
50
|
+
else:
|
|
51
|
+
key, value = [p.strip() for p in line.split('|', 1)]
|
|
52
|
+
current_section[key] = self._cast_value(value)
|
|
53
|
+
return self.data
|
|
54
|
+
|
|
55
|
+
def save(self, filepath, data):
|
|
56
|
+
lines = ["--h-- Auto-Generated by FruitSalad --h--", "--h-- BLAKE3: PLACEHOLDER --h--"]
|
|
57
|
+
|
|
58
|
+
def format_dict(d, indent=""):
|
|
59
|
+
for k, v in d.items():
|
|
60
|
+
if isinstance(v, dict):
|
|
61
|
+
lines.append(f"{indent}{k}")
|
|
62
|
+
format_dict(v, indent + " ")
|
|
63
|
+
else:
|
|
64
|
+
lines.append(f"{indent}{k} | {v}")
|
|
65
|
+
|
|
66
|
+
format_dict(data)
|
|
67
|
+
|
|
68
|
+
content = "\n".join(lines[2:]) + "\n"
|
|
69
|
+
actual_hash = blake3.blake3(content.encode('utf-8')).hexdigest()
|
|
70
|
+
lines[1] = f"--h-- BLAKE3: {actual_hash} --h--"
|
|
71
|
+
|
|
72
|
+
with open(filepath, 'w') as f:
|
|
73
|
+
f.write("\n".join(lines) + "\n")
|
|
74
|
+
|
|
75
|
+
def reseal(self, filepath):
|
|
76
|
+
"""Re-calculates the BLAKE3 hash for an existing .cucumber file after manual edits."""
|
|
77
|
+
with open(filepath, 'r') as f:
|
|
78
|
+
lines = f.readlines()
|
|
79
|
+
|
|
80
|
+
content = "".join(lines[2:]).encode('utf-8')
|
|
81
|
+
new_hash = blake3.blake3(content).hexdigest()
|
|
82
|
+
|
|
83
|
+
lines[1] = f"--h-- BLAKE3: {new_hash} --h--\n"
|
|
84
|
+
with open(filepath, 'w') as f:
|
|
85
|
+
f.writelines(lines)
|
|
86
|
+
print(f"Re-sealed {filepath} with hash: {new_hash}")
|
|
87
|
+
|
|
88
|
+
def test_file(self, filepath):
|
|
89
|
+
"""Creates a diagnostic file with system metadata."""
|
|
90
|
+
test_data = {
|
|
91
|
+
"test_meta": {
|
|
92
|
+
"date": datetime.datetime.now().strftime("%Y-%m-%d"),
|
|
93
|
+
"time": datetime.datetime.now().strftime("%H:%M:%S"),
|
|
94
|
+
"device": platform.node()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
self.save(filepath, test_data)
|
|
98
|
+
print(f"Test file generated at: {filepath}")
|
|
99
|
+
|
|
100
|
+
# Convenience functions
|
|
101
|
+
def load(filepath): return CucumberParser().load(filepath)
|
|
102
|
+
def save(filepath, data): CucumberParser().save(filepath, data)
|
|
103
|
+
def reseal(filepath): CucumberParser().reseal(filepath)
|
|
104
|
+
def test_file(filepath): CucumberParser().test_file(filepath)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
import blake3
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
# TLV Tag Definitions
|
|
6
|
+
TAG_NODE_ID = 0x01
|
|
7
|
+
TAG_NEIGHBOR = 0x02
|
|
8
|
+
TAG_KSIZE = 0x03
|
|
9
|
+
|
|
10
|
+
class MelonSerializer:
|
|
11
|
+
"""
|
|
12
|
+
Handles binary serialization using TLV pattern:
|
|
13
|
+
[Magic:4][Version:1][Checksum:32][Payload:...]
|
|
14
|
+
"""
|
|
15
|
+
MAGIC = b"MELN"
|
|
16
|
+
VERSION = 1
|
|
17
|
+
|
|
18
|
+
def save(self, filepath, data_dict):
|
|
19
|
+
"""
|
|
20
|
+
Serializes a dictionary into a .melon file.
|
|
21
|
+
Example data_dict: {'node_id': b'...', 'ksize': 20}
|
|
22
|
+
"""
|
|
23
|
+
payload = b""
|
|
24
|
+
|
|
25
|
+
# Packing simple types (Example logic)
|
|
26
|
+
if 'node_id' in data_dict:
|
|
27
|
+
val = data_dict['node_id']
|
|
28
|
+
payload += struct.pack(f"BB{len(val)}s", TAG_NODE_ID, len(val), val)
|
|
29
|
+
|
|
30
|
+
if 'ksize' in data_dict:
|
|
31
|
+
val = data_dict['ksize']
|
|
32
|
+
payload += struct.pack("BBB", TAG_KSIZE, 1, val)
|
|
33
|
+
|
|
34
|
+
# Integrity
|
|
35
|
+
checksum = blake3.blake3(payload).digest()
|
|
36
|
+
header = struct.pack(">4sB32s", self.MAGIC, self.VERSION, checksum)
|
|
37
|
+
|
|
38
|
+
with open(filepath, "wb") as f:
|
|
39
|
+
f.write(header + payload)
|
|
40
|
+
|
|
41
|
+
def load(self, filepath):
|
|
42
|
+
"""Reads and deserializes a .melon file."""
|
|
43
|
+
with open(filepath, "rb") as f:
|
|
44
|
+
header = f.read(37)
|
|
45
|
+
magic, version, stored_checksum = struct.unpack(">4sB32s", header)
|
|
46
|
+
|
|
47
|
+
if magic != self.MAGIC:
|
|
48
|
+
raise ValueError("Not a valid .melon file")
|
|
49
|
+
|
|
50
|
+
payload = f.read()
|
|
51
|
+
|
|
52
|
+
# Verify Integrity
|
|
53
|
+
if blake3.blake3(payload).digest() != stored_checksum:
|
|
54
|
+
raise ValueError("Corrupted .melon file: Hash mismatch!")
|
|
55
|
+
|
|
56
|
+
# Parsing Payload (TLV loop)
|
|
57
|
+
data = {}
|
|
58
|
+
offset = 0
|
|
59
|
+
while offset < len(payload):
|
|
60
|
+
tag = payload[offset]
|
|
61
|
+
length = payload[offset + 1]
|
|
62
|
+
val = payload[offset + 2 : offset + 2 + length]
|
|
63
|
+
|
|
64
|
+
if tag == TAG_NODE_ID:
|
|
65
|
+
data['node_id'] = val
|
|
66
|
+
elif tag == TAG_KSIZE:
|
|
67
|
+
data['ksize'] = struct.unpack("B", val)[0]
|
|
68
|
+
|
|
69
|
+
offset += 2 + length
|
|
70
|
+
return data
|
|
71
|
+
|
|
72
|
+
# Convenience functions
|
|
73
|
+
def save(filepath, data): MelonSerializer().save(filepath, data)
|
|
74
|
+
def load(filepath): return MelonSerializer().load(filepath)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from .cucumber import CucumberParser
|
|
4
|
+
from .melon import MelonSerializer
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
import yaml
|
|
8
|
+
HAS_YAML = True
|
|
9
|
+
except ImportError:
|
|
10
|
+
HAS_YAML = False
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
EXT_FORMAT_MAP = {
|
|
14
|
+
'.cucumber': 'cucumber',
|
|
15
|
+
'.melon': 'melon',
|
|
16
|
+
'.json': 'json',
|
|
17
|
+
'.yaml': 'yaml',
|
|
18
|
+
'.yml': 'yaml',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def read_data(filepath):
|
|
23
|
+
ext = os.path.splitext(filepath)[1].lower()
|
|
24
|
+
if ext == '.cucumber':
|
|
25
|
+
return CucumberParser().load(filepath)
|
|
26
|
+
elif ext == '.melon':
|
|
27
|
+
return MelonSerializer().load(filepath)
|
|
28
|
+
elif ext == '.json':
|
|
29
|
+
with open(filepath, 'r') as f:
|
|
30
|
+
return json.load(f)
|
|
31
|
+
elif ext in ('.yaml', '.yml'):
|
|
32
|
+
if not HAS_YAML:
|
|
33
|
+
raise ImportError("pyyaml is required to read YAML files")
|
|
34
|
+
with open(filepath, 'r') as f:
|
|
35
|
+
return yaml.safe_load(f)
|
|
36
|
+
else:
|
|
37
|
+
raise ValueError(f"Unknown format: {ext}")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def write_data(filepath, data):
|
|
41
|
+
ext = os.path.splitext(filepath)[1].lower()
|
|
42
|
+
if ext == '.cucumber':
|
|
43
|
+
CucumberParser().save(filepath, data)
|
|
44
|
+
elif ext == '.melon':
|
|
45
|
+
MelonSerializer().save(filepath, data)
|
|
46
|
+
elif ext == '.json':
|
|
47
|
+
with open(filepath, 'w') as f:
|
|
48
|
+
json.dump(data, f, indent=2)
|
|
49
|
+
elif ext in ('.yaml', '.yml'):
|
|
50
|
+
if not HAS_YAML:
|
|
51
|
+
raise ImportError("pyyaml is required to write YAML files")
|
|
52
|
+
with open(filepath, 'w') as f:
|
|
53
|
+
yaml.dump(data, f, default_flow_style=False)
|
|
54
|
+
else:
|
|
55
|
+
raise ValueError(f"Unknown format: {ext}")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def convert(in_path, out_path):
|
|
59
|
+
data = read_data(in_path)
|
|
60
|
+
write_data(out_path, data)
|
|
61
|
+
print(f"Converted {in_path} -> {out_path}")
|
|
62
|
+
return data
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def to_json(in_path, out_path=None):
|
|
66
|
+
if out_path is None:
|
|
67
|
+
base, _ = os.path.splitext(in_path)
|
|
68
|
+
out_path = base + '.json'
|
|
69
|
+
return convert(in_path, out_path)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def to_yaml(in_path, out_path=None):
|
|
73
|
+
if out_path is None:
|
|
74
|
+
base, _ = os.path.splitext(in_path)
|
|
75
|
+
out_path = base + '.yaml'
|
|
76
|
+
return convert(in_path, out_path)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def to_cucumber(in_path, out_path=None):
|
|
80
|
+
if out_path is None:
|
|
81
|
+
base, _ = os.path.splitext(in_path)
|
|
82
|
+
out_path = base + '.cucumber'
|
|
83
|
+
return convert(in_path, out_path)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def to_melon(in_path, out_path=None):
|
|
87
|
+
if out_path is None:
|
|
88
|
+
base, _ = os.path.splitext(in_path)
|
|
89
|
+
out_path = base + '.melon'
|
|
90
|
+
return convert(in_path, out_path)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: FruitSalad
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A fruit salad project
|
|
5
|
+
Author: FruitSalad Contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/your-username/FruitSalad
|
|
8
|
+
Project-URL: Repository, https://github.com/your-username/FruitSalad
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Classifier: Topic :: Utilities
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: blake3
|
|
21
|
+
Requires-Dist: pyyaml
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# FruitSalad
|
|
25
|
+
|
|
26
|
+
A Python toolkit for working with `.cucumber` and `.melon` file formats, with VS Code extension support for syntax highlighting and file icons.
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- **Cucumber format** — Human-readable config files with BLAKE3 integrity verification
|
|
31
|
+
- **Melon format** — Binary serialization using TLV (Type-Length-Value) pattern with BLAKE3 checksums
|
|
32
|
+
- **Conversion** — Convert between `.cucumber`, `.melon`, `.json`, and `.yaml` formats
|
|
33
|
+
- **VS Code extension** — Syntax highlighting, snippets, and file icons for `.cucumber` and `.melon` files
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install FruitSalad
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## CLI Usage
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Convert between formats
|
|
45
|
+
fruitsalad convert input.cucumber output.json
|
|
46
|
+
|
|
47
|
+
# Install VS Code extension and configure file icons
|
|
48
|
+
fruitsalad vscode
|
|
49
|
+
|
|
50
|
+
# Convert specific formats
|
|
51
|
+
fruitsalad to-json input.cucumber
|
|
52
|
+
fruitsalad to-yaml input.cucumber
|
|
53
|
+
fruitsalad to-cucumber input.json
|
|
54
|
+
fruitsalad to-melon input.json
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Python API
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from FruitSalad import load, save, reseal, test_file
|
|
61
|
+
|
|
62
|
+
# Load a .cucumber file
|
|
63
|
+
data = load("config.cucumber")
|
|
64
|
+
print(data)
|
|
65
|
+
|
|
66
|
+
# Save data
|
|
67
|
+
save("output.cucumber", data)
|
|
68
|
+
|
|
69
|
+
# Re-seal after manual edits
|
|
70
|
+
reseal("config.cucumber")
|
|
71
|
+
|
|
72
|
+
# Generate a test file
|
|
73
|
+
test_file("test.cucumber")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
MIT
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
src/FruitSalad/__init__.py
|
|
6
|
+
src/FruitSalad/__main__.py
|
|
7
|
+
src/FruitSalad/cli.py
|
|
8
|
+
src/FruitSalad/cucumber.py
|
|
9
|
+
src/FruitSalad/melon.py
|
|
10
|
+
src/FruitSalad/utils.py
|
|
11
|
+
src/FruitSalad.egg-info/PKG-INFO
|
|
12
|
+
src/FruitSalad.egg-info/SOURCES.txt
|
|
13
|
+
src/FruitSalad.egg-info/dependency_links.txt
|
|
14
|
+
src/FruitSalad.egg-info/entry_points.txt
|
|
15
|
+
src/FruitSalad.egg-info/requires.txt
|
|
16
|
+
src/FruitSalad.egg-info/top_level.txt
|
|
17
|
+
src/FruitSalad/.vscode-extension/fruit-salad-0.1.0.vsix
|
|
18
|
+
src/FruitSalad/.vscode-extension/language-configuration.json
|
|
19
|
+
src/FruitSalad/.vscode-extension/package.json
|
|
20
|
+
src/FruitSalad/.vscode-extension/icons/cucumber.svg
|
|
21
|
+
src/FruitSalad/.vscode-extension/icons/melon.svg
|
|
22
|
+
src/FruitSalad/.vscode-extension/snippets/cucumber.json
|
|
23
|
+
src/FruitSalad/.vscode-extension/syntaxes/cucumber.tmLanguage.json
|
|
24
|
+
tests/test_parsing.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
FruitSalad
|
|
File without changes
|