This commit is contained in:
2025-07-21 11:16:51 +02:00
parent b8759d29fb
commit 8282bdf6d5
12 changed files with 659 additions and 13 deletions

View File

@@ -7,17 +7,6 @@ Herolib is an opinionated library primarily used by ThreeFold to automate cloud
> [Complete Documentation](https://freeflowuniverse.github.io/herolib/) > [Complete Documentation](https://freeflowuniverse.github.io/herolib/)
## Table of Contents
- [Installation](#installation)
- [For Users](#for-users)
- [For Developers](#for-developers)
- [Features](#features)
- [Testing](#testing)
- [Contributing](#contributing)
- [Troubleshooting](#troubleshooting)
- [Additional Resources](#additional-resources)
## Installation ## Installation
### For Users ### For Users
@@ -27,6 +16,8 @@ The Hero tool can be installed with a single command:
```bash ```bash
curl https://raw.githubusercontent.com/freeflowuniverse/herolib/refs/heads/development/install_hero.sh > /tmp/install_hero.sh curl https://raw.githubusercontent.com/freeflowuniverse/herolib/refs/heads/development/install_hero.sh > /tmp/install_hero.sh
bash /tmp/install_hero.sh bash /tmp/install_hero.sh
#do not forget to do the following this makes sure vtest and vrun exists
bash install_herolib.vsh
``` ```
Hero will be installed in: Hero will be installed in:

View File

@@ -0,0 +1,141 @@
# Pathlib Module: Advanced Listing and Filtering
The `pathlib` module provides powerful capabilities for listing and filtering files and directories, especially through its `list` method. This document explains how to leverage advanced features like regular expressions and various filtering options.
## Advanced File Listing with `path.list()`
The `path.list()` method allows you to retrieve a `PathList` object containing `Path` objects that match specified criteria.
### `ListArgs` Parameters
The `list` method accepts a `ListArgs` struct to control its behavior:
```v
pub struct ListArgs {
pub mut:
regex []string // A slice of regular expressions to filter files.
recursive bool = true // Whether to list files recursively (default true).
ignoredefault bool = true // Whether to ignore files starting with . and _ (default true).
include_links bool // Whether to include symbolic links in the list.
dirs_only bool // Whether to include only directories in the list.
files_only bool // Whether to include only files in the list.
}
```
### Usage Examples
Here are examples demonstrating how to use these advanced filtering options:
#### 1. Listing Files by Regex Pattern
You can use regular expressions to filter files based on their names or extensions. The `regex` parameter accepts a slice of strings, where each string is a regex pattern.
```v
import freeflowuniverse.herolib.core.pathlib
// Get a directory path
mut dir := pathlib.get('/some/directory')!
// List only Vlang files (ending with .v)
mut vlang_files := dir.list(
regex: [r'.*\.v$']
)!
// List only image files (png, jpg, svg, jpeg)
mut image_files := dir.list(
regex: [r'.*\.png$', r'.*\.jpg$', r'.*\.svg$', r'.*\.jpeg$']
)!
// List files containing "test" in their name (case-insensitive)
mut test_files := dir.list(
regex: [r'(?i).*test.*'] // (?i) makes the regex case-insensitive
)!
for path_obj in vlang_files.paths {
println(path_obj.path)
}
```
#### 2. Controlling Recursion
By default, `list()` is recursive. You can disable recursion to list only items in the current directory.
```v
import freeflowuniverse.herolib.core.pathlib
mut dir := pathlib.get('/some/directory')!
// List only top-level files and directories (non-recursive)
mut top_level_items := dir.list(
recursive: false
)!
for path_obj in top_level_items.paths {
println(path_obj.path)
}
```
#### 3. Including or Excluding Hidden Files
The `ignoredefault` parameter controls whether files and directories starting with `.` or `_` are ignored.
```v
import freeflowuniverse.herolib.core.pathlib
mut dir := pathlib.get('/some/directory')!
// List all files and directories, including hidden ones
mut all_items := dir.list(
ignoredefault: false
)!
for path_obj in all_items.paths {
println(path_obj.path)
}
```
#### 4. Including Symbolic Links
By default, symbolic links are ignored when walking the directory structure. Set `include_links` to `true` to include them.
```v
import freeflowuniverse.herolib.core.pathlib
mut dir := pathlib.get('/some/directory')!
// List files and directories, including symbolic links
mut items_with_links := dir.list(
include_links: true
)!
for path_obj in items_with_links.paths {
println(path_obj.path)
}
```
#### 5. Listing Only Directories or Only Files
Use `dirs_only` or `files_only` to restrict the results to only directories or only files.
```v
import freeflowuniverse.herolib.core.pathlib
mut dir := pathlib.get('/some/directory')!
// List only directories (recursive)
mut only_dirs := dir.list(
dirs_only: true
)!
// List only files (non-recursive)
mut only_files := dir.list(
files_only: true,
recursive: false
)!
for path_obj in only_dirs.paths {
println(path_obj.path)
}
```
By combining these parameters, you can create highly specific and powerful file system listing operations tailored to your needs.

View File

@@ -0,0 +1,204 @@
# Redisclient Module
The `redisclient` module in Herolib provides a comprehensive client for interacting with Redis, supporting various commands, caching, queues, and RPC mechanisms.
## Key Features
- **Direct Redis Commands**: Access to a wide range of Redis commands (strings, hashes, lists, keys, etc.).
- **Caching**: Built-in caching mechanism with namespace support and expiration.
- **Queues**: Simple queue implementation using Redis lists.
- **RPC**: Remote Procedure Call (RPC) functionality over Redis queues for inter-service communication.
## Basic Usage
To get a Redis client instance, use `redisclient.core_get()`. By default, it connects to `127.0.0.1:6379`. You can specify a different address and port using the `RedisURL` struct.
```v
import freeflowuniverse.herolib.core.redisclient
// Connect to default Redis instance (127.0.0.1:6379)
mut redis := redisclient.core_get()!
// Or connect to a specific Redis instance
// mut redis_url := redisclient.RedisURL{address: 'my.redis.server', port: 6380}
// mut redis := redisclient.core_get(redis_url)!
// Example: Set and Get a key
redis.set('mykey', 'myvalue')!
value := redis.get('mykey')!
// assert value == 'myvalue'
// Example: Check if a key exists
exists := redis.exists('mykey')!
// assert exists == true
// Example: Delete a key
redis.del('mykey')!
```
## Redis Commands
The `Redis` object provides methods for most standard Redis commands. Here are some examples:
### String Commands
- `set(key string, value string) !`: Sets the string value of a key.
- `get(key string) !string`: Gets the string value of a key.
- `set_ex(key string, value string, ex string) !`: Sets a key with an expiration time in seconds.
- `incr(key string) !int`: Increments the integer value of a key by one.
- `decr(key string) !int`: Decrements the integer value of a key by one.
- `append(key string, value string) !int`: Appends a value to a key.
- `strlen(key string) !int`: Gets the length of the value stored in a key.
```v
redis.set('counter', '10')!
redis.incr('counter')! // counter is now 11
val := redis.get('counter')! // "11"
```
### Hash Commands
- `hset(key string, skey string, value string) !`: Sets the string value of a hash field.
- `hget(key string, skey string) !string`: Gets the value of a hash field.
- `hgetall(key string) !map[string]string`: Gets all fields and values in a hash.
- `hexists(key string, skey string) !bool`: Checks if a hash field exists.
- `hdel(key string, skey string) !int`: Deletes one or more hash fields.
```v
redis.hset('user:1', 'name', 'John Doe')!
redis.hset('user:1', 'email', 'john@example.com')!
user_name := redis.hget('user:1', 'name')! // "John Doe"
user_data := redis.hgetall('user:1')! // map['name':'John Doe', 'email':'john@example.com']
```
### List Commands
- `lpush(key string, element string) !int`: Inserts all specified values at the head of the list stored at key.
- `rpush(key string, element string) !int`: Inserts all specified values at the tail of the list stored at key.
- `lpop(key string) !string`: Removes and returns the first element of the list stored at key.
- `rpop(key string) !string`: Removes and returns the last element of the list stored at key.
- `llen(key string) !int`: Gets the length of a list.
- `lrange(key string, start int, end int) ![]resp.RValue`: Gets a range of elements from a list.
```v
redis.lpush('mylist', 'item1')!
redis.rpush('mylist', 'item2')!
first_item := redis.lpop('mylist')! // "item1"
```
### Set Commands
- `sadd(key string, members []string) !int`: Adds the specified members to the set stored at key.
- `smismember(key string, members []string) ![]int`: Returns if member is a member of the set stored at key.
```v
redis.sadd('myset', ['member1', 'member2'])!
is_member := redis.smismember('myset', ['member1', 'member3'])! // [1, 0]
```
### Key Management
- `keys(pattern string) ![]string`: Finds all keys matching the given pattern.
- `del(key string) !int`: Deletes a key.
- `expire(key string, seconds int) !int`: Sets a key's time to live in seconds.
- `ttl(key string) !int`: Gets the time to live for a key in seconds.
- `flushall() !`: Deletes all the keys of all the existing databases.
- `flushdb() !`: Deletes all the keys of the currently selected database.
- `selectdb(database int) !`: Changes the selected database.
```v
redis.set('temp_key', 'value')!
redis.expire('temp_key', 60)! // Expires in 60 seconds
```
## Redis Cache
The `RedisCache` struct provides a convenient way to implement caching using Redis.
```v
import freeflowuniverse.herolib.core.redisclient
mut redis := redisclient.core_get()!
mut cache := redis.cache('my_app_cache')
// Set a value in cache with expiration (e.g., 3600 seconds)
cache.set('user:profile:123', '{ "name": "Alice" }', 3600)!
// Get a value from cache
cached_data := cache.get('user:profile:123') or {
// Cache miss, fetch from source
println('Cache miss for user:profile:123')
return
}
// println('Cached data: ${cached_data}')
// Check if a key exists in cache
exists := cache.exists('user:profile:123')
// assert exists == true
// Reset the cache for the namespace
cache.reset()!
```
## Redis Queue
The `RedisQueue` struct provides a simple queue mechanism using Redis lists.
```v
import freeflowuniverse.herolib.core.redisclient
import time
mut redis := redisclient.core_get()!
mut my_queue := redis.queue_get('my_task_queue')
// Add items to the queue
my_queue.add('task1')!
my_queue.add('task2')!
// Get an item from the queue with a timeout (e.g., 1000 milliseconds)
task := my_queue.get(1000)!
// assert task == 'task1'
// Pop an item without timeout (returns error if no item)
task2 := my_queue.pop()!
// assert task2 == 'task2'
```
## Redis RPC
The `RedisRpc` struct enables Remote Procedure Call (RPC) over Redis, allowing services to communicate by sending messages to queues and waiting for responses.
```v
import freeflowuniverse.herolib.core.redisclient
import json
import time
mut redis := redisclient.core_get()!
mut rpc_client := redis.rpc_get('my_rpc_service')
// Define a function to process RPC requests (server-side)
fn my_rpc_processor(cmd string, data string) !string {
// Simulate some processing based on cmd and data
return 'Processed: cmd=${cmd}, data=${data}'
}
// --- Client Side (calling the RPC) ---
// Call the RPC service
response := rpc_client.call(
cmd: 'greet',
data: '{"name": "World"}',
wait: true,
timeout: 5000 // 5 seconds timeout
)!
// println('RPC Response: ${response}')
// assert response == 'Processed: cmd=greet, data={"name": "World"}'
// --- Server Side (processing RPC requests) ---
// In a separate goroutine or process, you would run:
// rpc_client.process(my_rpc_processor, timeout: 0)! // timeout 0 means no timeout, keeps processing
// Example of how to process a single request (for testing/demonstration)
// In a real application, this would be in a loop or a background worker
// return_queue_name := rpc_client.process(my_rpc_processor, timeout: 1000)!
// result := rpc_client.result(1000, return_queue_name)!
// println('Processed result: ${result}')

View File

@@ -0,0 +1,150 @@
# Pathlib Usage Guide
## Overview
The pathlib module provides a comprehensive interface for handling file system operations. Key features include:
- Robust path handling for files, directories, and symlinks
- Support for both absolute and relative paths
- Automatic home directory expansion (~)
- Recursive directory operations
- Path filtering and listing
- File and directory metadata access
## Basic Usage
### Importing pathlib
```v
import freeflowuniverse.herolib.core.pathlib
```
### Creating Path Objects
```v
// Create a Path object for a file
mut file_path := pathlib.get("path/to/file.txt")
// Create a Path object for a directory
mut dir_path := pathlib.get("path/to/directory")
```
### Basic Path Operations
```v
// Get absolute path
abs_path := file_path.absolute()
// Get real path (resolves symlinks)
real_path := file_path.realpath()
// Check if path exists
if file_path.exists() {
// Path exists
}
```
## Path Properties and Methods
### Path Types
```v
// Check if path is a file
if file_path.is_file() {
// Handle as file
}
// Check if path is a directory
if dir_path.is_dir() {
// Handle as directory
}
// Check if path is a symlink
if file_path.is_link() {
// Handle as symlink
}
```
### Path Normalization
```v
// Normalize path (remove extra slashes, resolve . and ..)
normalized_path := file_path.path_normalize()
// Get path directory
dir_path := file_path.path_dir()
// Get path name without extension
name_no_ext := file_path.name_no_ext()
```
## File and Directory Operations
### File Operations
```v
// Write to file
file_path.write("Content to write")!
// Read from file
content := file_path.read()!
// Delete file
file_path.delete()!
```
### Directory Operations
```v
// Create directory
mut dir := pathlib.get_dir(
path: "path/to/new/dir"
create: true
)!
// List directory contents
mut dir_list := dir.list()!
// Delete directory
dir.delete()!
```
### Symlink Operations
```v
// Create symlink
file_path.link("path/to/symlink", delete_exists: true)!
// Resolve symlink
real_path := file_path.realpath()
```
## Advanced Operations
### Path Copying
```v
// Copy file to destination
file_path.copy(dest: "path/to/destination")!
```
### Recursive Operations
```v
// List directory recursively
mut recursive_list := dir.list(recursive: true)!
// Delete directory recursively
dir.delete()!
```
### Path Filtering
```v
// List files matching pattern
mut filtered_list := dir.list(
regex: [r".*\.txt$"],
recursive: true
)!
```
## Best Practices
### Error Handling
```v
if file_path.exists() {
// Safe to operate
} else {
// Handle missing file
}
```

View File

@@ -15,3 +15,4 @@ the files are in ~/code/github/freeflowuniverse/herolib/examples for herolib
- never use fn main() in a .vsh script - never use fn main() in a .vsh script
- always use the top line as in example above - always use the top line as in example above
- these scripts can be executed as is but can also use vrun $pathOfFile

View File

@@ -0,0 +1,11 @@
# Getting the Current Script's Path in Herolib/V Shell
can be used in any .v or .vsh script, easy to find content close to the script itself.
```v
#!/usr/bin/env vsh
const script_path = os.dir(@FILE) + '/scripts'
echo "Current scripts directory: ${script_directory}"
```

View File

@@ -18,8 +18,15 @@ when I generate vlang scripts I will always use .vsh extension and use following
- in .vsh file there is no need for a main() function - in .vsh file there is no need for a main() function
- these scripts can be used for examples or instruction scripts e.g. an installs script - these scripts can be used for examples or instruction scripts e.g. an installs script
## executing vlang scripts
## when creating a test script As AI agent I should also execute v or .vsh scripts with vrun
```bash
vrun ~/code/github/freeflowuniverse/herolib/examples/biztools/bizmodel.vsh
```
## executing test scripts
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 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

View File

@@ -0,0 +1,45 @@
The `compress` module in V provides low-level functionalities for compressing and decompressing byte arrays.
**Functions Overview (Low-Level):**
* **`compress(data []u8, flags int) ![]u8`**: Compresses an array of bytes.
* **`decompress(data []u8, flags int) ![]u8`**: Decompresses an array of bytes.
* **`decompress_with_callback(data []u8, cb ChunkCallback, userdata voidptr, flags int) !u64`**: Decompresses byte arrays using a callback function for chunks.
**Type Definition (Low-Level):**
* **`ChunkCallback`**: A function type `fn (chunk []u8, userdata voidptr) int` used to receive decompressed chunks.
---
**`compress.gzip` Module (High-Level Gzip Operations):**
For high-level gzip compression and decompression, use the `compress.gzip` module. This module provides a more convenient and recommended way to handle gzip operations compared to the low-level `compress` module.
**Key Features of `compress.gzip`:**
* **`compress(data []u8, params CompressParams) ![]u8`**: Compresses data using gzip, allowing specification of `CompressParams` like `compression_level` (0-4095).
* **`decompress(data []u8, params DecompressParams) ![]u8`**: Decompresses gzip-compressed data, allowing specification of `DecompressParams` for verification.
* **`decompress_with_callback(data []u8, cb compr.ChunkCallback, userdata voidptr, params DecompressParams) !int`**: Decompresses gzip data with a callback for chunks, similar to the low-level version but for gzip streams.
* **`validate(data []u8, params DecompressParams) !GzipHeader`**: Validates a gzip header and returns its details.
**Parameter Structures:**
* **`CompressParams`**: Configures compression, primarily `compression_level` (0-4095).
* **`DecompressParams`**: Configures decompression, including `verify_header_checksum`, `verify_length`, and `verify_checksum`.
* **`GzipHeader`**: Represents the structure of a gzip header.
**Inline Code Example (Gzip Compression/Decompression):**
```v
import compress.gzip
data := 'Hello, Gzip!'
compressed := gzip.compress(data.bytes(), compression_level: 4095)!
decompressed := gzip.decompress(compressed)!
// Check if decompressed data matches original
// if data.bytes() == decompressed { ... }
```
**Important Note:** Always prefer `compress.gzip` for general gzip compression/decompression tasks over the low-level `compress` module.

View File

@@ -0,0 +1,12 @@
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.biz.bizmodel
import os
heroscript := os.join_path(os.dir(@FILE), 'examples/complete.heroscript')
// Execute the script and print results
bizmodel.play(heroscript_path:heroscript)!
mut bm := bizmodel.get("threefold")!
bm.sheet.pprint(nr_columns: 25)!

View File

@@ -0,0 +1,52 @@
// Define departments
!!bizmodel.department_define bizname:'threefold' name:'engineering' descr:'Engineering Department'
!!bizmodel.department_define bizname:'threefold' name:'operations' descr:'Operations Department'
!!bizmodel.department_define bizname:'threefold' name:'sales' descr:'Sales Department'
!!bizmodel.department_define bizname:'threefold' name:'admin' descr:'Administrative Department'
// Define engineering team with 10 people growing 10%/year (max 20)
!!bizmodel.employee_define bizname:'threefold' name:'engineering_team' descr:'Engineering Team'
nrpeople:'0:10,12:11,24:12,36:13,48:14,60:15,72:16,84:17,96:18,108:19,120:20'
cost:'5000USD'
indexation:'10%'
department:'engineering'
// Define operations team with 2 people and 4% of revenue
!!bizmodel.employee_define bizname:'threefold' name:'operations_team' descr:'Operations Team'
nrpeople:'2'
cost:'2000USD'
cost_percent_revenue:'4%'
department:'operations'
// Define sales team (placeholder)
!!bizmodel.employee_define bizname:'threefold' name:'sales_team' descr:'Sales Department'
nrpeople:'3'
cost:'4000USD'
department:'sales'
// Define admin team (placeholder)
!!bizmodel.employee_define bizname:'threefold' name:'admin_team' descr:'Administrative Team'
nrpeople:'2'
cost:'3000USD'
department:'admin'
// Define revenue stream
!!bizmodel.revenue_define bizname:'threefold' name:'cloud_services'
descr:'Cloud Services Revenue'
revenue:'10:10000,36:1000000'
extrapolate:1
// 5% annual growth after 3 years (36 months)
// Manually define growth for 5 years: 1.05^1, 1.05^2, etc.
// This is a simplified representation
// Actual implementation would need precise month:amount pairs
// Define travel cost at 3% of revenue
!!bizmodel.cost_define bizname:'threefold' name:'travel_cost'
descr:'Travel Expenses'
cost_percent_revenue:'3%'
// Define initial funding
!!bizmodel.funding_define bizname:'threefold' name:'initial_funding'
descr:'Initial Funding'
investment:'0:5000000'
type:'capital'

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env -S v -cg -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib...
import compress.gzip
// Define some sample data to compress
data := 'Hello, VLang Gzip Compression Example! This is some sample text to be compressed and then decompressed.'
println('Original Data: "${data}"')
println('Original Data Length: ${data.len}')
// Compress the data using gzip
// Using a compression_level of 4095 for best compression
compressed_data := gzip.compress(data.bytes(), compression_level: 4095)!
println('Compressed Data Length: ${compressed_data.len}')
// Decompress the data
decompressed_data := gzip.decompress(compressed_data)!
println('Decompressed Data: "${decompressed_data.string()}"')
println('Decompressed Data Length: ${decompressed_data.len}')
// Verify if the decompressed data matches the original data
if data.bytes() == decompressed_data {
println('Compression and decompression successful! Data matches.')
} else {
println('Error: Decompressed data does not match original data.')
}

View File

@@ -68,7 +68,10 @@ addtoscript('alias vtest=', 'alias vtest=\'v -stats -enable-globals -show-c-outp
eprintln('Failed to add vtest alias: ${err}') eprintln('Failed to add vtest alias: ${err}')
} }
//alias vtest='v -gc none -stats -enable-globals -show-c-output -keepc -n -w -cg -o /tmp/tester.c -g -cc tcc test' // Add vrun alias
addtoscript('alias vrun=', 'alias vrun=\'v -stats -enable-globals -show-c-output -n -w -cg -gc none -cc tcc run\' ') or {
eprintln('Failed to add vrun alias: ${err}')
}
addtoscript('HOME/hero/bin', 'export PATH="\$PATH:\$HOME/hero/bin"') or { addtoscript('HOME/hero/bin', 'export PATH="\$PATH:\$HOME/hero/bin"') or {
eprintln('Failed to add path to hero, ${err}') eprintln('Failed to add path to hero, ${err}')