add virt
This commit is contained in:
204
aiprompts/ai_core/core_heroscript.md
Normal file
204
aiprompts/ai_core/core_heroscript.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# instructions how to work with heroscript in vlang
|
||||
|
||||
## heroscript
|
||||
|
||||
Heroscript is our small scripting language which has following structure
|
||||
|
||||
an example of a heroscript is
|
||||
|
||||
```heroscript
|
||||
|
||||
!!mailclient.configure
|
||||
name: 'myname'
|
||||
host: 'localhost'
|
||||
port: 25
|
||||
secure: 1
|
||||
reset: 1
|
||||
description: '
|
||||
a description can be multiline
|
||||
|
||||
like this
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
Notice how:
|
||||
- every action starts with !!
|
||||
- the first part is the actor, mailclient in this case
|
||||
- the 2e part is the action name, configure in this case
|
||||
- multilines are supported see the description field
|
||||
|
||||
## how to process heroscript in Vlang
|
||||
|
||||
- heroscript can be converted to a struct,
|
||||
- the methods available to get the params are in 'params' section further in this doc
|
||||
|
||||
|
||||
```vlang
|
||||
//the object which will be configured
|
||||
pub struct mailclient {
|
||||
pub mut:
|
||||
name string
|
||||
host string
|
||||
port int
|
||||
secure bool
|
||||
description string
|
||||
}
|
||||
|
||||
mut plbook := playbook.new(text: $the_heroscript_from_above)!
|
||||
play_mailclient(mut plbook)! //see below in vlang block there it all happens
|
||||
|
||||
pub fn play_mailclient(mut plbook playbook.PlayBook) ! {
|
||||
|
||||
//find all actions are !!$actor.$actionname. in this case above the actor is !!mailclient, we check with the fitler if it exists, if not we return
|
||||
mailclient_actions := plbook.find(filter: 'mailclient.')!
|
||||
for action in mailclient_actions {
|
||||
if action.name == "configure"{
|
||||
mut p := action.params
|
||||
mut obj := mailclientScript{
|
||||
//INFO: all details about the get methods can be found in 'params get methods' section
|
||||
name : p.get('name')! //will give error if not exist
|
||||
homedir : p.get('homedir')!
|
||||
title : p.get_default('title', 'My Hero DAG')! //uses a default if not set
|
||||
reset : p.get_default_false('reset')
|
||||
start : p.get_default_true('start')
|
||||
colors : p.get_list('colors')
|
||||
description : p.get_default('description','')!
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
## params get methods (param getters)
|
||||
|
||||
above in the p.get...
|
||||
|
||||
below you can find the methods which can be used on the params
|
||||
|
||||
```vlang
|
||||
|
||||
exists(key_ string) bool
|
||||
|
||||
//check if arg exist (arg is just a value in the string e.g. red, not value:something)
|
||||
exists_arg(key_ string) bool
|
||||
|
||||
//see if the kwarg with the key exists if yes return as string trimmed
|
||||
get(key_ string) !string
|
||||
|
||||
//return the arg with nr, 0 is the first
|
||||
get_arg(nr int) !string
|
||||
|
||||
//return arg, if the nr is larger than amount of args, will return the defval
|
||||
get_arg_default(nr int, defval string) !string
|
||||
|
||||
get_default(key string, defval string) !string
|
||||
|
||||
get_default_false(key string) bool
|
||||
|
||||
get_default_true(key string) bool
|
||||
|
||||
get_float(key string) !f64
|
||||
|
||||
get_float_default(key string, defval f64) !f64
|
||||
|
||||
get_from_hashmap(key_ string, defval string, hashmap map[string]string) !string
|
||||
|
||||
get_int(key string) !int
|
||||
|
||||
get_int_default(key string, defval int) !int
|
||||
|
||||
//Looks for a list of strings in the parameters. ',' are used as deliminator to list
|
||||
get_list(key string) ![]string
|
||||
|
||||
get_list_default(key string, def []string) ![]string
|
||||
|
||||
get_list_f32(key string) ![]f32
|
||||
|
||||
get_list_f32_default(key string, def []f32) []f32
|
||||
|
||||
get_list_f64(key string) ![]f64
|
||||
|
||||
get_list_f64_default(key string, def []f64) []f64
|
||||
|
||||
get_list_i16(key string) ![]i16
|
||||
|
||||
get_list_i16_default(key string, def []i16) []i16
|
||||
|
||||
get_list_i64(key string) ![]i64
|
||||
|
||||
get_list_i64_default(key string, def []i64) []i64
|
||||
|
||||
get_list_i8(key string) ![]i8
|
||||
|
||||
get_list_i8_default(key string, def []i8) []i8
|
||||
|
||||
get_list_int(key string) ![]int
|
||||
|
||||
get_list_int_default(key string, def []int) []int
|
||||
|
||||
get_list_namefix(key string) ![]string
|
||||
|
||||
get_list_namefix_default(key string, def []string) ![]string
|
||||
|
||||
get_list_u16(key string) ![]u16
|
||||
|
||||
get_list_u16_default(key string, def []u16) []u16
|
||||
|
||||
get_list_u32(key string) ![]u32
|
||||
|
||||
get_list_u32_default(key string, def []u32) []u32
|
||||
|
||||
get_list_u64(key string) ![]u64
|
||||
|
||||
get_list_u64_default(key string, def []u64) []u64
|
||||
|
||||
get_list_u8(key string) ![]u8
|
||||
|
||||
get_list_u8_default(key string, def []u8) []u8
|
||||
|
||||
get_map() map[string]string
|
||||
|
||||
get_path(key string) !string
|
||||
|
||||
get_path_create(key string) !string
|
||||
|
||||
get_percentage(key string) !f64
|
||||
|
||||
get_percentage_default(key string, defval string) !f64
|
||||
|
||||
//convert GB, MB, KB to bytes e.g. 10 GB becomes bytes in u64
|
||||
get_storagecapacity_in_bytes(key string) !u64
|
||||
|
||||
get_storagecapacity_in_bytes_default(key string, defval u64) !u64
|
||||
|
||||
get_storagecapacity_in_gigabytes(key string) !u64
|
||||
|
||||
//Get Expiration object from time string input input can be either relative or absolute## Relative time
|
||||
get_time(key string) !ourtime.OurTime
|
||||
|
||||
get_time_default(key string, defval ourtime.OurTime) !ourtime.OurTime
|
||||
|
||||
get_time_interval(key string) !Duration
|
||||
|
||||
get_timestamp(key string) !Duration
|
||||
|
||||
get_timestamp_default(key string, defval Duration) !Duration
|
||||
|
||||
get_u32(key string) !u32
|
||||
|
||||
get_u32_default(key string, defval u32) !u32
|
||||
|
||||
get_u64(key string) !u64
|
||||
|
||||
get_u64_default(key string, defval u64) !u64
|
||||
|
||||
get_u8(key string) !u8
|
||||
|
||||
get_u8_default(key string, defval u8) !u8
|
||||
|
||||
```
|
||||
136
aiprompts/ai_core/core_params.md
Normal file
136
aiprompts/ai_core/core_params.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# how to use params
|
||||
|
||||
works very well in combination with heroscript
|
||||
|
||||
## How to get the paramsparser
|
||||
|
||||
```v
|
||||
import freeflowuniverse.crystallib.data.paramsparser
|
||||
|
||||
// Create new params from text
|
||||
params := paramsparser.new("color:red size:'large' priority:1 enable:true")!
|
||||
|
||||
// Or create empty params and add later
|
||||
mut params := paramsparser.new_params()
|
||||
params.set("color", "red")
|
||||
```
|
||||
|
||||
## Parameter Format
|
||||
|
||||
The parser supports several formats:
|
||||
|
||||
1. Key-value pairs: `key:value`
|
||||
2. Quoted values: `key:'value with spaces'`
|
||||
3. Arguments without keys: `arg1 arg2`
|
||||
4. Comments: `// this is a comment`
|
||||
|
||||
Example:
|
||||
```v
|
||||
text := "name:'John Doe' age:30 active:true // user details"
|
||||
params := paramsparser.new(text)!
|
||||
```
|
||||
|
||||
## Getting Values
|
||||
|
||||
The module provides various methods to retrieve values:
|
||||
|
||||
```v
|
||||
// Get string value
|
||||
name := params.get("name")! // returns "John Doe"
|
||||
|
||||
// Get with default value
|
||||
color := params.get_default("color", "blue")! // returns "blue" if color not set
|
||||
|
||||
// Get as integer
|
||||
age := params.get_int("age")! // returns 30
|
||||
|
||||
// Get as boolean (true if value is "1", "true", "y", "yes")
|
||||
is_active := params.get_default_true("active")
|
||||
|
||||
// Get as float
|
||||
score := params.get_float("score")!
|
||||
|
||||
// Get as percentage (converts "80%" to 0.8)
|
||||
progress := params.get_percentage("progress")!
|
||||
```
|
||||
|
||||
## Type Conversion Methods
|
||||
|
||||
The module supports various type conversions:
|
||||
|
||||
### Basic Types
|
||||
- `get_int()`: Convert to int32
|
||||
- `get_u32()`: Convert to unsigned 32-bit integer
|
||||
- `get_u64()`: Convert to unsigned 64-bit integer
|
||||
- `get_u8()`: Convert to unsigned 8-bit integer
|
||||
- `get_float()`: Convert to 64-bit float
|
||||
- `get_percentage()`: Convert percentage string to float (e.g., "80%" → 0.8)
|
||||
|
||||
### Boolean Values
|
||||
- `get_default_true()`: Returns true if value is empty, "1", "true", "y", or "yes"
|
||||
- `get_default_false()`: Returns false if value is empty, "0", "false", "n", or "no"
|
||||
|
||||
### Lists
|
||||
The module provides robust support for parsing and converting lists:
|
||||
|
||||
```v
|
||||
// Basic list parsing
|
||||
names := params.get_list("users")! // parses ["user1", "user2", "user3"]
|
||||
|
||||
// With default value
|
||||
tags := params.get_list_default("tags", ["default"])!
|
||||
|
||||
// Lists with type conversion
|
||||
numbers := params.get_list_int("ids")! // converts each item to int
|
||||
amounts := params.get_list_f64("prices")! // converts each item to f64
|
||||
|
||||
// Name-fixed lists (normalizes each item)
|
||||
clean_names := params.get_list_namefix("categories")!
|
||||
```
|
||||
|
||||
Supported list types:
|
||||
- `get_list()`: String list
|
||||
- `get_list_u8()`, `get_list_u16()`, `get_list_u32()`, `get_list_u64()`: Unsigned integers
|
||||
- `get_list_i8()`, `get_list_i16()`, `get_list_int()`, `get_list_i64()`: Signed integers
|
||||
- `get_list_f32()`, `get_list_f64()`: Floating point numbers
|
||||
|
||||
Each list method has a corresponding `_default` version that accepts a default value.
|
||||
|
||||
Valid list formats:
|
||||
```v
|
||||
users: "john, jane,bob"
|
||||
ids: "1,2,3,4,5"
|
||||
```
|
||||
|
||||
### Advanced
|
||||
|
||||
```v
|
||||
get_map() map[string]string
|
||||
|
||||
get_path(key string) !string
|
||||
|
||||
get_path_create(key string) !string //will create path if it doesnt exist yet
|
||||
|
||||
get_percentage(key string) !f64
|
||||
|
||||
get_percentage_default(key string, defval string) !f64
|
||||
|
||||
//convert GB, MB, KB to bytes e.g. 10 GB becomes bytes in u64
|
||||
get_storagecapacity_in_bytes(key string) !u64
|
||||
|
||||
get_storagecapacity_in_bytes_default(key string, defval u64) !u64
|
||||
|
||||
get_storagecapacity_in_gigabytes(key string) !u64
|
||||
|
||||
//Get Expiration object from time string input input can be either relative or absolute## Relative time
|
||||
get_time(key string) !ourtime.OurTime
|
||||
|
||||
get_time_default(key string, defval ourtime.OurTime) !ourtime.OurTime
|
||||
|
||||
get_time_interval(key string) !Duration
|
||||
|
||||
get_timestamp(key string) !Duration
|
||||
|
||||
get_timestamp_default(key string, defval Duration) !Duration
|
||||
|
||||
```
|
||||
340
aiprompts/ai_core/datatypes.md
Normal file
340
aiprompts/ai_core/datatypes.md
Normal file
@@ -0,0 +1,340 @@
|
||||
module datatypes
|
||||
|
||||
# datatypes
|
||||
|
||||
This module provides implementations of less frequently used, but still common data types.
|
||||
|
||||
V's `builtin` module is imported implicitly, and has implementations for arrays, maps and strings. These are good for many applications, but there are a plethora of other useful data structures/containers, like linked lists, priority queues, trees, etc, that allow for algorithms with different time complexities, which may be more suitable for your specific application.
|
||||
|
||||
It is implemented using generics, that you have to specialise for the type of your actual elements. For example:
|
||||
```v
|
||||
import datatypes
|
||||
|
||||
mut stack := datatypes.Stack[int]{}
|
||||
stack.push(1)
|
||||
println(stack)
|
||||
```
|
||||
|
||||
## Currently Implemented Datatypes:
|
||||
|
||||
- [x] Linked list
|
||||
- [x] Doubly linked list
|
||||
- [x] Stack (LIFO)
|
||||
- [x] Queue (FIFO)
|
||||
- [x] Min heap (priority queue)
|
||||
- [x] Set
|
||||
- [x] Quadtree
|
||||
- [x] Bloom filter
|
||||
- [ ] ...
|
||||
|
||||
|
||||
fn new_bloom_filter[T](hash_func fn (T) u32, table_size int, num_functions int) !&BloomFilter[T]
|
||||
new_bloom_filter creates a new bloom_filter. `table_size` should be greater than 0, and `num_functions` should be 1~16.
|
||||
fn new_bloom_filter_fast[T](hash_func fn (T) u32) &BloomFilter[T]
|
||||
new_bloom_filter_fast creates a new bloom_filter. `table_size` is 16384, and `num_functions` is 4.
|
||||
fn new_ringbuffer[T](s int) RingBuffer[T]
|
||||
new_ringbuffer creates an empty ring buffer of size `s`.
|
||||
fn (mut bst BSTree[T]) insert(value T) bool
|
||||
insert give the possibility to insert an element in the BST.
|
||||
fn (bst &BSTree[T]) contains(value T) bool
|
||||
contains checks if an element with a given `value` is inside the BST.
|
||||
fn (mut bst BSTree[T]) remove(value T) bool
|
||||
remove removes an element with `value` from the BST.
|
||||
fn (bst &BSTree[T]) is_empty() bool
|
||||
is_empty checks if the BST is empty
|
||||
fn (bst &BSTree[T]) in_order_traversal() []T
|
||||
in_order_traversal traverses the BST in order, and returns the result as an array.
|
||||
fn (bst &BSTree[T]) post_order_traversal() []T
|
||||
post_order_traversal traverses the BST in post order, and returns the result in an array.
|
||||
fn (bst &BSTree[T]) pre_order_traversal() []T
|
||||
pre_order_traversal traverses the BST in pre order, and returns the result as an array.
|
||||
fn (bst &BSTree[T]) to_left(value T) !T
|
||||
to_left returns the value of the node to the left of the node with `value` specified if it exists, otherwise the a false value is returned.
|
||||
|
||||
An example of usage can be the following one
|
||||
```v
|
||||
left_value, exist := bst.to_left(10)
|
||||
```
|
||||
fn (bst &BSTree[T]) to_right(value T) !T
|
||||
to_right return the value of the element to the right of the node with `value` specified, if exist otherwise, the boolean value is false An example of usage can be the following one
|
||||
|
||||
```v
|
||||
left_value, exist := bst.to_right(10)
|
||||
```
|
||||
fn (bst &BSTree[T]) max() !T
|
||||
max return the max element inside the BST. Time complexity O(N) if the BST is not balanced
|
||||
fn (bst &BSTree[T]) min() !T
|
||||
min return the minimum element in the BST. Time complexity O(N) if the BST is not balanced.
|
||||
fn (mut b BloomFilter[T]) add(element T)
|
||||
adds the element to bloom filter.
|
||||
fn (b &BloomFilter[T]) exists(element T) bool
|
||||
checks the element is exists.
|
||||
fn (l &BloomFilter[T]) @union(r &BloomFilter[T]) !&BloomFilter[T]
|
||||
@union returns the union of the two bloom filters.
|
||||
fn (l &BloomFilter[T]) intersection(r &BloomFilter[T]) !&BloomFilter[T]
|
||||
intersection returns the intersection of bloom filters.
|
||||
fn (list DoublyLinkedList[T]) is_empty() bool
|
||||
is_empty checks if the linked list is empty
|
||||
fn (list DoublyLinkedList[T]) len() int
|
||||
len returns the length of the linked list
|
||||
fn (list DoublyLinkedList[T]) first() !T
|
||||
first returns the first element of the linked list
|
||||
fn (list DoublyLinkedList[T]) last() !T
|
||||
last returns the last element of the linked list
|
||||
fn (mut list DoublyLinkedList[T]) push_back(item T)
|
||||
push_back adds an element to the end of the linked list
|
||||
fn (mut list DoublyLinkedList[T]) push_front(item T)
|
||||
push_front adds an element to the beginning of the linked list
|
||||
fn (mut list DoublyLinkedList[T]) push_many(elements []T, direction Direction)
|
||||
push_many adds array of elements to the beginning of the linked list
|
||||
fn (mut list DoublyLinkedList[T]) pop_back() !T
|
||||
pop_back removes the last element of the linked list
|
||||
fn (mut list DoublyLinkedList[T]) pop_front() !T
|
||||
pop_front removes the last element of the linked list
|
||||
fn (mut list DoublyLinkedList[T]) insert(idx int, item T) !
|
||||
insert adds an element to the linked list at the given index
|
||||
fn (list &DoublyLinkedList[T]) index(item T) !int
|
||||
index searches the linked list for item and returns the forward index or none if not found.
|
||||
fn (mut list DoublyLinkedList[T]) delete(idx int)
|
||||
delete removes index idx from the linked list and is safe to call for any idx.
|
||||
fn (list DoublyLinkedList[T]) str() string
|
||||
str returns a string representation of the linked list
|
||||
fn (list DoublyLinkedList[T]) array() []T
|
||||
array returns a array representation of the linked list
|
||||
fn (mut list DoublyLinkedList[T]) next() ?T
|
||||
next implements the iter interface to use DoublyLinkedList with V's `for x in list {` loop syntax.
|
||||
fn (mut list DoublyLinkedList[T]) iterator() DoublyListIter[T]
|
||||
iterator returns a new iterator instance for the `list`.
|
||||
fn (mut list DoublyLinkedList[T]) back_iterator() DoublyListIterBack[T]
|
||||
back_iterator returns a new backwards iterator instance for the `list`.
|
||||
fn (mut iter DoublyListIterBack[T]) next() ?T
|
||||
next returns *the previous* element of the list, or `none` when the start of the list is reached. It is called by V's `for x in iter{` on each iteration.
|
||||
fn (mut iter DoublyListIter[T]) next() ?T
|
||||
next returns *the next* element of the list, or `none` when the end of the list is reached. It is called by V's `for x in iter{` on each iteration.
|
||||
fn (list LinkedList[T]) is_empty() bool
|
||||
is_empty checks if the linked list is empty
|
||||
fn (list LinkedList[T]) len() int
|
||||
len returns the length of the linked list
|
||||
fn (list LinkedList[T]) first() !T
|
||||
first returns the first element of the linked list
|
||||
fn (list LinkedList[T]) last() !T
|
||||
last returns the last element of the linked list
|
||||
fn (list LinkedList[T]) index(idx int) !T
|
||||
index returns the element at the given index of the linked list
|
||||
fn (mut list LinkedList[T]) push(item T)
|
||||
push adds an element to the end of the linked list
|
||||
fn (mut list LinkedList[T]) push_many(elements []T)
|
||||
push adds an array of elements to the end of the linked list
|
||||
fn (mut list LinkedList[T]) pop() !T
|
||||
pop removes the last element of the linked list
|
||||
fn (mut list LinkedList[T]) shift() !T
|
||||
shift removes the first element of the linked list
|
||||
fn (mut list LinkedList[T]) insert(idx int, item T) !
|
||||
insert adds an element to the linked list at the given index
|
||||
fn (mut list LinkedList[T]) prepend(item T)
|
||||
prepend adds an element to the beginning of the linked list (equivalent to insert(0, item))
|
||||
fn (list LinkedList[T]) str() string
|
||||
str returns a string representation of the linked list
|
||||
fn (list LinkedList[T]) array() []T
|
||||
array returns a array representation of the linked list
|
||||
fn (mut list LinkedList[T]) next() ?T
|
||||
next implements the iteration interface to use LinkedList with V's `for` loop syntax.
|
||||
fn (mut list LinkedList[T]) iterator() ListIter[T]
|
||||
iterator returns a new iterator instance for the `list`.
|
||||
fn (mut iter ListIter[T]) next() ?T
|
||||
next returns the next element of the list, or `none` when the end of the list is reached. It is called by V's `for x in iter{` on each iteration.
|
||||
fn (mut heap MinHeap[T]) insert(item T)
|
||||
insert adds an element to the heap.
|
||||
fn (mut heap MinHeap[T]) insert_many(elements []T)
|
||||
insert array of elements to the heap.
|
||||
fn (mut heap MinHeap[T]) pop() !T
|
||||
pop removes the top-most element from the heap.
|
||||
fn (heap MinHeap[T]) peek() !T
|
||||
peek gets the top-most element from the heap without removing it.
|
||||
fn (heap MinHeap[T]) len() int
|
||||
len returns the number of elements in the heap.
|
||||
fn (queue Queue[T]) is_empty() bool
|
||||
is_empty checks if the queue is empty
|
||||
fn (queue Queue[T]) len() int
|
||||
len returns the length of the queue
|
||||
fn (queue Queue[T]) peek() !T
|
||||
peek returns the head of the queue (first element added)
|
||||
fn (queue Queue[T]) last() !T
|
||||
last returns the tail of the queue (last element added)
|
||||
fn (queue Queue[T]) index(idx int) !T
|
||||
index returns the element at the given index of the queue
|
||||
fn (mut queue Queue[T]) push(item T)
|
||||
push adds an element to the tail of the queue
|
||||
fn (mut queue Queue[T]) pop() !T
|
||||
pop removes the element at the head of the queue and returns it
|
||||
fn (queue Queue[T]) str() string
|
||||
str returns a string representation of the queue
|
||||
fn (queue Queue[T]) array() []T
|
||||
array returns a array representation of the queue
|
||||
fn (mut rb RingBuffer[T]) push(element T) !
|
||||
push adds an element to the ring buffer.
|
||||
fn (mut rb RingBuffer[T]) pop() !T
|
||||
pop returns the oldest element in the buffer.
|
||||
fn (mut rb RingBuffer[T]) push_many(elements []T) !
|
||||
push_many pushes an array to the buffer.
|
||||
fn (mut rb RingBuffer[T]) pop_many(n u64) ![]T
|
||||
pop_many returns `n` elements of the buffer starting with the oldest one.
|
||||
fn (rb RingBuffer[T]) is_empty() bool
|
||||
is_empty returns `true` if the ring buffer is empty, `false` otherwise.
|
||||
fn (rb RingBuffer[T]) is_full() bool
|
||||
is_full returns `true` if the ring buffer is full, `false` otherwise.
|
||||
fn (rb RingBuffer[T]) capacity() int
|
||||
capacity returns the capacity of the ring buffer.
|
||||
fn (mut rb RingBuffer[T]) clear()
|
||||
clear empties the ring buffer and all pushed elements.
|
||||
fn (rb RingBuffer[T]) occupied() int
|
||||
occupied returns the occupied capacity of the buffer.
|
||||
fn (rb RingBuffer[T]) remaining() int
|
||||
remaining returns the remaining capacity of the buffer.
|
||||
fn (set Set[T]) exists(element T) bool
|
||||
checks the element is exists.
|
||||
fn (mut set Set[T]) add(element T)
|
||||
adds the element to set, if it is not present already.
|
||||
fn (mut set Set[T]) remove(element T)
|
||||
removes the element from set.
|
||||
fn (set Set[T]) pick() !T
|
||||
pick returns an arbitrary element of set, if set is not empty.
|
||||
fn (mut set Set[T]) rest() ![]T
|
||||
rest returns the set consisting of all elements except for the arbitrary element.
|
||||
fn (mut set Set[T]) pop() !T
|
||||
pop returns an arbitrary element and deleting it from set.
|
||||
fn (mut set Set[T]) clear()
|
||||
delete all elements of set.
|
||||
fn (l Set[T]) == (r Set[T]) bool
|
||||
== checks whether the two given sets are equal (i.e. contain all and only the same elements).
|
||||
fn (set Set[T]) is_empty() bool
|
||||
is_empty checks whether the set is empty or not.
|
||||
fn (set Set[T]) size() int
|
||||
size returns the number of elements in the set.
|
||||
fn (set Set[T]) copy() Set[T]
|
||||
copy returns a copy of all the elements in the set.
|
||||
fn (mut set Set[T]) add_all(elements []T)
|
||||
add_all adds the whole `elements` array to the set
|
||||
fn (l Set[T]) @union(r Set[T]) Set[T]
|
||||
@union returns the union of the two sets.
|
||||
fn (l Set[T]) intersection(r Set[T]) Set[T]
|
||||
intersection returns the intersection of sets.
|
||||
fn (l Set[T]) - (r Set[T]) Set[T]
|
||||
- returns the difference of sets.
|
||||
fn (l Set[T]) subset(r Set[T]) bool
|
||||
subset returns true if the set `r` is a subset of the set `l`.
|
||||
fn (stack Stack[T]) is_empty() bool
|
||||
is_empty checks if the stack is empty
|
||||
fn (stack Stack[T]) len() int
|
||||
len returns the length of the stack
|
||||
fn (stack Stack[T]) peek() !T
|
||||
peek returns the top of the stack
|
||||
fn (mut stack Stack[T]) push(item T)
|
||||
push adds an element to the top of the stack
|
||||
fn (mut stack Stack[T]) pop() !T
|
||||
pop removes the element at the top of the stack and returns it
|
||||
fn (stack Stack[T]) str() string
|
||||
str returns a string representation of the stack
|
||||
fn (stack Stack[T]) array() []T
|
||||
array returns a array representation of the stack
|
||||
enum Direction {
|
||||
front
|
||||
back
|
||||
}
|
||||
struct AABB {
|
||||
pub mut:
|
||||
x f64
|
||||
y f64
|
||||
width f64
|
||||
height f64
|
||||
}
|
||||
struct BSTree[T] {
|
||||
mut:
|
||||
root &BSTreeNode[T] = unsafe { 0 }
|
||||
}
|
||||
Pure Binary Seach Tree implementation
|
||||
|
||||
Pure V implementation of the Binary Search Tree Time complexity of main operation O(log N) Space complexity O(N)
|
||||
struct DoublyLinkedList[T] {
|
||||
mut:
|
||||
head &DoublyListNode[T] = unsafe { 0 }
|
||||
tail &DoublyListNode[T] = unsafe { 0 }
|
||||
// Internal iter pointer for allowing safe modification
|
||||
// of the list while iterating. TODO: use an option
|
||||
// instead of a pointer to determine it is initialized.
|
||||
iter &DoublyListIter[T] = unsafe { 0 }
|
||||
len int
|
||||
}
|
||||
DoublyLinkedList[T] represents a generic doubly linked list of elements, each of type T.
|
||||
struct DoublyListIter[T] {
|
||||
mut:
|
||||
node &DoublyListNode[T] = unsafe { 0 }
|
||||
}
|
||||
DoublyListIter[T] is an iterator for DoublyLinkedList. It starts from *the start* and moves forwards to *the end* of the list. It can be used with V's `for x in iter {` construct. One list can have multiple independent iterators, pointing to different positions/places in the list. A DoublyListIter iterator instance always traverses the list from *start to finish*.
|
||||
struct DoublyListIterBack[T] {
|
||||
mut:
|
||||
node &DoublyListNode[T] = unsafe { 0 }
|
||||
}
|
||||
DoublyListIterBack[T] is an iterator for DoublyLinkedList. It starts from *the end* and moves backwards to *the start* of the list. It can be used with V's `for x in iter {` construct. One list can have multiple independent iterators, pointing to different positions/places in the list. A DoublyListIterBack iterator instance always traverses the list from *finish to start*.
|
||||
struct LinkedList[T] {
|
||||
mut:
|
||||
head &ListNode[T] = unsafe { 0 }
|
||||
len int
|
||||
// Internal iter pointer for allowing safe modification
|
||||
// of the list while iterating. TODO: use an option
|
||||
// instead of a pointer to determine if it is initialized.
|
||||
iter &ListIter[T] = unsafe { 0 }
|
||||
}
|
||||
struct ListIter[T] {
|
||||
mut:
|
||||
node &ListNode[T] = unsafe { 0 }
|
||||
}
|
||||
ListIter[T] is an iterator for LinkedList. It can be used with V's `for x in iter {` construct. One list can have multiple independent iterators, pointing to different positions/places in the list. An iterator instance always traverses the list from start to finish.
|
||||
struct ListNode[T] {
|
||||
mut:
|
||||
data T
|
||||
next &ListNode[T] = unsafe { 0 }
|
||||
}
|
||||
struct MinHeap[T] {
|
||||
mut:
|
||||
data []T
|
||||
}
|
||||
MinHeap is a binary minimum heap data structure.
|
||||
struct Quadtree {
|
||||
pub mut:
|
||||
perimeter AABB
|
||||
capacity int
|
||||
depth int
|
||||
level int
|
||||
particles []AABB
|
||||
nodes []Quadtree
|
||||
}
|
||||
fn (mut q Quadtree) create(x f64, y f64, width f64, height f64, capacity int, depth int, level int) Quadtree
|
||||
create returns a new configurable root node for the tree.
|
||||
fn (mut q Quadtree) insert(p AABB)
|
||||
insert recursively adds a particle in the correct index of the tree.
|
||||
fn (mut q Quadtree) retrieve(p AABB) []AABB
|
||||
retrieve recursively checks if a particle is in a specific index of the tree.
|
||||
fn (mut q Quadtree) clear()
|
||||
clear flushes out nodes and particles from the tree.
|
||||
fn (q Quadtree) get_nodes() []Quadtree
|
||||
get_nodes recursively returns the subdivisions the tree has.
|
||||
struct Queue[T] {
|
||||
mut:
|
||||
elements LinkedList[T]
|
||||
}
|
||||
struct RingBuffer[T] {
|
||||
mut:
|
||||
reader int // index of the tail where data is going to be read
|
||||
writer int // index of the head where data is going to be written
|
||||
content []T
|
||||
}
|
||||
RingBuffer represents a ring buffer also known as a circular buffer.
|
||||
struct Set[T] {
|
||||
mut:
|
||||
elements map[T]u8
|
||||
}
|
||||
struct Stack[T] {
|
||||
mut:
|
||||
elements []T
|
||||
}
|
||||
309
aiprompts/ai_core/heroscript & params instructions.md
Normal file
309
aiprompts/ai_core/heroscript & params instructions.md
Normal file
@@ -0,0 +1,309 @@
|
||||
# how to work with heroscript in vlang
|
||||
|
||||
## heroscript
|
||||
|
||||
Heroscript is our small scripting language which has following structure
|
||||
|
||||
an example of a heroscript is
|
||||
|
||||
```heroscript
|
||||
|
||||
!!dagu.script_define
|
||||
name: 'test_dag'
|
||||
homedir:''
|
||||
title:'a title'
|
||||
reset:1
|
||||
start:true //trie or 1 is same
|
||||
colors: 'green,red,purple' //lists are comma separated
|
||||
description: '
|
||||
a description can be multiline
|
||||
|
||||
like this
|
||||
'
|
||||
|
||||
|
||||
!!dagu.add_step
|
||||
dag: 'test_dag'
|
||||
name: 'hello_world'
|
||||
command: 'echo hello world'
|
||||
|
||||
!!dagu.add_step
|
||||
dag: 'test_dag'
|
||||
name: 'last_step'
|
||||
command: 'echo last step'
|
||||
|
||||
|
||||
```
|
||||
|
||||
Notice how:
|
||||
- every action starts with !!
|
||||
- the first part is the actor e.g. dagu in this case
|
||||
- the 2e part is the action name
|
||||
- multilines are supported see the description field
|
||||
|
||||
## how to process heroscript in Vlang
|
||||
|
||||
- heroscript can be converted to a struct,
|
||||
- the methods available to get the params are in 'params' section further in this doc
|
||||
|
||||
|
||||
```vlang
|
||||
|
||||
fn test_play_dagu() ! {
|
||||
mut plbook := playbook.new(text: thetext_from_above)!
|
||||
play_dagu(mut plbook)! //see below in vlang block there it all happens
|
||||
}
|
||||
|
||||
|
||||
pub fn play_dagu(mut plbook playbook.PlayBook) ! {
|
||||
|
||||
//find all actions are !!$actor.$actionname. in this case above the actor is !!dagu, we check with the fitler if it exists, if not we return
|
||||
dagu_actions := plbook.find(filter: 'dagu.')!
|
||||
if dagu_actions.len == 0 {
|
||||
return
|
||||
}
|
||||
play_dagu_basic(mut plbook)!
|
||||
}
|
||||
|
||||
pub struct DaguScript {
|
||||
pub mut:
|
||||
name string
|
||||
homedir string
|
||||
title string
|
||||
reset bool
|
||||
start bool
|
||||
colors []string
|
||||
}
|
||||
|
||||
// play_dagu plays the dagu play commands
|
||||
pub fn play_dagu_basic(mut plbook playbook.PlayBook) ! {
|
||||
|
||||
//now find the specific ones for dagu.script_define
|
||||
mut actions := plbook.find(filter: 'dagu.script_define')!
|
||||
|
||||
if actions.len > 0 {
|
||||
for myaction in actions {
|
||||
mut p := myaction.params //get the params object from the action object, this can then be processed using the param getters
|
||||
mut obj := DaguScript{
|
||||
//INFO: all details about the get methods can be found in 'params get methods' section
|
||||
name : p.get('name')! //will give error if not exist
|
||||
homedir : p.get('homedir')!
|
||||
title : p.get_default('title', 'My Hero DAG')! //uses a default if not set
|
||||
reset : p.get_default_false('reset')
|
||||
start : p.get_default_true('start')
|
||||
colors : p.get_list('colors')
|
||||
description : p.get_default('description','')!
|
||||
}
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
//there can be more actions which will have other filter
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## params get methods (param getters)
|
||||
|
||||
```vlang
|
||||
|
||||
fn (params &Params) exists(key_ string) bool
|
||||
|
||||
//check if arg exist (arg is just a value in the string e.g. red, not value:something)
|
||||
fn (params &Params) exists_arg(key_ string) bool
|
||||
|
||||
//see if the kwarg with the key exists if yes return as string trimmed
|
||||
fn (params &Params) get(key_ string) !string
|
||||
|
||||
//return the arg with nr, 0 is the first
|
||||
fn (params &Params) get_arg(nr int) !string
|
||||
|
||||
//return arg, if the nr is larger than amount of args, will return the defval
|
||||
fn (params &Params) get_arg_default(nr int, defval string) !string
|
||||
|
||||
fn (params &Params) get_default(key string, defval string) !string
|
||||
|
||||
fn (params &Params) get_default_false(key string) bool
|
||||
|
||||
fn (params &Params) get_default_true(key string) bool
|
||||
|
||||
fn (params &Params) get_float(key string) !f64
|
||||
|
||||
fn (params &Params) get_float_default(key string, defval f64) !f64
|
||||
|
||||
fn (params &Params) get_from_hashmap(key_ string, defval string, hashmap map[string]string) !string
|
||||
|
||||
fn (params &Params) get_int(key string) !int
|
||||
|
||||
fn (params &Params) get_int_default(key string, defval int) !int
|
||||
|
||||
//Looks for a list of strings in the parameters. ',' are used as deliminator to list
|
||||
fn (params &Params) get_list(key string) ![]string
|
||||
|
||||
fn (params &Params) get_list_default(key string, def []string) ![]string
|
||||
|
||||
fn (params &Params) get_list_f32(key string) ![]f32
|
||||
|
||||
fn (params &Params) get_list_f32_default(key string, def []f32) []f32
|
||||
|
||||
fn (params &Params) get_list_f64(key string) ![]f64
|
||||
|
||||
fn (params &Params) get_list_f64_default(key string, def []f64) []f64
|
||||
|
||||
fn (params &Params) get_list_i16(key string) ![]i16
|
||||
|
||||
fn (params &Params) get_list_i16_default(key string, def []i16) []i16
|
||||
|
||||
fn (params &Params) get_list_i64(key string) ![]i64
|
||||
|
||||
fn (params &Params) get_list_i64_default(key string, def []i64) []i64
|
||||
|
||||
fn (params &Params) get_list_i8(key string) ![]i8
|
||||
|
||||
fn (params &Params) get_list_i8_default(key string, def []i8) []i8
|
||||
|
||||
fn (params &Params) get_list_int(key string) ![]int
|
||||
|
||||
fn (params &Params) get_list_int_default(key string, def []int) []int
|
||||
|
||||
fn (params &Params) get_list_namefix(key string) ![]string
|
||||
|
||||
fn (params &Params) get_list_namefix_default(key string, def []string) ![]string
|
||||
|
||||
fn (params &Params) get_list_u16(key string) ![]u16
|
||||
|
||||
fn (params &Params) get_list_u16_default(key string, def []u16) []u16
|
||||
|
||||
fn (params &Params) get_list_u32(key string) ![]u32
|
||||
|
||||
fn (params &Params) get_list_u32_default(key string, def []u32) []u32
|
||||
|
||||
fn (params &Params) get_list_u64(key string) ![]u64
|
||||
|
||||
fn (params &Params) get_list_u64_default(key string, def []u64) []u64
|
||||
|
||||
fn (params &Params) get_list_u8(key string) ![]u8
|
||||
|
||||
fn (params &Params) get_list_u8_default(key string, def []u8) []u8
|
||||
|
||||
fn (params &Params) get_map() map[string]string
|
||||
|
||||
fn (params &Params) get_path(key string) !string
|
||||
|
||||
fn (params &Params) get_path_create(key string) !string
|
||||
|
||||
fn (params &Params) get_percentage(key string) !f64
|
||||
|
||||
fn (params &Params) get_percentage_default(key string, defval string) !f64
|
||||
|
||||
//convert GB, MB, KB to bytes e.g. 10 GB becomes bytes in u64
|
||||
fn (params &Params) get_storagecapacity_in_bytes(key string) !u64
|
||||
|
||||
fn (params &Params) get_storagecapacity_in_bytes_default(key string, defval u64) !u64
|
||||
|
||||
fn (params &Params) get_storagecapacity_in_gigabytes(key string) !u64
|
||||
|
||||
//Get Expiration object from time string input input can be either relative or absolute## Relative time
|
||||
fn (params &Params) get_time(key string) !ourtime.OurTime
|
||||
|
||||
fn (params &Params) get_time_default(key string, defval ourtime.OurTime) !ourtime.OurTime
|
||||
|
||||
fn (params &Params) get_time_interval(key string) !Duration
|
||||
|
||||
fn (params &Params) get_timestamp(key string) !Duration
|
||||
|
||||
fn (params &Params) get_timestamp_default(key string, defval Duration) !Duration
|
||||
|
||||
fn (params &Params) get_u32(key string) !u32
|
||||
|
||||
fn (params &Params) get_u32_default(key string, defval u32) !u32
|
||||
|
||||
fn (params &Params) get_u64(key string) !u64
|
||||
|
||||
fn (params &Params) get_u64_default(key string, defval u64) !u64
|
||||
|
||||
fn (params &Params) get_u8(key string) !u8
|
||||
|
||||
fn (params &Params) get_u8_default(key string, defval u8) !u8
|
||||
|
||||
```
|
||||
|
||||
## how internally a heroscript gets parsed for params
|
||||
|
||||
- example to show how a heroscript gets parsed in action with params
|
||||
- params are part of action object
|
||||
|
||||
```heroscript
|
||||
example text to parse (heroscript)
|
||||
|
||||
id:a1 name6:aaaaa
|
||||
name:'need to do something 1'
|
||||
description:
|
||||
'
|
||||
## markdown works in it
|
||||
description can be multiline
|
||||
lets see what happens
|
||||
|
||||
- a
|
||||
- something else
|
||||
|
||||
### subtitle
|
||||
'
|
||||
|
||||
name2: test
|
||||
name3: hi
|
||||
name10:'this is with space' name11:aaa11
|
||||
|
||||
name4: 'aaa'
|
||||
|
||||
//somecomment
|
||||
name5: 'aab'
|
||||
```
|
||||
|
||||
the params are part of the action and are represented as follow for the above:
|
||||
|
||||
```vlang
|
||||
Params{
|
||||
params: [Param{
|
||||
key: 'id'
|
||||
value: 'a1'
|
||||
}, Param{
|
||||
key: 'name6'
|
||||
value: 'aaaaa'
|
||||
}, Param{
|
||||
key: 'name'
|
||||
value: 'need to do something 1'
|
||||
}, Param{
|
||||
key: 'description'
|
||||
value: '## markdown works in it
|
||||
|
||||
description can be multiline
|
||||
lets see what happens
|
||||
|
||||
- a
|
||||
- something else
|
||||
|
||||
### subtitle
|
||||
'
|
||||
}, Param{
|
||||
key: 'name2'
|
||||
value: 'test'
|
||||
}, Param{
|
||||
key: 'name3'
|
||||
value: 'hi'
|
||||
}, Param{
|
||||
key: 'name10'
|
||||
value: 'this is with space'
|
||||
}, Param{
|
||||
key: 'name11'
|
||||
value: 'aaa11'
|
||||
}, Param{
|
||||
key: 'name4'
|
||||
value: 'aaa'
|
||||
}, Param{
|
||||
key: 'name5'
|
||||
value: 'aab'
|
||||
}]
|
||||
}
|
||||
```
|
||||
440
aiprompts/ai_core/osal_os_system_tools.md
Normal file
440
aiprompts/ai_core/osal_os_system_tools.md
Normal file
@@ -0,0 +1,440 @@
|
||||
# module osal
|
||||
|
||||
|
||||
import as
|
||||
|
||||
```vlang
|
||||
import freeflowuniverse.crystallib.osal
|
||||
|
||||
osal.ping...
|
||||
|
||||
```
|
||||
|
||||
## ping
|
||||
|
||||
```go
|
||||
assert ping(address:"338.8.8.8")==.unknownhost
|
||||
assert ping(address:"8.8.8.8")==.ok
|
||||
assert ping(address:"18.8.8.8")==.timeout
|
||||
```
|
||||
|
||||
will do a panic if its not one of them, an unknown error
|
||||
|
||||
## platform
|
||||
|
||||
```go
|
||||
if platform()==.osx{
|
||||
//do something
|
||||
}
|
||||
|
||||
pub enum PlatformType {
|
||||
unknown
|
||||
osx
|
||||
ubuntu
|
||||
alpine
|
||||
}
|
||||
|
||||
pub enum CPUType {
|
||||
unknown
|
||||
intel
|
||||
arm
|
||||
intel32
|
||||
arm32
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## process
|
||||
|
||||
|
||||
### execute jobs
|
||||
|
||||
```v
|
||||
mut job2:=osal.exec(cmd:"ls /")?
|
||||
println(job2)
|
||||
|
||||
//wont die, the result can be found in /tmp/execscripts
|
||||
mut job:=osal.exec(cmd:"ls dsds",ignore_error:true)?
|
||||
//this one has an error
|
||||
println(job)
|
||||
```
|
||||
|
||||
All scripts are executed from a file from /tmp/execscripts
|
||||
|
||||
If the script executes well then its removed, so no leftovers, if it fails the script stays in the dir
|
||||
|
||||
### check process logs
|
||||
|
||||
```
|
||||
mut pm:=process.processmap_get()?
|
||||
```
|
||||
|
||||
info returns like:
|
||||
|
||||
```json
|
||||
}, freeflowuniverse.crystallib.process.ProcessInfo{
|
||||
cpu_perc: 0
|
||||
mem_perc: 0
|
||||
cmd: 'mc'
|
||||
pid: 84455
|
||||
ppid: 84467
|
||||
rss: 3168
|
||||
}, freeflowuniverse.crystallib.process.ProcessInfo{
|
||||
cpu_perc: 0
|
||||
mem_perc: 0
|
||||
cmd: 'zsh -Z -g'
|
||||
pid: 84467
|
||||
ppid: 84469
|
||||
rss: 1360
|
||||
}]
|
||||
```
|
||||
|
||||
## other commands
|
||||
|
||||
fn bin_path() !string
|
||||
fn cmd_add(args_ CmdAddArgs) !
|
||||
copy a binary to the right location on the local computer . e.g. is /usr/local/bin on linux . e.g. is ~/hero/bin on osx . will also add the bin location to the path of .zprofile and .zshrc (different per platform)
|
||||
fn cmd_exists(cmd string) bool
|
||||
fn cmd_exists_profile(cmd string) bool
|
||||
fn cmd_path(cmd string) !string
|
||||
is same as executing which in OS returns path or error
|
||||
fn cmd_to_script_path(cmd Command) !string
|
||||
will return temporary path which then can be executed, is a helper function for making script out of command
|
||||
fn cputype() CPUType
|
||||
fn cputype_enum_from_string(cpytype string) CPUType
|
||||
Returns the enum value that matches the provided string for CPUType
|
||||
fn dir_delete(path string) !
|
||||
remove all if it exists
|
||||
fn dir_ensure(path string) !
|
||||
remove all if it exists
|
||||
fn dir_reset(path string) !
|
||||
remove all if it exists and then (re-)create
|
||||
fn done_delete(key string) !
|
||||
fn done_exists(key string) bool
|
||||
fn done_get(key string) ?string
|
||||
fn done_get_int(key string) int
|
||||
fn done_get_str(key string) string
|
||||
fn done_print() !
|
||||
fn done_reset() !
|
||||
fn done_set(key string, val string) !
|
||||
fn download(args_ DownloadArgs) !pathlib.Path
|
||||
if name is not specified, then will be the filename part if the last ends in an extension like .md .txt .log .text ... the file will be downloaded
|
||||
fn env_get(key string) !string
|
||||
Returns the requested environment variable if it exists or throws an error if it does not
|
||||
fn env_get_all() map[string]string
|
||||
Returns all existing environment variables
|
||||
fn env_get_default(key string, def string) string
|
||||
Returns the requested environment variable if it exists or returns the provided default value if it does not
|
||||
fn env_set(args EnvSet)
|
||||
Sets an environment if it was not set before, it overwrites the enviroment variable if it exists and if overwrite was set to true (default)
|
||||
fn env_set_all(args EnvSetAll)
|
||||
Allows to set multiple enviroment variables in one go, if clear_before_set is true all existing environment variables will be unset before the operation, if overwrite_if_exists is set to true it will overwrite all existing enviromnent variables
|
||||
fn env_unset(key string)
|
||||
Unsets an environment variable
|
||||
fn env_unset_all()
|
||||
Unsets all environment variables
|
||||
fn exec(cmd Command) !Job
|
||||
cmd is the cmd to execute can use ' ' and spaces . if \n in cmd it will write it to ext and then execute with bash . if die==false then will just return returncode,out but not return error . if stdout will show stderr and stdout . . if cmd starts with find or ls, will give to bash -c so it can execute . if cmd has no path, path will be found . . Command argument: .
|
||||
```
|
||||
name string // to give a name to your command, good to see logs...
|
||||
cmd string
|
||||
description string
|
||||
timeout int = 3600 // timeout in sec
|
||||
stdout bool = true
|
||||
stdout_log bool = true
|
||||
raise_error bool = true // if false, will not raise an error but still error report
|
||||
ignore_error bool // means if error will just exit and not raise, there will be no error reporting
|
||||
work_folder string // location where cmd will be executed
|
||||
environment map[string]string // env variables
|
||||
ignore_error_codes []int
|
||||
scriptpath string // is the path where the script will be put which is executed
|
||||
scriptkeep bool // means we don't remove the script
|
||||
debug bool // if debug will put +ex in the script which is being executed and will make sure script stays
|
||||
shell bool // means we will execute it in a shell interactive
|
||||
retry int
|
||||
interactive bool = true // make sure we run on non interactive way
|
||||
async bool
|
||||
runtime RunTime (.bash, .python)
|
||||
|
||||
returns Job:
|
||||
start time.Time
|
||||
end time.Time
|
||||
cmd Command
|
||||
output []string
|
||||
error []string
|
||||
exit_code int
|
||||
status JobStatus
|
||||
process os.Process
|
||||
```
|
||||
return Job .
|
||||
fn exec_string(cmd Command) !string
|
||||
cmd is the cmd to execute can use ' ' and spaces if \n in cmd it will write it to ext and then execute with bash if die==false then will just return returncode,out but not return error if stdout will show stderr and stdout
|
||||
|
||||
if cmd starts with find or ls, will give to bash -c so it can execute if cmd has no path, path will be found $... are remplaced by environment arguments TODO:implement
|
||||
|
||||
Command argument: cmd string timeout int = 600 stdout bool = true die bool = true debug bool
|
||||
|
||||
return what needs to be executed can give it to bash -c ...
|
||||
fn execute_debug(cmd string) !string
|
||||
fn execute_interactive(cmd string) !
|
||||
shortcut to execute a job interactive means in shell
|
||||
fn execute_ok(cmd string) bool
|
||||
executes a cmd, if not error return true
|
||||
fn execute_silent(cmd string) !string
|
||||
shortcut to execute a job silent
|
||||
fn execute_stdout(cmd string) !string
|
||||
shortcut to execute a job to stdout
|
||||
fn file_read(path string) !string
|
||||
fn file_write(path string, text string) !
|
||||
fn get_logger() log.Logger
|
||||
Returns a logger object and allows you to specify via environment argument OSAL_LOG_LEVEL the debug level
|
||||
fn hero_path() !string
|
||||
fn hostname() !string
|
||||
fn initname() !string
|
||||
e.g. systemd, bash, zinit
|
||||
fn ipaddr_pub_get() !string
|
||||
Returns the ipaddress as known on the public side is using resolver4.opendns.com
|
||||
fn is_linux() bool
|
||||
fn is_linux_arm() bool
|
||||
fn is_linux_intel() bool
|
||||
fn is_osx() bool
|
||||
fn is_osx_arm() bool
|
||||
fn is_osx_intel() bool
|
||||
fn is_ubuntu() bool
|
||||
fn load_env_file(file_path string) !
|
||||
fn memdb_exists(key string) bool
|
||||
fn memdb_get(key string) string
|
||||
fn memdb_set(key string, val string)
|
||||
fn package_install(name_ string) !
|
||||
install a package will use right commands per platform
|
||||
fn package_refresh() !
|
||||
update the package list
|
||||
fn ping(args PingArgs) PingResult
|
||||
if reached in timout result will be True address is e.g. 8.8.8.8 ping means we check if the destination responds
|
||||
fn platform() PlatformType
|
||||
fn platform_enum_from_string(platform string) PlatformType
|
||||
fn process_exists(pid int) bool
|
||||
fn process_exists_byname(name string) !bool
|
||||
fn process_kill_recursive(args ProcessKillArgs) !
|
||||
kill process and all the ones underneith
|
||||
fn processinfo_children(pid int) !ProcessMap
|
||||
get all children of 1 process
|
||||
fn processinfo_get(pid int) !ProcessInfo
|
||||
get process info from 1 specific process returns
|
||||
```
|
||||
pub struct ProcessInfo {
|
||||
pub mut:
|
||||
cpu_perc f32
|
||||
mem_perc f32
|
||||
cmd string
|
||||
pid int
|
||||
ppid int
|
||||
//resident memory
|
||||
rss int
|
||||
}
|
||||
```
|
||||
fn processinfo_get_byname(name string) ![]ProcessInfo
|
||||
fn processinfo_with_children(pid int) !ProcessMap
|
||||
return the process and its children
|
||||
fn processmap_get() !ProcessMap
|
||||
make sure to use new first, so that the connection has been initted then you can get it everywhere
|
||||
fn profile_path() string
|
||||
fn profile_path_add(args ProfilePathAddArgs) !
|
||||
add the following path to a profile
|
||||
fn profile_path_add_hero() !string
|
||||
fn profile_path_source() string
|
||||
return the source statement if the profile exists
|
||||
fn profile_path_source_and() string
|
||||
return source $path && . or empty if it doesn't exist
|
||||
fn sleep(duration int)
|
||||
sleep in seconds
|
||||
fn tcp_port_test(args TcpPortTestArgs) bool
|
||||
test if a tcp port answers
|
||||
```
|
||||
address string //192.168.8.8
|
||||
port int = 22
|
||||
timeout u16 = 2000 // total time in milliseconds to keep on trying
|
||||
```
|
||||
fn user_add(args UserArgs) !int
|
||||
add's a user if the user does not exist yet
|
||||
fn user_exists(username string) bool
|
||||
fn user_id_get(username string) !int
|
||||
fn usr_local_path() !string
|
||||
/usr/local on linux, ${os.home_dir()}/hero on osx
|
||||
fn whoami() !string
|
||||
fn write_flags[T](options T) string
|
||||
enum CPUType {
|
||||
unknown
|
||||
intel
|
||||
arm
|
||||
intel32
|
||||
arm32
|
||||
}
|
||||
enum ErrorType {
|
||||
exec
|
||||
timeout
|
||||
args
|
||||
}
|
||||
enum JobStatus {
|
||||
init
|
||||
running
|
||||
error_exec
|
||||
error_timeout
|
||||
error_args
|
||||
done
|
||||
}
|
||||
enum PMState {
|
||||
init
|
||||
ok
|
||||
old
|
||||
}
|
||||
enum PingResult {
|
||||
ok
|
||||
timeout // timeout from ping
|
||||
unknownhost // means we don't know the hostname its a dns issue
|
||||
}
|
||||
enum PlatformType {
|
||||
unknown
|
||||
osx
|
||||
ubuntu
|
||||
alpine
|
||||
arch
|
||||
suse
|
||||
}
|
||||
enum RunTime {
|
||||
bash
|
||||
python
|
||||
heroscript
|
||||
herocmd
|
||||
v
|
||||
}
|
||||
struct CmdAddArgs {
|
||||
pub mut:
|
||||
cmdname string
|
||||
source string @[required] // path where the binary is
|
||||
symlink bool // if rather than copy do a symlink
|
||||
reset bool // if existing cmd will delete
|
||||
// bin_repo_url string = 'https://github.com/freeflowuniverse/freeflow_binary' // binary where we put the results
|
||||
}
|
||||
struct Command {
|
||||
pub mut:
|
||||
name string // to give a name to your command, good to see logs...
|
||||
cmd string
|
||||
description string
|
||||
timeout int = 3600 // timeout in sec
|
||||
stdout bool = true
|
||||
stdout_log bool = true
|
||||
raise_error bool = true // if false, will not raise an error but still error report
|
||||
ignore_error bool // means if error will just exit and not raise, there will be no error reporting
|
||||
work_folder string // location where cmd will be executed
|
||||
environment map[string]string // env variables
|
||||
ignore_error_codes []int
|
||||
scriptpath string // is the path where the script will be put which is executed
|
||||
scriptkeep bool // means we don't remove the script
|
||||
debug bool // if debug will put +ex in the script which is being executed and will make sure script stays
|
||||
shell bool // means we will execute it in a shell interactive
|
||||
retry int
|
||||
interactive bool = true
|
||||
async bool
|
||||
runtime RunTime
|
||||
}
|
||||
struct DownloadArgs {
|
||||
pub mut:
|
||||
name string // optional (otherwise derived out of filename)
|
||||
url string
|
||||
reset bool // will remove
|
||||
hash string // if hash is known, will verify what hash is
|
||||
dest string // if specified will copy to that destination
|
||||
timeout int = 180
|
||||
retry int = 3
|
||||
minsize_kb u32 = 10 // is always in kb
|
||||
maxsize_kb u32
|
||||
expand_dir string
|
||||
expand_file string
|
||||
}
|
||||
struct EnvSet {
|
||||
pub mut:
|
||||
key string @[required]
|
||||
value string @[required]
|
||||
overwrite bool = true
|
||||
}
|
||||
struct EnvSetAll {
|
||||
pub mut:
|
||||
env map[string]string
|
||||
clear_before_set bool
|
||||
overwrite_if_exists bool = true
|
||||
}
|
||||
struct Job {
|
||||
pub mut:
|
||||
start time.Time
|
||||
end time.Time
|
||||
cmd Command
|
||||
output string
|
||||
error string
|
||||
exit_code int
|
||||
status JobStatus
|
||||
process ?&os.Process @[skip; str: skip]
|
||||
runnr int // nr of time it runs, is for retry
|
||||
}
|
||||
fn (mut job Job) execute_retry() !
|
||||
execute the job and wait on result will retry as specified
|
||||
fn (mut job Job) execute() !
|
||||
execute the job, start process, process will not be closed . important you need to close the process later by job.close()! otherwise we get zombie processes
|
||||
fn (mut job Job) wait() !
|
||||
wait till the job finishes or goes in error
|
||||
fn (mut job Job) process() !
|
||||
process (read std.err and std.out of process)
|
||||
fn (mut job Job) close() !
|
||||
will wait & close
|
||||
struct JobError {
|
||||
Error
|
||||
pub mut:
|
||||
job Job
|
||||
error_type ErrorType
|
||||
}
|
||||
struct PingArgs {
|
||||
pub mut:
|
||||
address string @[required]
|
||||
count u8 = 1 // the ping is successful if it got count amount of replies from the other side
|
||||
timeout u16 = 1 // the time in which the other side should respond in seconds
|
||||
retry u8
|
||||
}
|
||||
struct ProcessInfo {
|
||||
pub mut:
|
||||
cpu_perc f32
|
||||
mem_perc f32
|
||||
cmd string
|
||||
pid int
|
||||
ppid int // parentpid
|
||||
// resident memory
|
||||
rss int
|
||||
}
|
||||
fn (mut p ProcessInfo) str() string
|
||||
struct ProcessKillArgs {
|
||||
pub mut:
|
||||
name string
|
||||
pid int
|
||||
}
|
||||
struct ProcessMap {
|
||||
pub mut:
|
||||
processes []ProcessInfo
|
||||
lastscan time.Time
|
||||
state PMState
|
||||
pids []int
|
||||
}
|
||||
struct ProfilePathAddArgs {
|
||||
pub mut:
|
||||
path string @[required]
|
||||
todelete string // see which one to remove
|
||||
}
|
||||
struct TcpPortTestArgs {
|
||||
pub mut:
|
||||
address string @[required] // 192.168.8.8
|
||||
port int = 22
|
||||
timeout u16 = 2000 // total time in milliseconds to keep on trying
|
||||
}
|
||||
struct UserArgs {
|
||||
pub mut:
|
||||
name string @[required]
|
||||
}
|
||||
*
|
||||
2939
aiprompts/ai_core/v_manual.md
Normal file
2939
aiprompts/ai_core/v_manual.md
Normal file
File diff suppressed because it is too large
Load Diff
2009
aiprompts/ai_core/v_manual2.md
Normal file
2009
aiprompts/ai_core/v_manual2.md
Normal file
File diff suppressed because it is too large
Load Diff
1
aiprompts/binencoder.md
Symbolic link
1
aiprompts/binencoder.md
Symbolic link
@@ -0,0 +1 @@
|
||||
../lib/data/encoder/readme.md
|
||||
1
aiprompts/currency.md
Symbolic link
1
aiprompts/currency.md
Symbolic link
@@ -0,0 +1 @@
|
||||
../lib/data/currency/readme.md
|
||||
340
aiprompts/datatypes.md
Normal file
340
aiprompts/datatypes.md
Normal file
@@ -0,0 +1,340 @@
|
||||
module datatypes
|
||||
|
||||
# datatypes
|
||||
|
||||
This module provides implementations of less frequently used, but still common data types.
|
||||
|
||||
V's `builtin` module is imported implicitly, and has implementations for arrays, maps and strings. These are good for many applications, but there are a plethora of other useful data structures/containers, like linked lists, priority queues, trees, etc, that allow for algorithms with different time complexities, which may be more suitable for your specific application.
|
||||
|
||||
It is implemented using generics, that you have to specialise for the type of your actual elements. For example:
|
||||
```v
|
||||
import datatypes
|
||||
|
||||
mut stack := datatypes.Stack[int]{}
|
||||
stack.push(1)
|
||||
println(stack)
|
||||
```
|
||||
|
||||
## Currently Implemented Datatypes:
|
||||
|
||||
- [x] Linked list
|
||||
- [x] Doubly linked list
|
||||
- [x] Stack (LIFO)
|
||||
- [x] Queue (FIFO)
|
||||
- [x] Min heap (priority queue)
|
||||
- [x] Set
|
||||
- [x] Quadtree
|
||||
- [x] Bloom filter
|
||||
- [ ] ...
|
||||
|
||||
|
||||
fn new_bloom_filter[T](hash_func fn (T) u32, table_size int, num_functions int) !&BloomFilter[T]
|
||||
new_bloom_filter creates a new bloom_filter. `table_size` should be greater than 0, and `num_functions` should be 1~16.
|
||||
fn new_bloom_filter_fast[T](hash_func fn (T) u32) &BloomFilter[T]
|
||||
new_bloom_filter_fast creates a new bloom_filter. `table_size` is 16384, and `num_functions` is 4.
|
||||
fn new_ringbuffer[T](s int) RingBuffer[T]
|
||||
new_ringbuffer creates an empty ring buffer of size `s`.
|
||||
fn (mut bst BSTree[T]) insert(value T) bool
|
||||
insert give the possibility to insert an element in the BST.
|
||||
fn (bst &BSTree[T]) contains(value T) bool
|
||||
contains checks if an element with a given `value` is inside the BST.
|
||||
fn (mut bst BSTree[T]) remove(value T) bool
|
||||
remove removes an element with `value` from the BST.
|
||||
fn (bst &BSTree[T]) is_empty() bool
|
||||
is_empty checks if the BST is empty
|
||||
fn (bst &BSTree[T]) in_order_traversal() []T
|
||||
in_order_traversal traverses the BST in order, and returns the result as an array.
|
||||
fn (bst &BSTree[T]) post_order_traversal() []T
|
||||
post_order_traversal traverses the BST in post order, and returns the result in an array.
|
||||
fn (bst &BSTree[T]) pre_order_traversal() []T
|
||||
pre_order_traversal traverses the BST in pre order, and returns the result as an array.
|
||||
fn (bst &BSTree[T]) to_left(value T) !T
|
||||
to_left returns the value of the node to the left of the node with `value` specified if it exists, otherwise the a false value is returned.
|
||||
|
||||
An example of usage can be the following one
|
||||
```v
|
||||
left_value, exist := bst.to_left(10)
|
||||
```
|
||||
fn (bst &BSTree[T]) to_right(value T) !T
|
||||
to_right return the value of the element to the right of the node with `value` specified, if exist otherwise, the boolean value is false An example of usage can be the following one
|
||||
|
||||
```v
|
||||
left_value, exist := bst.to_right(10)
|
||||
```
|
||||
fn (bst &BSTree[T]) max() !T
|
||||
max return the max element inside the BST. Time complexity O(N) if the BST is not balanced
|
||||
fn (bst &BSTree[T]) min() !T
|
||||
min return the minimum element in the BST. Time complexity O(N) if the BST is not balanced.
|
||||
fn (mut b BloomFilter[T]) add(element T)
|
||||
adds the element to bloom filter.
|
||||
fn (b &BloomFilter[T]) exists(element T) bool
|
||||
checks the element is exists.
|
||||
fn (l &BloomFilter[T]) @union(r &BloomFilter[T]) !&BloomFilter[T]
|
||||
@union returns the union of the two bloom filters.
|
||||
fn (l &BloomFilter[T]) intersection(r &BloomFilter[T]) !&BloomFilter[T]
|
||||
intersection returns the intersection of bloom filters.
|
||||
fn (list DoublyLinkedList[T]) is_empty() bool
|
||||
is_empty checks if the linked list is empty
|
||||
fn (list DoublyLinkedList[T]) len() int
|
||||
len returns the length of the linked list
|
||||
fn (list DoublyLinkedList[T]) first() !T
|
||||
first returns the first element of the linked list
|
||||
fn (list DoublyLinkedList[T]) last() !T
|
||||
last returns the last element of the linked list
|
||||
fn (mut list DoublyLinkedList[T]) push_back(item T)
|
||||
push_back adds an element to the end of the linked list
|
||||
fn (mut list DoublyLinkedList[T]) push_front(item T)
|
||||
push_front adds an element to the beginning of the linked list
|
||||
fn (mut list DoublyLinkedList[T]) push_many(elements []T, direction Direction)
|
||||
push_many adds array of elements to the beginning of the linked list
|
||||
fn (mut list DoublyLinkedList[T]) pop_back() !T
|
||||
pop_back removes the last element of the linked list
|
||||
fn (mut list DoublyLinkedList[T]) pop_front() !T
|
||||
pop_front removes the last element of the linked list
|
||||
fn (mut list DoublyLinkedList[T]) insert(idx int, item T) !
|
||||
insert adds an element to the linked list at the given index
|
||||
fn (list &DoublyLinkedList[T]) index(item T) !int
|
||||
index searches the linked list for item and returns the forward index or none if not found.
|
||||
fn (mut list DoublyLinkedList[T]) delete(idx int)
|
||||
delete removes index idx from the linked list and is safe to call for any idx.
|
||||
fn (list DoublyLinkedList[T]) str() string
|
||||
str returns a string representation of the linked list
|
||||
fn (list DoublyLinkedList[T]) array() []T
|
||||
array returns a array representation of the linked list
|
||||
fn (mut list DoublyLinkedList[T]) next() ?T
|
||||
next implements the iter interface to use DoublyLinkedList with V's `for x in list {` loop syntax.
|
||||
fn (mut list DoublyLinkedList[T]) iterator() DoublyListIter[T]
|
||||
iterator returns a new iterator instance for the `list`.
|
||||
fn (mut list DoublyLinkedList[T]) back_iterator() DoublyListIterBack[T]
|
||||
back_iterator returns a new backwards iterator instance for the `list`.
|
||||
fn (mut iter DoublyListIterBack[T]) next() ?T
|
||||
next returns *the previous* element of the list, or `none` when the start of the list is reached. It is called by V's `for x in iter{` on each iteration.
|
||||
fn (mut iter DoublyListIter[T]) next() ?T
|
||||
next returns *the next* element of the list, or `none` when the end of the list is reached. It is called by V's `for x in iter{` on each iteration.
|
||||
fn (list LinkedList[T]) is_empty() bool
|
||||
is_empty checks if the linked list is empty
|
||||
fn (list LinkedList[T]) len() int
|
||||
len returns the length of the linked list
|
||||
fn (list LinkedList[T]) first() !T
|
||||
first returns the first element of the linked list
|
||||
fn (list LinkedList[T]) last() !T
|
||||
last returns the last element of the linked list
|
||||
fn (list LinkedList[T]) index(idx int) !T
|
||||
index returns the element at the given index of the linked list
|
||||
fn (mut list LinkedList[T]) push(item T)
|
||||
push adds an element to the end of the linked list
|
||||
fn (mut list LinkedList[T]) push_many(elements []T)
|
||||
push adds an array of elements to the end of the linked list
|
||||
fn (mut list LinkedList[T]) pop() !T
|
||||
pop removes the last element of the linked list
|
||||
fn (mut list LinkedList[T]) shift() !T
|
||||
shift removes the first element of the linked list
|
||||
fn (mut list LinkedList[T]) insert(idx int, item T) !
|
||||
insert adds an element to the linked list at the given index
|
||||
fn (mut list LinkedList[T]) prepend(item T)
|
||||
prepend adds an element to the beginning of the linked list (equivalent to insert(0, item))
|
||||
fn (list LinkedList[T]) str() string
|
||||
str returns a string representation of the linked list
|
||||
fn (list LinkedList[T]) array() []T
|
||||
array returns a array representation of the linked list
|
||||
fn (mut list LinkedList[T]) next() ?T
|
||||
next implements the iteration interface to use LinkedList with V's `for` loop syntax.
|
||||
fn (mut list LinkedList[T]) iterator() ListIter[T]
|
||||
iterator returns a new iterator instance for the `list`.
|
||||
fn (mut iter ListIter[T]) next() ?T
|
||||
next returns the next element of the list, or `none` when the end of the list is reached. It is called by V's `for x in iter{` on each iteration.
|
||||
fn (mut heap MinHeap[T]) insert(item T)
|
||||
insert adds an element to the heap.
|
||||
fn (mut heap MinHeap[T]) insert_many(elements []T)
|
||||
insert array of elements to the heap.
|
||||
fn (mut heap MinHeap[T]) pop() !T
|
||||
pop removes the top-most element from the heap.
|
||||
fn (heap MinHeap[T]) peek() !T
|
||||
peek gets the top-most element from the heap without removing it.
|
||||
fn (heap MinHeap[T]) len() int
|
||||
len returns the number of elements in the heap.
|
||||
fn (queue Queue[T]) is_empty() bool
|
||||
is_empty checks if the queue is empty
|
||||
fn (queue Queue[T]) len() int
|
||||
len returns the length of the queue
|
||||
fn (queue Queue[T]) peek() !T
|
||||
peek returns the head of the queue (first element added)
|
||||
fn (queue Queue[T]) last() !T
|
||||
last returns the tail of the queue (last element added)
|
||||
fn (queue Queue[T]) index(idx int) !T
|
||||
index returns the element at the given index of the queue
|
||||
fn (mut queue Queue[T]) push(item T)
|
||||
push adds an element to the tail of the queue
|
||||
fn (mut queue Queue[T]) pop() !T
|
||||
pop removes the element at the head of the queue and returns it
|
||||
fn (queue Queue[T]) str() string
|
||||
str returns a string representation of the queue
|
||||
fn (queue Queue[T]) array() []T
|
||||
array returns a array representation of the queue
|
||||
fn (mut rb RingBuffer[T]) push(element T) !
|
||||
push adds an element to the ring buffer.
|
||||
fn (mut rb RingBuffer[T]) pop() !T
|
||||
pop returns the oldest element in the buffer.
|
||||
fn (mut rb RingBuffer[T]) push_many(elements []T) !
|
||||
push_many pushes an array to the buffer.
|
||||
fn (mut rb RingBuffer[T]) pop_many(n u64) ![]T
|
||||
pop_many returns `n` elements of the buffer starting with the oldest one.
|
||||
fn (rb RingBuffer[T]) is_empty() bool
|
||||
is_empty returns `true` if the ring buffer is empty, `false` otherwise.
|
||||
fn (rb RingBuffer[T]) is_full() bool
|
||||
is_full returns `true` if the ring buffer is full, `false` otherwise.
|
||||
fn (rb RingBuffer[T]) capacity() int
|
||||
capacity returns the capacity of the ring buffer.
|
||||
fn (mut rb RingBuffer[T]) clear()
|
||||
clear empties the ring buffer and all pushed elements.
|
||||
fn (rb RingBuffer[T]) occupied() int
|
||||
occupied returns the occupied capacity of the buffer.
|
||||
fn (rb RingBuffer[T]) remaining() int
|
||||
remaining returns the remaining capacity of the buffer.
|
||||
fn (set Set[T]) exists(element T) bool
|
||||
checks the element is exists.
|
||||
fn (mut set Set[T]) add(element T)
|
||||
adds the element to set, if it is not present already.
|
||||
fn (mut set Set[T]) remove(element T)
|
||||
removes the element from set.
|
||||
fn (set Set[T]) pick() !T
|
||||
pick returns an arbitrary element of set, if set is not empty.
|
||||
fn (mut set Set[T]) rest() ![]T
|
||||
rest returns the set consisting of all elements except for the arbitrary element.
|
||||
fn (mut set Set[T]) pop() !T
|
||||
pop returns an arbitrary element and deleting it from set.
|
||||
fn (mut set Set[T]) clear()
|
||||
delete all elements of set.
|
||||
fn (l Set[T]) == (r Set[T]) bool
|
||||
== checks whether the two given sets are equal (i.e. contain all and only the same elements).
|
||||
fn (set Set[T]) is_empty() bool
|
||||
is_empty checks whether the set is empty or not.
|
||||
fn (set Set[T]) size() int
|
||||
size returns the number of elements in the set.
|
||||
fn (set Set[T]) copy() Set[T]
|
||||
copy returns a copy of all the elements in the set.
|
||||
fn (mut set Set[T]) add_all(elements []T)
|
||||
add_all adds the whole `elements` array to the set
|
||||
fn (l Set[T]) @union(r Set[T]) Set[T]
|
||||
@union returns the union of the two sets.
|
||||
fn (l Set[T]) intersection(r Set[T]) Set[T]
|
||||
intersection returns the intersection of sets.
|
||||
fn (l Set[T]) - (r Set[T]) Set[T]
|
||||
- returns the difference of sets.
|
||||
fn (l Set[T]) subset(r Set[T]) bool
|
||||
subset returns true if the set `r` is a subset of the set `l`.
|
||||
fn (stack Stack[T]) is_empty() bool
|
||||
is_empty checks if the stack is empty
|
||||
fn (stack Stack[T]) len() int
|
||||
len returns the length of the stack
|
||||
fn (stack Stack[T]) peek() !T
|
||||
peek returns the top of the stack
|
||||
fn (mut stack Stack[T]) push(item T)
|
||||
push adds an element to the top of the stack
|
||||
fn (mut stack Stack[T]) pop() !T
|
||||
pop removes the element at the top of the stack and returns it
|
||||
fn (stack Stack[T]) str() string
|
||||
str returns a string representation of the stack
|
||||
fn (stack Stack[T]) array() []T
|
||||
array returns a array representation of the stack
|
||||
enum Direction {
|
||||
front
|
||||
back
|
||||
}
|
||||
struct AABB {
|
||||
pub mut:
|
||||
x f64
|
||||
y f64
|
||||
width f64
|
||||
height f64
|
||||
}
|
||||
struct BSTree[T] {
|
||||
mut:
|
||||
root &BSTreeNode[T] = unsafe { 0 }
|
||||
}
|
||||
Pure Binary Seach Tree implementation
|
||||
|
||||
Pure V implementation of the Binary Search Tree Time complexity of main operation O(log N) Space complexity O(N)
|
||||
struct DoublyLinkedList[T] {
|
||||
mut:
|
||||
head &DoublyListNode[T] = unsafe { 0 }
|
||||
tail &DoublyListNode[T] = unsafe { 0 }
|
||||
// Internal iter pointer for allowing safe modification
|
||||
// of the list while iterating. TODO: use an option
|
||||
// instead of a pointer to determine it is initialized.
|
||||
iter &DoublyListIter[T] = unsafe { 0 }
|
||||
len int
|
||||
}
|
||||
DoublyLinkedList[T] represents a generic doubly linked list of elements, each of type T.
|
||||
struct DoublyListIter[T] {
|
||||
mut:
|
||||
node &DoublyListNode[T] = unsafe { 0 }
|
||||
}
|
||||
DoublyListIter[T] is an iterator for DoublyLinkedList. It starts from *the start* and moves forwards to *the end* of the list. It can be used with V's `for x in iter {` construct. One list can have multiple independent iterators, pointing to different positions/places in the list. A DoublyListIter iterator instance always traverses the list from *start to finish*.
|
||||
struct DoublyListIterBack[T] {
|
||||
mut:
|
||||
node &DoublyListNode[T] = unsafe { 0 }
|
||||
}
|
||||
DoublyListIterBack[T] is an iterator for DoublyLinkedList. It starts from *the end* and moves backwards to *the start* of the list. It can be used with V's `for x in iter {` construct. One list can have multiple independent iterators, pointing to different positions/places in the list. A DoublyListIterBack iterator instance always traverses the list from *finish to start*.
|
||||
struct LinkedList[T] {
|
||||
mut:
|
||||
head &ListNode[T] = unsafe { 0 }
|
||||
len int
|
||||
// Internal iter pointer for allowing safe modification
|
||||
// of the list while iterating. TODO: use an option
|
||||
// instead of a pointer to determine if it is initialized.
|
||||
iter &ListIter[T] = unsafe { 0 }
|
||||
}
|
||||
struct ListIter[T] {
|
||||
mut:
|
||||
node &ListNode[T] = unsafe { 0 }
|
||||
}
|
||||
ListIter[T] is an iterator for LinkedList. It can be used with V's `for x in iter {` construct. One list can have multiple independent iterators, pointing to different positions/places in the list. An iterator instance always traverses the list from start to finish.
|
||||
struct ListNode[T] {
|
||||
mut:
|
||||
data T
|
||||
next &ListNode[T] = unsafe { 0 }
|
||||
}
|
||||
struct MinHeap[T] {
|
||||
mut:
|
||||
data []T
|
||||
}
|
||||
MinHeap is a binary minimum heap data structure.
|
||||
struct Quadtree {
|
||||
pub mut:
|
||||
perimeter AABB
|
||||
capacity int
|
||||
depth int
|
||||
level int
|
||||
particles []AABB
|
||||
nodes []Quadtree
|
||||
}
|
||||
fn (mut q Quadtree) create(x f64, y f64, width f64, height f64, capacity int, depth int, level int) Quadtree
|
||||
create returns a new configurable root node for the tree.
|
||||
fn (mut q Quadtree) insert(p AABB)
|
||||
insert recursively adds a particle in the correct index of the tree.
|
||||
fn (mut q Quadtree) retrieve(p AABB) []AABB
|
||||
retrieve recursively checks if a particle is in a specific index of the tree.
|
||||
fn (mut q Quadtree) clear()
|
||||
clear flushes out nodes and particles from the tree.
|
||||
fn (q Quadtree) get_nodes() []Quadtree
|
||||
get_nodes recursively returns the subdivisions the tree has.
|
||||
struct Queue[T] {
|
||||
mut:
|
||||
elements LinkedList[T]
|
||||
}
|
||||
struct RingBuffer[T] {
|
||||
mut:
|
||||
reader int // index of the tail where data is going to be read
|
||||
writer int // index of the head where data is going to be written
|
||||
content []T
|
||||
}
|
||||
RingBuffer represents a ring buffer also known as a circular buffer.
|
||||
struct Set[T] {
|
||||
mut:
|
||||
elements map[T]u8
|
||||
}
|
||||
struct Stack[T] {
|
||||
mut:
|
||||
elements []T
|
||||
}
|
||||
79
aiprompts/details/heroscript_internal.md
Normal file
79
aiprompts/details/heroscript_internal.md
Normal file
@@ -0,0 +1,79 @@
|
||||
|
||||
## how internally a heroscript gets parsed for params
|
||||
|
||||
- example to show how a heroscript gets parsed in action with params
|
||||
- params are part of action object
|
||||
|
||||
```heroscript
|
||||
example text to parse (heroscript)
|
||||
|
||||
id:a1 name6:aaaaa
|
||||
name:'need to do something 1'
|
||||
description:
|
||||
'
|
||||
## markdown works in it
|
||||
description can be multiline
|
||||
lets see what happens
|
||||
|
||||
- a
|
||||
- something else
|
||||
|
||||
### subtitle
|
||||
'
|
||||
|
||||
name2: test
|
||||
name3: hi
|
||||
name10:'this is with space' name11:aaa11
|
||||
|
||||
name4: 'aaa'
|
||||
|
||||
//somecomment
|
||||
name5: 'aab'
|
||||
```
|
||||
|
||||
the params are part of the action and are represented as follow for the above:
|
||||
|
||||
```vlang
|
||||
Params{
|
||||
params: [Param{
|
||||
key: 'id'
|
||||
value: 'a1'
|
||||
}, Param{
|
||||
key: 'name6'
|
||||
value: 'aaaaa'
|
||||
}, Param{
|
||||
key: 'name'
|
||||
value: 'need to do something 1'
|
||||
}, Param{
|
||||
key: 'description'
|
||||
value: '## markdown works in it
|
||||
|
||||
description can be multiline
|
||||
lets see what happens
|
||||
|
||||
- a
|
||||
- something else
|
||||
|
||||
### subtitle
|
||||
'
|
||||
}, Param{
|
||||
key: 'name2'
|
||||
value: 'test'
|
||||
}, Param{
|
||||
key: 'name3'
|
||||
value: 'hi'
|
||||
}, Param{
|
||||
key: 'name10'
|
||||
value: 'this is with space'
|
||||
}, Param{
|
||||
key: 'name11'
|
||||
value: 'aaa11'
|
||||
}, Param{
|
||||
key: 'name4'
|
||||
value: 'aaa'
|
||||
}, Param{
|
||||
key: 'name5'
|
||||
value: 'aab'
|
||||
}]
|
||||
}
|
||||
```
|
||||
1
aiprompts/docker.md
Symbolic link
1
aiprompts/docker.md
Symbolic link
@@ -0,0 +1 @@
|
||||
../crystallib/virt/docker/readme.md
|
||||
1
aiprompts/gittools.md
Symbolic link
1
aiprompts/gittools.md
Symbolic link
@@ -0,0 +1 @@
|
||||
../lib/develop/gittools/README.md
|
||||
124
aiprompts/html_parser.md
Normal file
124
aiprompts/html_parser.md
Normal file
@@ -0,0 +1,124 @@
|
||||
module net.html
|
||||
|
||||
net/html is an **HTML Parser** written in pure V.
|
||||
|
||||
## Usage
|
||||
|
||||
```v
|
||||
import net.html
|
||||
|
||||
fn main() {
|
||||
doc := html.parse('<html><body><h1 class="title">Hello world!</h1></body></html>')
|
||||
tag := doc.get_tags(name: 'h1')[0] // <h1>Hello world!</h1>
|
||||
println(tag.name) // h1
|
||||
println(tag.content) // Hello world!
|
||||
println(tag.attributes) // {'class':'title'}
|
||||
println(tag.str()) // <h1 class="title">Hello world!</h1>
|
||||
}
|
||||
```
|
||||
|
||||
More examples found on [`parser_test.v`](parser_test.v) and [`html_test.v`](html_test.v)
|
||||
|
||||
fn parse(text string) DocumentObjectModel
|
||||
parse parses and returns the DOM from the given text.
|
||||
|
||||
Note: this function converts tags to lowercase. E.g. <MyTag>content</MyTag> is parsed as <mytag>content</mytag>.
|
||||
fn parse_file(filename string) DocumentObjectModel
|
||||
parse_file parses and returns the DOM from the contents of a file.
|
||||
|
||||
Note: this function converts tags to lowercase. E.g. <MyTag>content</MyTag> is parsed as <mytag>content</mytag>.
|
||||
enum CloseTagType {
|
||||
in_name
|
||||
new_tag
|
||||
}
|
||||
struct DocumentObjectModel {
|
||||
mut:
|
||||
root &Tag = unsafe { nil }
|
||||
constructed bool
|
||||
btree BTree
|
||||
all_tags []&Tag
|
||||
all_attributes map[string][]&Tag
|
||||
close_tags map[string]bool // add a counter to see count how many times is closed and parse correctly
|
||||
attributes map[string][]string
|
||||
tag_attributes map[string][][]&Tag
|
||||
tag_type map[string][]&Tag
|
||||
debug_file os.File
|
||||
}
|
||||
The W3C Document Object Model (DOM) is a platform and language-neutral interface that allows programs and scripts to dynamically access and update the content, structure, and style of a document.
|
||||
|
||||
https://www.w3.org/TR/WD-DOM/introduction.html
|
||||
fn (dom DocumentObjectModel) get_root() &Tag
|
||||
get_root returns the root of the document.
|
||||
fn (dom DocumentObjectModel) get_tag(name string) []&Tag
|
||||
get_tag retrieves all tags in the document that have the given tag name.
|
||||
fn (dom DocumentObjectModel) get_tags(options GetTagsOptions) []&Tag
|
||||
get_tags returns all tags stored in the document.
|
||||
fn (dom DocumentObjectModel) get_tags_by_class_name(names ...string) []&Tag
|
||||
get_tags_by_class_name retrieves all tags recursively in the document root that have the given class name(s).
|
||||
fn (dom DocumentObjectModel) get_tag_by_attribute(name string) []&Tag
|
||||
get_tag_by_attribute retrieves all tags in the document that have the given attribute name.
|
||||
fn (dom DocumentObjectModel) get_tags_by_attribute(name string) []&Tag
|
||||
get_tags_by_attribute retrieves all tags in the document that have the given attribute name.
|
||||
fn (mut dom DocumentObjectModel) get_tags_by_attribute_value(name string, value string) []&Tag
|
||||
get_tags_by_attribute_value retrieves all tags in the document that have the given attribute name and value.
|
||||
fn (mut dom DocumentObjectModel) get_tag_by_attribute_value(name string, value string) []&Tag
|
||||
get_tag_by_attribute_value retrieves all tags in the document that have the given attribute name and value.
|
||||
struct GetTagsOptions {
|
||||
pub:
|
||||
name string
|
||||
}
|
||||
struct Parser {
|
||||
mut:
|
||||
dom DocumentObjectModel
|
||||
lexical_attributes LexicalAttributes = LexicalAttributes{
|
||||
current_tag: &Tag{}
|
||||
}
|
||||
filename string = 'direct-parse'
|
||||
initialized bool
|
||||
tags []&Tag
|
||||
debug_file os.File
|
||||
}
|
||||
Parser is responsible for reading the HTML strings and converting them into a `DocumentObjectModel`.
|
||||
fn (mut parser Parser) add_code_tag(name string)
|
||||
This function is used to add a tag for the parser ignore it's content. For example, if you have an html or XML with a custom tag, like `<script>`, using this function, like `add_code_tag('script')` will make all `script` tags content be jumped, so you still have its content, but will not confuse the parser with it's `>` or `<`.
|
||||
fn (mut parser Parser) split_parse(data string)
|
||||
split_parse parses the HTML fragment
|
||||
fn (mut parser Parser) parse_html(data string)
|
||||
parse_html parses the given HTML string
|
||||
fn (mut parser Parser) finalize()
|
||||
finalize finishes the parsing stage .
|
||||
fn (mut parser Parser) get_dom() DocumentObjectModel
|
||||
get_dom returns the parser's current DOM representation.
|
||||
struct Tag {
|
||||
pub mut:
|
||||
name string
|
||||
content string
|
||||
children []&Tag
|
||||
attributes map[string]string // attributes will be like map[name]value
|
||||
last_attribute string
|
||||
class_set datatypes.Set[string]
|
||||
parent &Tag = unsafe { nil }
|
||||
position_in_parent int
|
||||
closed bool
|
||||
close_type CloseTagType = .in_name
|
||||
}
|
||||
Tag holds the information of an HTML tag.
|
||||
fn (tag Tag) text() string
|
||||
text returns the text contents of the tag.
|
||||
fn (tag &Tag) str() string
|
||||
fn (tag &Tag) get_tag(name string) ?&Tag
|
||||
get_tag retrieves the first found child tag in the tag that has the given tag name.
|
||||
fn (tag &Tag) get_tags(name string) []&Tag
|
||||
get_tags retrieves all child tags recursively in the tag that have the given tag name.
|
||||
fn (tag &Tag) get_tag_by_attribute(name string) ?&Tag
|
||||
get_tag_by_attribute retrieves the first found child tag in the tag that has the given attribute name.
|
||||
fn (tag &Tag) get_tags_by_attribute(name string) []&Tag
|
||||
get_tags_by_attribute retrieves all child tags recursively in the tag that have the given attribute name.
|
||||
fn (tag &Tag) get_tag_by_attribute_value(name string, value string) ?&Tag
|
||||
get_tag_by_attribute_value retrieves the first found child tag in the tag that has the given attribute name and value.
|
||||
fn (tag &Tag) get_tags_by_attribute_value(name string, value string) []&Tag
|
||||
get_tags_by_attribute_value retrieves all child tags recursively in the tag that have the given attribute name and value.
|
||||
fn (tag &Tag) get_tag_by_class_name(names ...string) ?&Tag
|
||||
get_tag_by_class_name retrieves the first found child tag in the tag that has the given class name(s).
|
||||
fn (tag &Tag) get_tags_by_class_name(names ...string) []&Tag
|
||||
get_tags_by_class_name retrieves all child tags recursively in the tag that have the given class name(s).
|
||||
1
aiprompts/httpconnection.md
Symbolic link
1
aiprompts/httpconnection.md
Symbolic link
@@ -0,0 +1 @@
|
||||
../lib/core/httpconnection/readme.md
|
||||
105
aiprompts/io.md
Normal file
105
aiprompts/io.md
Normal file
@@ -0,0 +1,105 @@
|
||||
module io
|
||||
## Description
|
||||
|
||||
`io` provides common interfaces for buffered reading/writing of data.
|
||||
|
||||
const read_all_len = 10 * 1024
|
||||
const read_all_grow_len = 1024
|
||||
fn cp(mut src Reader, mut dst Writer) !
|
||||
cp copies from `src` to `dst` by allocating a maximum of 1024 bytes buffer for reading until either EOF is reached on `src` or an error occurs. An error is returned if an error is encountered during write.
|
||||
fn make_readerwriter(r Reader, w Writer) ReaderWriterImpl
|
||||
make_readerwriter takes a rstream and a wstream and makes an rwstream with them.
|
||||
fn new_buffered_reader(o BufferedReaderConfig) &BufferedReader
|
||||
new_buffered_reader creates a new BufferedReader.
|
||||
fn new_multi_writer(writers ...Writer) Writer
|
||||
new_multi_writer returns a Writer that writes to all writers. The write function of the returned Writer writes to all writers of the MultiWriter, returns the length of bytes written, and if any writer fails to write the full length an error is returned and writing to other writers stops, and if any writer returns an error the error is returned immediately and writing to other writers stops.
|
||||
fn read_all(config ReadAllConfig) ![]u8
|
||||
read_all reads all bytes from a reader until either a 0 length read or if read_to_end_of_stream is true then the end of the stream (`none`).
|
||||
fn read_any(mut r Reader) ![]u8
|
||||
read_any reads any available bytes from a reader (until the reader returns a read of 0 length).
|
||||
interface RandomReader {
|
||||
read_from(pos u64, mut buf []u8) !int
|
||||
}
|
||||
RandomReader represents a stream of readable data from at a random location.
|
||||
interface RandomWriter {
|
||||
write_to(pos u64, buf []u8) !int
|
||||
}
|
||||
RandomWriter is the interface that wraps the `write_to` method, which writes `buf.len` bytes to the underlying data stream at a random `pos`.
|
||||
interface Reader {
|
||||
// read reads up to buf.len bytes and places
|
||||
// them into buf.
|
||||
// A type that implements this should return
|
||||
// `io.Eof` on end of stream (EOF) instead of just returning 0
|
||||
mut:
|
||||
read(mut buf []u8) !int
|
||||
}
|
||||
Reader represents a stream of data that can be read.
|
||||
interface ReaderWriter {
|
||||
Reader
|
||||
Writer
|
||||
}
|
||||
ReaderWriter represents a stream that can be read and written.
|
||||
interface Writer {
|
||||
mut:
|
||||
write(buf []u8) !int
|
||||
}
|
||||
Writer is the interface that wraps the `write` method, which writes `buf.len` bytes to the underlying data stream.
|
||||
fn (mut r ReaderWriterImpl) read(mut buf []u8) !int
|
||||
read reads up to `buf.len` bytes into `buf`. It returns the number of bytes read or any error encountered.
|
||||
fn (mut r ReaderWriterImpl) write(buf []u8) !int
|
||||
write writes `buf.len` bytes from `buf` to the underlying data stream. It returns the number of bytes written or any error encountered.
|
||||
struct BufferedReadLineConfig {
|
||||
pub:
|
||||
delim u8 = `\n` // line delimiter
|
||||
}
|
||||
BufferedReadLineConfig are options that can be given to the read_line() function.
|
||||
struct BufferedReader {
|
||||
mut:
|
||||
reader Reader
|
||||
buf []u8
|
||||
offset int // current offset in the buffer
|
||||
len int
|
||||
fails int // how many times fill_buffer has read 0 bytes in a row
|
||||
mfails int // maximum fails, after which we can assume that the stream has ended
|
||||
pub mut:
|
||||
end_of_stream bool // whether we reached the end of the upstream reader
|
||||
total_read int // total number of bytes read
|
||||
}
|
||||
BufferedReader provides a buffered interface for a reader.
|
||||
fn (mut r BufferedReader) read(mut buf []u8) !int
|
||||
read fufills the Reader interface.
|
||||
fn (mut r BufferedReader) free()
|
||||
free deallocates the memory for a buffered reader's internal buffer.
|
||||
fn (r BufferedReader) end_of_stream() bool
|
||||
end_of_stream returns whether the end of the stream was reached.
|
||||
fn (mut r BufferedReader) read_line(config BufferedReadLineConfig) !string
|
||||
read_line attempts to read a line from the buffered reader. It will read until it finds the specified line delimiter such as (\n, the default or \0) or the end of stream.
|
||||
struct BufferedReaderConfig {
|
||||
pub:
|
||||
reader Reader
|
||||
cap int = 128 * 1024 // large for fast reading of big(ish) files
|
||||
retries int = 2 // how many times to retry before assuming the stream ended
|
||||
}
|
||||
BufferedReaderConfig are options that can be given to a buffered reader.
|
||||
struct Eof {
|
||||
Error
|
||||
}
|
||||
/ Eof error means that we reach the end of the stream.
|
||||
struct MultiWriter {
|
||||
pub mut:
|
||||
writers []Writer
|
||||
}
|
||||
MultiWriter writes to all its writers.
|
||||
fn (mut m MultiWriter) write(buf []u8) !int
|
||||
write writes to all writers of the MultiWriter. Returns the length of bytes written. If any writer fails to write the full length an error is returned and writing to other writers stops. If any writer returns an error the error is returned immediately and writing to other writers stops.
|
||||
struct NotExpected {
|
||||
cause string
|
||||
code int
|
||||
}
|
||||
NotExpected is a generic error that means that we receave a not expected error.
|
||||
struct ReadAllConfig {
|
||||
pub:
|
||||
read_to_end_of_stream bool
|
||||
reader Reader
|
||||
}
|
||||
ReadAllConfig allows options to be passed for the behaviour of read_all.
|
||||
354
aiprompts/net.md
Normal file
354
aiprompts/net.md
Normal file
@@ -0,0 +1,354 @@
|
||||
module net
|
||||
## Description
|
||||
|
||||
`net` provides networking functions. It is mostly a wrapper to BSD sockets, so you can listen on a port, connect to remote TCP/UDP services, and communicate with them.
|
||||
|
||||
const msg_nosignal = 0x4000
|
||||
const err_connection_refused = error_with_code('net: connection refused', errors_base + 10)
|
||||
const err_option_wrong_type = error_with_code('net: set_option_xxx option wrong type',
|
||||
errors_base + 3)
|
||||
const opts_can_set = [
|
||||
SocketOption.broadcast,
|
||||
.debug,
|
||||
.dont_route,
|
||||
.keep_alive,
|
||||
.linger,
|
||||
.oob_inline,
|
||||
.receive_buf_size,
|
||||
.receive_low_size,
|
||||
.receive_timeout,
|
||||
.send_buf_size,
|
||||
.send_low_size,
|
||||
.send_timeout,
|
||||
.ipv6_only,
|
||||
]
|
||||
const error_eagain = C.EAGAIN
|
||||
const err_port_out_of_range = error_with_code('net: port out of range', errors_base + 5)
|
||||
const opts_bool = [SocketOption.broadcast, .debug, .dont_route, .error, .keep_alive, .oob_inline]
|
||||
const err_connect_failed = error_with_code('net: connect failed', errors_base + 7)
|
||||
const errors_base = 0
|
||||
Well defined errors that are returned from socket functions
|
||||
const opts_int = [
|
||||
SocketOption.receive_buf_size,
|
||||
.receive_low_size,
|
||||
.receive_timeout,
|
||||
.send_buf_size,
|
||||
.send_low_size,
|
||||
.send_timeout,
|
||||
]
|
||||
const error_eintr = C.EINTR
|
||||
const error_ewouldblock = C.EWOULDBLOCK
|
||||
const err_no_udp_remote = error_with_code('net: no udp remote', errors_base + 6)
|
||||
const error_einprogress = C.EINPROGRESS
|
||||
const err_timed_out_code = errors_base + 9
|
||||
const err_connect_timed_out = error_with_code('net: connect timed out', errors_base + 8)
|
||||
const err_new_socket_failed = error_with_code('net: new_socket failed to create socket',
|
||||
errors_base + 1)
|
||||
const msg_dontwait = C.MSG_DONTWAIT
|
||||
const infinite_timeout = time.infinite
|
||||
infinite_timeout should be given to functions when an infinite_timeout is wanted (i.e. functions only ever return with data)
|
||||
const no_timeout = time.Duration(0)
|
||||
no_timeout should be given to functions when no timeout is wanted (i.e. all functions return instantly)
|
||||
const err_timed_out = error_with_code('net: op timed out', errors_base + 9)
|
||||
const tcp_default_read_timeout = 30 * time.second
|
||||
const err_option_not_settable = error_with_code('net: set_option_xxx option not settable',
|
||||
errors_base + 2)
|
||||
const tcp_default_write_timeout = 30 * time.second
|
||||
fn addr_from_socket_handle(handle int) Addr
|
||||
addr_from_socket_handle returns an address, based on the given integer socket `handle`
|
||||
fn close(handle int) !
|
||||
close a socket, given its file descriptor `handle`. In non-blocking mode, if `close()` does not succeed immediately, it causes an error to be propagated to `TcpSocket.close()`, which is not intended. Therefore, `select` is used just like `connect()`.
|
||||
fn default_tcp_dialer() Dialer
|
||||
default_tcp_dialer will give you an instance of Dialer, that is suitable for making new tcp connections.
|
||||
fn dial_tcp(oaddress string) !&TcpConn
|
||||
dial_tcp will try to create a new TcpConn to the given address.
|
||||
fn dial_tcp_with_bind(saddr string, laddr string) !&TcpConn
|
||||
dial_tcp_with_bind will bind the given local address `laddr` and dial.
|
||||
fn dial_udp(raddr string) !&UdpConn
|
||||
fn error_code() int
|
||||
fn listen_tcp(family AddrFamily, saddr string, options ListenOptions) !&TcpListener
|
||||
fn listen_udp(laddr string) !&UdpConn
|
||||
fn new_ip(port u16, addr [4]u8) Addr
|
||||
new_ip creates a new Addr from the IPv4 address family, based on the given port and addr
|
||||
fn new_ip6(port u16, addr [16]u8) Addr
|
||||
new_ip6 creates a new Addr from the IP6 address family, based on the given port and addr
|
||||
fn peer_addr_from_socket_handle(handle int) !Addr
|
||||
peer_addr_from_socket_handle retrieves the ip address and port number, given a socket handle
|
||||
fn resolve_addrs(addr string, family AddrFamily, @type SocketType) ![]Addr
|
||||
resolve_addrs converts the given `addr`, `family` and `@type` to a list of addresses
|
||||
fn resolve_addrs_fuzzy(addr string, @type SocketType) ![]Addr
|
||||
resolve_addrs converts the given `addr` and `@type` to a list of addresses
|
||||
fn resolve_ipaddrs(addr string, family AddrFamily, typ SocketType) ![]Addr
|
||||
resolve_ipaddrs converts the given `addr`, `family` and `typ` to a list of addresses
|
||||
fn set_blocking(handle int, state bool) !
|
||||
set_blocking will change the state of the socket to either blocking, when state is true, or non blocking (false).
|
||||
fn shutdown(handle int, config ShutdownConfig) int
|
||||
shutdown shutsdown a socket, given its file descriptor `handle`. By default it shuts it down in both directions, both for reading and for writing. You can change that using `net.shutdown(handle, how: .read)` or `net.shutdown(handle, how: .write)` In non-blocking mode, `shutdown()` may not succeed immediately, so `select` is also used to make sure that the function doesn't return an incorrect result.
|
||||
fn socket_error(potential_code int) !int
|
||||
fn socket_error_message(potential_code int, s string) !int
|
||||
fn split_address(addr string) !(string, u16)
|
||||
split_address splits an address into its host name and its port
|
||||
fn tcp_socket_from_handle_raw(sockfd int) TcpSocket
|
||||
tcp_socket_from_handle_raw is similar to tcp_socket_from_handle, but it does not modify any socket options
|
||||
fn validate_port(port int) !u16
|
||||
validate_port checks whether a port is valid and returns the port or an error
|
||||
fn wrap_error(error_code int) !
|
||||
interface Connection {
|
||||
addr() !Addr
|
||||
peer_addr() !Addr
|
||||
mut:
|
||||
read(mut []u8) !int
|
||||
write([]u8) !int
|
||||
close() !
|
||||
}
|
||||
Connection provides a generic SOCK_STREAM style interface that protocols can use as a base connection object to support TCP, UNIX Domain Sockets and various proxying solutions.
|
||||
interface Dialer {
|
||||
dial(address string) !Connection
|
||||
}
|
||||
Dialer is an abstract dialer interface for producing connections to adresses.
|
||||
fn (mut s TcpSocket) set_option_bool(opt SocketOption, value bool) !
|
||||
fn (mut s TcpSocket) set_option_int(opt SocketOption, value int) !
|
||||
fn (mut s TcpSocket) set_dualstack(on bool) !
|
||||
fn (mut s TcpSocket) bind(addr string) !
|
||||
bind a local rddress for TcpSocket
|
||||
fn (mut s UdpSocket) set_option_bool(opt SocketOption, value bool) !
|
||||
fn (mut s UdpSocket) set_dualstack(on bool) !
|
||||
enum AddrFamily {
|
||||
unix = C.AF_UNIX
|
||||
ip = C.AF_INET
|
||||
ip6 = C.AF_INET6
|
||||
unspec = C.AF_UNSPEC
|
||||
}
|
||||
AddrFamily are the available address families
|
||||
enum ShutdownDirection {
|
||||
read
|
||||
write
|
||||
read_and_write
|
||||
}
|
||||
ShutdownDirection is used by `net.shutdown`, for specifying the direction for which the communication will be cut.
|
||||
enum SocketOption {
|
||||
// TODO: SO_ACCEPT_CONN is not here because windows doesn't support it
|
||||
// and there is no easy way to define it
|
||||
broadcast = C.SO_BROADCAST
|
||||
debug = C.SO_DEBUG
|
||||
dont_route = C.SO_DONTROUTE
|
||||
error = C.SO_ERROR
|
||||
keep_alive = C.SO_KEEPALIVE
|
||||
linger = C.SO_LINGER
|
||||
oob_inline = C.SO_OOBINLINE
|
||||
reuse_addr = C.SO_REUSEADDR
|
||||
receive_buf_size = C.SO_RCVBUF
|
||||
receive_low_size = C.SO_RCVLOWAT
|
||||
receive_timeout = C.SO_RCVTIMEO
|
||||
send_buf_size = C.SO_SNDBUF
|
||||
send_low_size = C.SO_SNDLOWAT
|
||||
send_timeout = C.SO_SNDTIMEO
|
||||
socket_type = C.SO_TYPE
|
||||
ipv6_only = C.IPV6_V6ONLY
|
||||
}
|
||||
enum SocketType {
|
||||
udp = C.SOCK_DGRAM
|
||||
tcp = C.SOCK_STREAM
|
||||
seqpacket = C.SOCK_SEQPACKET
|
||||
}
|
||||
SocketType are the available sockets
|
||||
struct Addr {
|
||||
pub:
|
||||
len u8
|
||||
f u8
|
||||
addr AddrData
|
||||
}
|
||||
fn (a Addr) family() AddrFamily
|
||||
family returns the family/kind of the given address `a`
|
||||
fn (a Addr) len() u32
|
||||
len returns the length in bytes of the address `a`, depending on its family
|
||||
fn (a Addr) port() !u16
|
||||
port returns the ip or ip6 port of the given address `a`
|
||||
fn (a Addr) str() string
|
||||
str returns a string representation of the address `a`
|
||||
struct C.addrinfo {
|
||||
mut:
|
||||
ai_family int
|
||||
ai_socktype int
|
||||
ai_flags int
|
||||
ai_protocol int
|
||||
ai_addrlen int
|
||||
ai_addr voidptr
|
||||
ai_canonname voidptr
|
||||
ai_next voidptr
|
||||
}
|
||||
struct C.fd_set {}
|
||||
struct C.sockaddr_in {
|
||||
mut:
|
||||
sin_len u8
|
||||
sin_family u8
|
||||
sin_port u16
|
||||
sin_addr u32
|
||||
sin_zero [8]char
|
||||
}
|
||||
struct C.sockaddr_in6 {
|
||||
mut:
|
||||
// 1 + 1 + 2 + 4 + 16 + 4 = 28;
|
||||
sin6_len u8 // 1
|
||||
sin6_family u8 // 1
|
||||
sin6_port u16 // 2
|
||||
sin6_flowinfo u32 // 4
|
||||
sin6_addr [16]u8 // 16
|
||||
sin6_scope_id u32 // 4
|
||||
}
|
||||
struct C.sockaddr_un {
|
||||
mut:
|
||||
sun_len u8
|
||||
sun_family u8
|
||||
sun_path [max_unix_path]char
|
||||
}
|
||||
struct Ip {
|
||||
port u16
|
||||
addr [4]u8
|
||||
// Pad to size so that socket functions
|
||||
// dont complain to us (see in.h and bind())
|
||||
// TODO(emily): I would really like to use
|
||||
// some constant calculations here
|
||||
// so that this doesnt have to be hardcoded
|
||||
sin_pad [8]u8
|
||||
}
|
||||
fn (a Ip) str() string
|
||||
str returns a string representation of `a`
|
||||
struct Ip6 {
|
||||
port u16
|
||||
flow_info u32
|
||||
addr [16]u8
|
||||
scope_id u32
|
||||
}
|
||||
fn (a Ip6) str() string
|
||||
str returns a string representation of `a`
|
||||
struct ListenOptions {
|
||||
pub:
|
||||
dualstack bool = true
|
||||
backlog int = 128
|
||||
}
|
||||
struct ShutdownConfig {
|
||||
pub:
|
||||
how ShutdownDirection = .read_and_write
|
||||
}
|
||||
struct Socket {
|
||||
pub:
|
||||
handle int
|
||||
}
|
||||
fn (s &Socket) address() !Addr
|
||||
address gets the address of a socket
|
||||
struct TCPDialer {}
|
||||
TCPDialer is a concrete instance of the Dialer interface, for creating tcp connections.
|
||||
fn (t TCPDialer) dial(address string) !Connection
|
||||
dial will try to create a new abstract connection to the given address. It will return an error, if that is not possible.
|
||||
struct TcpConn {
|
||||
pub mut:
|
||||
sock TcpSocket
|
||||
handle int
|
||||
write_deadline time.Time
|
||||
read_deadline time.Time
|
||||
read_timeout time.Duration
|
||||
write_timeout time.Duration
|
||||
is_blocking bool = true
|
||||
}
|
||||
fn (c &TcpConn) addr() !Addr
|
||||
fn (mut c TcpConn) close() !
|
||||
close closes the tcp connection
|
||||
fn (mut con TcpConn) get_blocking() bool
|
||||
get_blocking returns whether the connection is in a blocking state, that is calls to .read_line, C.recv etc will block till there is new data arrived, instead of returning immediately.
|
||||
fn (c &TcpConn) peer_addr() !Addr
|
||||
peer_addr retrieves the ip address and port number used by the peer
|
||||
fn (c &TcpConn) peer_ip() !string
|
||||
peer_ip retrieves the ip address used by the peer, and returns it as a string
|
||||
fn (c TcpConn) read(mut buf []u8) !int
|
||||
read reads data from the tcp connection into the mutable buffer `buf`. The number of bytes read is limited to the length of the buffer `buf.len`. The returned value is the number of read bytes (between 0 and `buf.len`).
|
||||
fn (mut c TcpConn) read_deadline() !time.Time
|
||||
fn (mut con TcpConn) read_line() string
|
||||
read_line is a *simple*, *non customizable*, blocking line reader. It will return a line, ending with LF, or just '', on EOF.
|
||||
|
||||
Note: if you want more control over the buffer, please use a buffered IO reader instead: `io.new_buffered_reader({reader: io.make_reader(con)})`
|
||||
fn (mut con TcpConn) read_line_max(max_line_len int) string
|
||||
read_line_max is a *simple*, *non customizable*, blocking line reader. It will return a line, ending with LF, '' on EOF. It stops reading, when the result line length exceeds max_line_len.
|
||||
fn (c TcpConn) read_ptr(buf_ptr &u8, len int) !int
|
||||
read_ptr reads data from the tcp connection to the given buffer. It reads at most `len` bytes. It returns the number of actually read bytes, which can vary between 0 to `len`.
|
||||
fn (c &TcpConn) read_timeout() time.Duration
|
||||
fn (mut con TcpConn) set_blocking(state bool) !
|
||||
set_blocking will change the state of the connection to either blocking, when state is true, or non blocking (false). The default for `net` tcp connections is the blocking mode. Calling .read_line will set the connection to blocking mode. In general, changing the blocking mode after a successful connection may cause unexpected surprises, so this function is not recommended to be called anywhere but for this file.
|
||||
fn (mut c TcpConn) set_read_deadline(deadline time.Time)
|
||||
fn (mut c TcpConn) set_read_timeout(t time.Duration)
|
||||
fn (mut c TcpConn) set_sock() !
|
||||
set_sock initialises the c.sock field. It should be called after `.accept_only()!`.
|
||||
|
||||
Note: just use `.accept()!`. In most cases it is simpler, and calls `.set_sock()!` for you.
|
||||
fn (mut c TcpConn) set_write_deadline(deadline time.Time)
|
||||
fn (mut c TcpConn) set_write_timeout(t time.Duration)
|
||||
fn (c TcpConn) str() string
|
||||
fn (c TcpConn) wait_for_read() !
|
||||
fn (mut c TcpConn) wait_for_write() !
|
||||
fn (mut c TcpConn) write(bytes []u8) !int
|
||||
write blocks and attempts to write all data
|
||||
fn (mut c TcpConn) write_deadline() !time.Time
|
||||
fn (mut c TcpConn) write_ptr(b &u8, len int) !int
|
||||
write_ptr blocks and attempts to write all data
|
||||
fn (mut c TcpConn) write_string(s string) !int
|
||||
write_string blocks and attempts to write all data
|
||||
fn (c &TcpConn) write_timeout() time.Duration
|
||||
struct TcpListener {
|
||||
pub mut:
|
||||
sock TcpSocket
|
||||
accept_timeout time.Duration
|
||||
accept_deadline time.Time
|
||||
is_blocking bool = true
|
||||
}
|
||||
fn (mut l TcpListener) accept() !&TcpConn
|
||||
accept a tcp connection from an external source to the listener `l`.
|
||||
fn (mut l TcpListener) accept_only() !&TcpConn
|
||||
accept_only accepts a tcp connection from an external source to the listener `l`. Unlike `accept`, `accept_only` *will not call* `.set_sock()!` on the result, and is thus faster.
|
||||
|
||||
|
||||
|
||||
Note: you *need* to call `.set_sock()!` manually, before using theconnection after calling `.accept_only()!`, but that does not have to happen in the same thread that called `.accept_only()!`. The intention of this API, is to have a more efficient way to accept connections, that are later processed by a thread pool, while the main thread remains active, so that it can accept other connections. See also vlib/vweb/vweb.v .
|
||||
|
||||
If you do not need that, just call `.accept()!` instead, which will call `.set_sock()!` for you.
|
||||
fn (c &TcpListener) accept_deadline() !time.Time
|
||||
fn (mut c TcpListener) set_accept_deadline(deadline time.Time)
|
||||
fn (c &TcpListener) accept_timeout() time.Duration
|
||||
fn (mut c TcpListener) set_accept_timeout(t time.Duration)
|
||||
fn (mut c TcpListener) wait_for_accept() !
|
||||
fn (mut c TcpListener) close() !
|
||||
fn (c &TcpListener) addr() !Addr
|
||||
struct UdpConn {
|
||||
pub mut:
|
||||
sock UdpSocket
|
||||
mut:
|
||||
write_deadline time.Time
|
||||
read_deadline time.Time
|
||||
read_timeout time.Duration
|
||||
write_timeout time.Duration
|
||||
}
|
||||
fn (mut c UdpConn) write_ptr(b &u8, len int) !int
|
||||
sock := UdpSocket{ handle: sbase.handle l: local r: resolve_wrapper(raddr) } }
|
||||
fn (mut c UdpConn) write(buf []u8) !int
|
||||
fn (mut c UdpConn) write_string(s string) !int
|
||||
fn (mut c UdpConn) write_to_ptr(addr Addr, b &u8, len int) !int
|
||||
fn (mut c UdpConn) write_to(addr Addr, buf []u8) !int
|
||||
write_to blocks and writes the buf to the remote addr specified
|
||||
fn (mut c UdpConn) write_to_string(addr Addr, s string) !int
|
||||
write_to_string blocks and writes the buf to the remote addr specified
|
||||
fn (mut c UdpConn) read(mut buf []u8) !(int, Addr)
|
||||
read reads from the socket into buf up to buf.len returning the number of bytes read
|
||||
fn (c &UdpConn) read_deadline() !time.Time
|
||||
fn (mut c UdpConn) set_read_deadline(deadline time.Time)
|
||||
fn (c &UdpConn) write_deadline() !time.Time
|
||||
fn (mut c UdpConn) set_write_deadline(deadline time.Time)
|
||||
fn (c &UdpConn) read_timeout() time.Duration
|
||||
fn (mut c UdpConn) set_read_timeout(t time.Duration)
|
||||
fn (c &UdpConn) write_timeout() time.Duration
|
||||
fn (mut c UdpConn) set_write_timeout(t time.Duration)
|
||||
fn (mut c UdpConn) wait_for_read() !
|
||||
fn (mut c UdpConn) wait_for_write() !
|
||||
fn (c &UdpConn) str() string
|
||||
fn (mut c UdpConn) close() !
|
||||
struct Unix {
|
||||
path [max_unix_path]char
|
||||
}
|
||||
1
aiprompts/osal.md
Symbolic link
1
aiprompts/osal.md
Symbolic link
@@ -0,0 +1 @@
|
||||
../lib/osal/readme.md
|
||||
1
aiprompts/ourdb.md
Symbolic link
1
aiprompts/ourdb.md
Symbolic link
@@ -0,0 +1 @@
|
||||
../lib/data/ourdb/README.md
|
||||
1
aiprompts/ourtime.md
Symbolic link
1
aiprompts/ourtime.md
Symbolic link
@@ -0,0 +1 @@
|
||||
../lib/data/ourtime/readme.md
|
||||
1
aiprompts/paramsparser.md
Symbolic link
1
aiprompts/paramsparser.md
Symbolic link
@@ -0,0 +1 @@
|
||||
../lib/data/paramsparser/readme.md
|
||||
6
aiprompts/readme.md
Normal file
6
aiprompts/readme.md
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
|
||||
to generate relevant instructions
|
||||
|
||||
v doc -f text -m . -o stdout -readme -comments | code -
|
||||
|
||||
516
aiprompts/regex.md
Normal file
516
aiprompts/regex.md
Normal file
@@ -0,0 +1,516 @@
|
||||
|
||||
# Regex Library
|
||||
|
||||
#### Differences with PCRE:
|
||||
|
||||
> regex is not PCRE compatible.
|
||||
|
||||
- The basic element is the token not the sequence of symbols, and the mostsimple token, is a single character.
|
||||
| the OR operator acts on tokens, for example abc|ebc is notabc OR ebc. Instead it is evaluated like ab, followed by c OR e, followed by bc, because the token is the base element, not the sequence of symbols.
|
||||
- Two char classes with an OR in the middle is a syntax error.
|
||||
- The match operation stops at the end of the string. It does NOT stopat new line characters.
|
||||
- The tokens are the atomic units, used by this regex engine. They can be one of the following:
|
||||
- Simple char, This token is a simple single character like a or b etc.
|
||||
- Match positional delimiters
|
||||
- ^ Matches the start of the string.
|
||||
- $ Matches the end of the string.
|
||||
|
||||
#### Char class (cc)
|
||||
|
||||
- The character classes match all the chars specified inside. Use square brackets [ ] to enclose them.
|
||||
- The sequence of the chars in the character class, is evaluated with an OR op.
|
||||
- For example, the cc [abc], matches any character, that is a or b or c, but it doesn't match C or z.
|
||||
- Inside a cc, it is possible to specify a "range" of characters, for example [ad-h] is equivalent to writing [adefgh].
|
||||
- A cc can have different ranges at the same time, for example [a-zA-Z0-9] matches all the latin lowercase, uppercase and numeric characters.
|
||||
- It is possible to negate the meaning of a cc, using the caret char at the start of the cc like this: [^abc] . That matches every char that is NOT a or b or c.
|
||||
- A cc can contain meta-chars like: [a-z\d], that match all the lowercase latin chars a-z and all the digits \d.
|
||||
- It is possible to mix all the properties of the char class together.
|
||||
- Note > In order to match the - (minus) char, it must be preceded by > a backslash in the cc, for example [\-_\d\a] will match: > - - minus, > - _ underscore, > - \d numeric chars, > - \a lower case chars.
|
||||
|
||||
#### Meta-chars
|
||||
|
||||
- A meta-char is specified by a backslash, before a character. For example \w is the meta-char w.
|
||||
- A meta-char can match different types of characters.
|
||||
- \w matches a word char [a-zA-Z0-9_]
|
||||
- \W matches a non word char
|
||||
- \d matches a digit [0-9]
|
||||
- \D matches a non digit
|
||||
- \s matches a space char, one of [' ','\t','\n','\r','\v','\f']
|
||||
- \S matches a non space char
|
||||
- \a matches only a lowercase char [a-z]
|
||||
- \A matches only an uppercase char [A-Z]
|
||||
- \x41 match a byte of value 0x41, A in ascii code
|
||||
- \X414C match two consecutive bytes of value 0x414c, AL in ascii code
|
||||
|
||||
#### Quantifiers
|
||||
|
||||
Each token can have a quantifier, that specifies how many times the character must be matched.
|
||||
|
||||
Short quantifiers
|
||||
|
||||
- ? matches 0 or 1 time, a?b matches both ab or b
|
||||
- + matches at least 1 time, for example, a+ matches both aaa or a
|
||||
- * matches 0 or more times, for example, a*b matches aaab, ab or b
|
||||
|
||||
Long quantifiers
|
||||
|
||||
- {x} matches exactly x times, a{2} matches aa, but not aaa or a
|
||||
- {min,} matches at least min times, a{2,} matches aaa or aa, not a
|
||||
- {,max} matches at least 0 times and at maximum max times,for example, a{,2} matches a and aa, but doesn't match aaa- {min,max} matches from min times, to max times, for examplea{2,3} matches aa and aaa, but doesn't match a or aaaa
|
||||
- A long quantifier, may have a greedy off flag, that is the ? character after the brackets. {2,4}? means to match the minimum number of possible tokens, in this case 2.
|
||||
|
||||
|
||||
#### dot char
|
||||
|
||||
The dot is a particular meta-char, that matches "any char".
|
||||
|
||||
input:
|
||||
|
||||
'''abccc ddeef'''
|
||||
|
||||
The following table shows the query strings and the result of parsing source string.
|
||||
|
||||
| query string | result |
|
||||
|--------------|---------|
|
||||
| `.*c` | `abc` |
|
||||
| `.*dd` | `abcc dd` |
|
||||
| `ab.*e` | `abccc dde`|
|
||||
| `ab.{3} .*e` | `abccc dde`|
|
||||
|
||||
- The dot matches any character, until the next token match is satisfied.
|
||||
- Important Note: Consecutive dots, for example ..., are not allowed. > This will cause a syntax error. Use a quantifier instead.
|
||||
|
||||
OR token
|
||||
The token |, means a logic OR operation between two consecutive tokens, i.e. a|b matches a character that is a or b.
|
||||
|
||||
The OR token can work in a "chained way": a|(b)|cd means test first a, if the char is not a, then test the group (b), and if the group doesn't match too, finally test the token c.
|
||||
|
||||
Note > Unlike in PCRE, the OR operation works at token level! > It doesn't work at concatenation level!
|
||||
|
||||
Note > Two char classes with an OR in the middle is a syntax error.
|
||||
|
||||
That also means, that a query string like abc|bde is not equal to (abc)|(bde), but instead to ab(c|b)de. The OR operation works only for c|b, not at char concatenation level.
|
||||
|
||||
#### Groups
|
||||
|
||||
Groups are a method to create complex patterns with repetitions of blocks of tokens.
|
||||
|
||||
The groups are delimited by round brackets `( )`.
|
||||
|
||||
Groups can be nested. Like all other tokens, groups can have a quantifier too.
|
||||
|
||||
- `c(pa)+z` match `cpapaz` or `cpaz` or `cpapapaz`.
|
||||
- `(c(pa)+z ?)+` matches `cpaz cpapaz cpapapaz` or `cpapaz`
|
||||
|
||||
Let's analyze this last case, first we have the group `#0`, that is the most outer round brackets `(...)+`. This group has a quantifier `+`, that says to match its content at least one time.
|
||||
Then we have a simple char token `c`, and a second group `#1`: `(pa)+`. This group also tries to match the sequence `pa`, at least one time, as specified by the `+` quantifier.
|
||||
Then, we have another simple token `z` and another simple token `?`, i.e. the space char (ascii code 32) followed by the `?` quantifier, which means that the preceding space should be matched 0 or 1 time.
|
||||
|
||||
This explains why the `(c(pa)+z ?)+` query string, can match `cpaz cpapaz cpapapaz`.
|
||||
|
||||
In this implementation the groups are "capture groups". This means that the last temporal result for each group, can be retrieved from the `RE` struct.
|
||||
|
||||
The "capture groups" are stored as indexes in the field `groups`, that is an `[]int` inside the `RE` struct.
|
||||
|
||||
example
|
||||
|
||||
```v
|
||||
import regex
|
||||
|
||||
text := 'cpaz cpapaz cpapapaz'
|
||||
query := r'(c(pa)+z ?)+'
|
||||
mut re := regex.regex_opt(query) or { panic(err) }
|
||||
println(re.get_query())
|
||||
// #0(c#1(pa)+z ?)+
|
||||
// #0 and #1 are the ids of the groups, are shown if re.debug is 1 or 2
|
||||
start, end := re.match_string(text)
|
||||
// [start=0, end=20] match => [cpaz cpapaz cpapapaz]
|
||||
mut gi := 0
|
||||
for gi < re.groups.len {
|
||||
if re.groups[gi] >= 0 {
|
||||
println('${gi / 2} :[${text[re.groups[gi]..re.groups[gi + 1]]}]')
|
||||
}
|
||||
gi += 2
|
||||
}
|
||||
// groups captured
|
||||
// 0 :[cpapapaz]
|
||||
// 1 :[pa]
|
||||
```
|
||||
|
||||
Note > To show the group id number in the result of the get_query() > the flag debug of the RE object must be 1 or 2
|
||||
|
||||
In order to simplify the use of the captured groups, it is possible to use the utility function: get_group_list.
|
||||
|
||||
This function returns a list of groups using this support struct:
|
||||
|
||||
```v
|
||||
pub struct Re_group {
|
||||
pub:
|
||||
start int = -1
|
||||
end int = -1
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Groups example:
|
||||
|
||||
This simple function converts an HTML RGB value with 3 or 6 hex digits to an u32 value, this function is not optimized and it is only for didatical purpose. Example: #A0B0CC #A9F
|
||||
|
||||
```v
|
||||
import regex
|
||||
|
||||
fn convert_html_rgb(in_col string) u32 {
|
||||
mut n_digit := if in_col.len == 4 { 1 } else { 2 }
|
||||
mut col_mul := if in_col.len == 4 { 4 } else { 0 }
|
||||
// this is the regex query, it uses the V string interpolation to customize the regex query
|
||||
// Note: If you want to use escaped code you must use the r"" (raw) strings,
|
||||
// *** please remember that the V interpoaltion doesn't work on raw strings. ***
|
||||
query :='#([a-fA-F0-9]{${n_digit}})([a-fA-F0-9]{${n_digit}})([a-fA-F0-9]{${n_digit}})'
|
||||
mut re := regex.regex_opt(query) or { panic(err) }
|
||||
start, end := re.match_string(in_col)
|
||||
println('start: ${start}, end: ${end}')
|
||||
mut res := u32(0)
|
||||
if start >= 0 {
|
||||
group_list := re.get_group_list() // this is the utility function
|
||||
r := ('0x' + in_col[group_list[0].start..group_list[0].end]).int() << col_mul
|
||||
g := ('0x' + in_col[group_list[1].start..group_list[1].end]).int() << col_mul
|
||||
b := ('0x' + in_col[group_list[2].start..group_list[2].end]).int() << col_mul
|
||||
println('r: ${r} g: ${g} b: ${b}')
|
||||
res = u32(r) << 16 | u32(g) << 8 | u32(b)
|
||||
}
|
||||
return res
|
||||
}
|
||||
```
|
||||
|
||||
Other utility functions are get_group_by_id and get_group_bounds_by_id that get directly the string of a group using its id:
|
||||
|
||||
```v
|
||||
txt := 'my used string....'
|
||||
for g_index := 0; g_index < re.group_count; g_index++ {
|
||||
println('#${g_index} [${re.get_group_by_id(txt, g_index)}] \
|
||||
}] bounds: ${re.get_group_bounds_by_id(g_index)}')
|
||||
}
|
||||
```
|
||||
|
||||
More helper functions are listed in the Groups query functions section.
|
||||
|
||||
Groups Continuous saving
|
||||
In particular situations, it is useful to have a continuous group saving. This is possible by initializing the group_csave field in the RE struct.
|
||||
|
||||
This feature allows you to collect data in a continuous/streaming way.
|
||||
|
||||
In the example, we can pass a text, followed by an integer list, that we wish to collect. To achieve this task, we can use the continuous group saving, by enabling the right flag: re.group_csave_flag = true.
|
||||
|
||||
The .group_csave array will be filled then, following this logic:
|
||||
|
||||
re.group_csave[0] - number of total saved records re.group_csave[1+n*3] - id of the saved group re.group_csave[2+n*3] - start index in the source string of the saved group re.group_csave[3+n*3] - end index in the source string of the saved group
|
||||
|
||||
The regex will save groups, until it finishes, or finds that the array has no more space. If the space ends, no error is raised, and further records will not be saved.
|
||||
|
||||
```v
|
||||
import regex
|
||||
|
||||
txt := 'http://www.ciao.mondo/hello/pippo12_/pera.html'
|
||||
query := r'(?P<format>https?)|(?P<format>ftps?)://(?P<token>[\w_]+.)+'
|
||||
|
||||
mut re := regex.regex_opt(query) or { panic(err) }
|
||||
// println(re.get_code()) // uncomment to see the print of the regex execution code
|
||||
re.debug = 2 // enable maximum log
|
||||
println('String: ${txt}')
|
||||
println('Query : ${re.get_query()}')
|
||||
re.debug = 0 // disable log
|
||||
re.group_csave_flag = true
|
||||
start, end := re.match_string(txt)
|
||||
if start >= 0 {
|
||||
println('Match (${start}, ${end}) => [${txt[start..end]}]')
|
||||
} else {
|
||||
println('No Match')
|
||||
}
|
||||
|
||||
if re.group_csave_flag == true && start >= 0 && re.group_csave.len > 0 {
|
||||
println('cg: ${re.group_csave}')
|
||||
mut cs_i := 1
|
||||
for cs_i < re.group_csave[0] * 3 {
|
||||
g_id := re.group_csave[cs_i]
|
||||
st := re.group_csave[cs_i + 1]
|
||||
en := re.group_csave[cs_i + 2]
|
||||
println('cg[${g_id}] ${st} ${en}:[${txt[st..en]}]')
|
||||
cs_i += 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The output will be:
|
||||
|
||||
```v
|
||||
String: http://www.ciao.mondo/hello/pippo12_/pera.html
|
||||
Query : #0(?P<format>https?)|{8,14}#0(?P<format>ftps?)://#1(?P<token>[\w_]+.)+
|
||||
Match (0, 46) => [http://www.ciao.mondo/hello/pippo12_/pera.html]
|
||||
cg: [8, 0, 0, 4, 1, 7, 11, 1, 11, 16, 1, 16, 22, 1, 22, 28, 1, 28, 37, 1, 37, 42, 1, 42, 46]
|
||||
cg[0] 0 4:[http]
|
||||
cg[1] 7 11:[www.]
|
||||
cg[1] 11 16:[ciao.]
|
||||
cg[1] 16 22:[mondo/]
|
||||
cg[1] 22 28:[hello/]
|
||||
cg[1] 28 37:[pippo12_/]
|
||||
cg[1] 37 42:[pera.]
|
||||
cg[1] 42 46:[html]
|
||||
```v
|
||||
|
||||
#### Named capturing groups
|
||||
|
||||
This regex module supports partially the question mark ? PCRE syntax for groups.
|
||||
|
||||
- `(?:abcd)` non capturing group: the content of the group will not be saved.
|
||||
- `(?P<mygroup>abcdef)` named group: the group content is saved and labeled as mygroup.
|
||||
|
||||
The label of the groups is saved in the `group_map` of the `RE` struct, that is a map from `string` to `int`, where the value is the index in `group_csave` list of indexes.
|
||||
|
||||
Here is an example for how to use them:
|
||||
|
||||
```v
|
||||
import regex
|
||||
|
||||
|
||||
txt := 'http://www.ciao.mondo/hello/pippo12_/pera.html'
|
||||
query := r'(?P<format>https?)|(?P<format>ftps?)://(?P<token>[\w_]+.)+'
|
||||
|
||||
mut re := regex.regex_opt(query) or { panic(err) }
|
||||
// println(re.get_code()) // uncomment to see the print of the regex execution code
|
||||
re.debug = 2 // enable maximum log
|
||||
println('String: ${txt}')
|
||||
println('Query : ${re.get_query()}')
|
||||
re.debug = 0 // disable log
|
||||
start, end := re.match_string(txt)
|
||||
if start >= 0 {
|
||||
println('Match (${start}, ${end}) => [${txt[start..end]}]')
|
||||
} else {
|
||||
println('No Match')
|
||||
}
|
||||
|
||||
for name in re.group_map.keys() {
|
||||
println('group:${name} \t=> [${re.get_group_by_name(txt, name)}] \
|
||||
}] bounds: ${re.get_group_bounds_by_name(name)}')
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
String: http://www.ciao.mondo/hello/pippo12_/pera.html
|
||||
Query : #0(?P<format>https?)|{8,14}#0(?P<format>ftps?)://#1(?P<token>[\w_]+.)+
|
||||
Match (0, 46) => [http://www.ciao.mondo/hello/pippo12_/pera.html]
|
||||
group:format => [http] bounds: (0, 4)
|
||||
group:token => [html] bounds: (42, 46)
|
||||
```
|
||||
|
||||
In order to simplify the use of the named groups, it is possible to use a name map in the re struct, using the function `re.get_group_by_name`.
|
||||
|
||||
Here is a more complex example of using them:
|
||||
|
||||
```v
|
||||
import regex
|
||||
|
||||
// This function demonstrate the use of the named groups
|
||||
fn convert_html_rgb_n(in_col string) u32 {
|
||||
mut n_digit := if in_col.len == 4 { 1 } else { 2 }
|
||||
mut col_mul := if in_col.len == 4 { 4 } else { 0 }
|
||||
query := r'#(?P<red>[a-fA-F0-9]{${n_digit}})(?P<green>[a-fA-F0-9]{${n_digit}})(?P<blue>[a-fA-F0-9]{${n_digit}})'
|
||||
mut re := regex.regex_opt(query) or { panic(err) }
|
||||
start, end := re.match_string(in_col)
|
||||
println('start: ${start}, end: ${end}')
|
||||
mut res := u32(0)
|
||||
if start >= 0 {
|
||||
red_s, red_e := re.get_group_bounds_by_name('red')
|
||||
r := ('0x' + in_col[red_s..red_e]).int() << col_mul
|
||||
green_s, green_e := re.get_group_bounds_by_name('green')
|
||||
g := ('0x' + in_col[green_s..green_e]).int() << col_mul
|
||||
blue_s, blue_e := re.get_group_bounds_by_name('blue')
|
||||
b := ('0x' + in_col[blue_s..blue_e]).int() << col_mul
|
||||
println('r: ${r} g: ${g} b: ${b}')
|
||||
res = u32(r) << 16 | u32(g) << 8 | u32(b)
|
||||
}
|
||||
return res
|
||||
}
|
||||
```
|
||||
|
||||
Other utilities are `get_group_by_name` and `get_group_bounds_by_name`, that return the string of a group using its name:
|
||||
|
||||
```v
|
||||
txt := 'my used string....'
|
||||
for name in re.group_map.keys() {
|
||||
println('group:${name} \t=> [${re.get_group_by_name(txt, name)}] \
|
||||
}] bounds: ${re.get_group_bounds_by_name(name)}')
|
||||
}
|
||||
```
|
||||
|
||||
#### Groups query functions
|
||||
|
||||
These functions are helpers to query the captured groups
|
||||
|
||||
```v
|
||||
// get_group_bounds_by_name get a group boundaries by its name
|
||||
pub fn (re RE) get_group_bounds_by_name(group_name string) (int, int)
|
||||
|
||||
// get_group_by_name get a group string by its name
|
||||
pub fn (re RE) get_group_by_name(group_name string) string
|
||||
|
||||
// get_group_by_id get a group boundaries by its id
|
||||
pub fn (re RE) get_group_bounds_by_id(group_id int) (int, int)
|
||||
|
||||
// get_group_by_id get a group string by its id
|
||||
pub fn (re RE) get_group_by_id(in_txt string, group_id int) string
|
||||
|
||||
struct Re_group {
|
||||
pub:
|
||||
start int = -1
|
||||
end int = -1
|
||||
}
|
||||
|
||||
// get_group_list return a list of Re_group for the found groups
|
||||
pub fn (re RE) get_group_list() []Re_group
|
||||
|
||||
// get_group_list return a list of Re_group for the found groups
|
||||
pub fn (re RE) get_group_list() []Re_group
|
||||
Flags
|
||||
It is possible to set some flags in the regex parser, that change the behavior of the parser itself.
|
||||
|
||||
```
|
||||
|
||||
#### init the regex struct with flags (optional)
|
||||
|
||||
```v
|
||||
mut re := regex.new()
|
||||
re.flag = regex.f_bin
|
||||
// f_bin: parse a string as bytes, utf-8 management disabled.
|
||||
// f_efm: exit on the first char matches in the query, used by thefind function.
|
||||
//f_ms: matches only if the index of the start match is 0,same as ^ at the start of the query string.
|
||||
// f_me: matches only if the end index of the match is the last charof the input string, same as $ end of query string.
|
||||
// f_nl: stop the matching if found a new line char \n or \r
|
||||
```
|
||||
|
||||
|
||||
## Simplified initializer
|
||||
|
||||
```v
|
||||
// regex create a regex object from the query string and compile it
|
||||
pub fn regex_opt(in_query string) ?RE
|
||||
|
||||
// new create a RE of small size, usually sufficient for ordinary use
|
||||
pub fn new() RE
|
||||
|
||||
//After an initializer is used, the regex expression must be compiled with:
|
||||
|
||||
// compile_opt compile RE pattern string, returning an error if the compilation fails
|
||||
pub fn (mut re RE) compile_opt(pattern string) !
|
||||
```
|
||||
|
||||
## Matching Functions
|
||||
|
||||
```v
|
||||
|
||||
// match_string try to match the input string, return start and end index if found else start is -1
|
||||
pub fn (mut re RE) match_string(in_txt string) (int, int)
|
||||
```
|
||||
|
||||
## Find functions
|
||||
|
||||
```v
|
||||
// find try to find the first match in the input string
|
||||
// return start and end index if found else start is -1
|
||||
pub fn (mut re RE) find(in_txt string) (int, int)
|
||||
|
||||
// find_all find all the "non overlapping" occurrences of the matching pattern
|
||||
// return a list of start end indexes like: [3,4,6,8]
|
||||
// the matches are [3,4] and [6,8]
|
||||
pub fn (mut re RE) find_all(in_txt string) []int
|
||||
|
||||
// find_all_str find all the "non overlapping" occurrences of the match pattern
|
||||
// return a list of strings
|
||||
// the result is like ['first match','secon match']
|
||||
pub fn (mut re RE) find_all_str(in_txt string) []string
|
||||
```
|
||||
|
||||
## Replace functions
|
||||
|
||||
```v
|
||||
// replace return a string where the matches are replaced with the repl_str string,
|
||||
// this function supports groups in the replace string
|
||||
pub fn (mut re RE) replace(in_txt string, repl string) string
|
||||
|
||||
//replace string can include groups references:
|
||||
|
||||
|
||||
txt := 'Today it is a good day.'
|
||||
query := r'(a\w)[ ,.]'
|
||||
mut re := regex.regex_opt(query)?
|
||||
res := re.replace(txt, r'__[\0]__')
|
||||
|
||||
```
|
||||
in above example we used the group 0 in the replace string: \0, the result will be:
|
||||
|
||||
Today it is a good day. => Tod__[ay]__it is a good d__[ay]__
|
||||
|
||||
Note > In the replace strings can be used only groups from 0 to 9.
|
||||
|
||||
If the usage of groups in the replace process, is not needed, it is possible to use a quick function:
|
||||
|
||||
```v
|
||||
// replace_simple return a string where the matches are replaced with the replace string
|
||||
pub fn (mut re RE) replace_simple(in_txt string, repl string) string
|
||||
|
||||
//If it is needed to replace N instances of the found strings it is possible to use:
|
||||
|
||||
|
||||
// replace_n return a string where the first `count` matches are replaced with the repl_str string
|
||||
// `count` indicate the number of max replacements that will be done.
|
||||
// if count is > 0 the replace began from the start of the string toward the end
|
||||
// if count is < 0 the replace began from the end of the string toward the start
|
||||
// if count is 0 do nothing
|
||||
pub fn (mut re RE) replace_n(in_txt string, repl_str string, count int) string
|
||||
|
||||
//For complex find and replace operations, you can use replace_by_fn . The replace_by_fn, uses a custom replace callback function, thus allowing customizations. The custom callback function is called for every non overlapped find.
|
||||
// The custom callback function must be of the type:
|
||||
|
||||
|
||||
// type of function used for custom replace
|
||||
// in_txt source text
|
||||
// start index of the start of the match in in_txt
|
||||
// end index of the end of the match in in_txt
|
||||
// --- the match is in in_txt[start..end] ---
|
||||
fn (re RE, in_txt string, start int, end int) string
|
||||
The following example will clarify its usage:
|
||||
|
||||
```
|
||||
customized replace function example
|
||||
|
||||
```v
|
||||
|
||||
import regex
|
||||
//
|
||||
// it will be called on each non overlapped find
|
||||
|
||||
fn my_repl(re regex.RE, in_txt string, start int, end int) string {
|
||||
g0 := re.get_group_by_id(in_txt, 0)
|
||||
g1 := re.get_group_by_id(in_txt, 1)
|
||||
g2 := re.get_group_by_id(in_txt, 2)
|
||||
return'*${g0}*${g1}*${g2}*'
|
||||
}
|
||||
|
||||
fn main() {
|
||||
txt := 'today [John] is gone to his house with (Jack) and [Marie].'
|
||||
query := r'(.)(\A\w+)(.)'
|
||||
|
||||
mut re := regex.regex_opt(query) or { panic(err) }
|
||||
|
||||
result := re.replace_by_fn(txt, my_repl)
|
||||
println(result)
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```txt
|
||||
today *[*John*]* is gone to his house with *(*Jack*)* and *[*Marie*]*.
|
||||
```
|
||||
49
aiprompts/smtp.md
Normal file
49
aiprompts/smtp.md
Normal file
@@ -0,0 +1,49 @@
|
||||
module net.smtp
|
||||
|
||||
fn new_client(config Client) !&Client
|
||||
new_client returns a new SMTP client and connects to it
|
||||
enum BodyType {
|
||||
text
|
||||
html
|
||||
}
|
||||
struct Attachment {
|
||||
filename string
|
||||
bytes []u8
|
||||
cid string
|
||||
}
|
||||
struct Client {
|
||||
mut:
|
||||
conn net.TcpConn
|
||||
ssl_conn &ssl.SSLConn = unsafe { nil }
|
||||
reader ?&io.BufferedReader
|
||||
pub:
|
||||
server string
|
||||
port int = 25
|
||||
username string
|
||||
password string
|
||||
from string
|
||||
ssl bool
|
||||
starttls bool
|
||||
pub mut:
|
||||
is_open bool
|
||||
encrypted bool
|
||||
}
|
||||
fn (mut c Client) reconnect() !
|
||||
reconnect reconnects to the SMTP server if the connection was closed
|
||||
fn (mut c Client) send(config Mail) !
|
||||
send sends an email
|
||||
fn (mut c Client) quit() !
|
||||
quit closes the connection to the server
|
||||
struct Mail {
|
||||
pub:
|
||||
from string
|
||||
to string
|
||||
cc string
|
||||
bcc string
|
||||
date time.Time = time.now()
|
||||
subject string
|
||||
body_type BodyType
|
||||
body string
|
||||
attachments []Attachment
|
||||
boundary string
|
||||
}
|
||||
@@ -21,7 +21,7 @@ when I generate vlang scripts I will always use .vsh extension and use following
|
||||
## to do argument parsing use following examples
|
||||
|
||||
```v
|
||||
#!/usr/bin/env -S v -n -w -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import os
|
||||
import flag
|
||||
|
||||
311
aiprompts/time instructions.md
Normal file
311
aiprompts/time instructions.md
Normal file
@@ -0,0 +1,311 @@
|
||||
|
||||
## Description
|
||||
|
||||
V's `time` module, provides utilities for working with time and dates:
|
||||
|
||||
- parsing of time values expressed in one of the commonly used standard time/date formats
|
||||
- formatting of time values
|
||||
- arithmetic over times/durations
|
||||
- converting between local time and UTC (timezone support)
|
||||
- stop watches for accurately measuring time durations
|
||||
- sleeping for a period of time
|
||||
|
||||
## Examples
|
||||
|
||||
You can see the current time. [See](https://play.vlang.io/?query=c121a6dda7):
|
||||
|
||||
```v
|
||||
import time
|
||||
|
||||
println(time.now())
|
||||
```
|
||||
|
||||
`time.Time` values can be compared, [see](https://play.vlang.io/?query=133d1a0ce5):
|
||||
|
||||
```v
|
||||
import time
|
||||
|
||||
const time_to_test = time.Time{
|
||||
year: 1980
|
||||
month: 7
|
||||
day: 11
|
||||
hour: 21
|
||||
minute: 23
|
||||
second: 42
|
||||
nanosecond: 123456789
|
||||
}
|
||||
|
||||
println(time_to_test.format())
|
||||
|
||||
assert '1980-07-11 21:23' == time_to_test.format()
|
||||
assert '1980-07-11 21:23:42' == time_to_test.format_ss()
|
||||
assert '1980-07-11 21:23:42.123' == time_to_test.format_ss_milli()
|
||||
assert '1980-07-11 21:23:42.123456' == time_to_test.format_ss_micro()
|
||||
assert '1980-07-11 21:23:42.123456789' == time_to_test.format_ss_nano()
|
||||
```
|
||||
|
||||
You can also parse strings to produce time.Time values,
|
||||
[see](https://play.vlang.io/p/b02ca6027f):
|
||||
|
||||
```v
|
||||
import time
|
||||
|
||||
s := '2018-01-27 12:48:34'
|
||||
t := time.parse(s) or { panic('failing format: ${s} | err: ${err}') }
|
||||
println(t)
|
||||
println(t.unix())
|
||||
```
|
||||
|
||||
V's time module also has these parse methods:
|
||||
|
||||
```v ignore
|
||||
fn parse(s string) !Time
|
||||
fn parse_iso8601(s string) !Time
|
||||
fn parse_rfc2822(s string) !Time
|
||||
fn parse_rfc3339(s string) !Time
|
||||
```
|
||||
|
||||
Another very useful feature of the `time` module is the stop watch,
|
||||
for when you want to measure short time periods, elapsed while you
|
||||
executed other tasks. [See](https://play.vlang.io/?query=f6c008bc34):
|
||||
|
||||
```v
|
||||
import time
|
||||
|
||||
fn do_something() {
|
||||
time.sleep(510 * time.millisecond)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
sw := time.new_stopwatch()
|
||||
do_something()
|
||||
println('Note: do_something() took: ${sw.elapsed().milliseconds()} ms')
|
||||
}
|
||||
```
|
||||
|
||||
```vlang
|
||||
|
||||
module time
|
||||
|
||||
const second = Duration(1000 * millisecond)
|
||||
const long_months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
|
||||
'September', 'October', 'November', 'December']
|
||||
const nanosecond = Duration(1)
|
||||
const absolute_zero_year = i64(-292277022399)
|
||||
const days_string = 'MonTueWedThuFriSatSun'
|
||||
const long_days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']!
|
||||
const month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]!
|
||||
const seconds_per_hour = 60 * seconds_per_minute
|
||||
const millisecond = Duration(1000 * microsecond)
|
||||
const days_per_4_years = days_in_year * 4 + 1
|
||||
const microsecond = Duration(1000 * nanosecond)
|
||||
const days_per_400_years = days_in_year * 400 + 97
|
||||
const minute = Duration(60 * second)
|
||||
const days_before = [
|
||||
0,
|
||||
31,
|
||||
31 + 28,
|
||||
31 + 28 + 31,
|
||||
31 + 28 + 31 + 30,
|
||||
31 + 28 + 31 + 30 + 31,
|
||||
31 + 28 + 31 + 30 + 31 + 30,
|
||||
31 + 28 + 31 + 30 + 31 + 30 + 31,
|
||||
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
|
||||
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
|
||||
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
|
||||
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
|
||||
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
|
||||
]!
|
||||
const months_string = 'JanFebMarAprMayJunJulAugSepOctNovDec'
|
||||
const seconds_per_week = 7 * seconds_per_day
|
||||
const hour = Duration(60 * minute)
|
||||
const days_per_100_years = days_in_year * 100 + 24
|
||||
const seconds_per_minute = 60
|
||||
const days_in_year = 365
|
||||
const infinite = Duration(i64(9223372036854775807))
|
||||
const seconds_per_day = 24 * seconds_per_hour
|
||||
fn date_from_days_after_unix_epoch(days int) Time
|
||||
fn day_of_week(y int, m int, d int) int
|
||||
fn days_from_unix_epoch(year int, month int, day int) int
|
||||
fn days_in_month(month int, year int) !int
|
||||
fn is_leap_year(year int) bool
|
||||
fn new(t Time) Time
|
||||
fn new_stopwatch(opts StopWatchOptions) StopWatch
|
||||
fn new_time(t Time) Time
|
||||
fn now() Time
|
||||
fn offset() int
|
||||
fn parse(s string) !Time
|
||||
fn parse_format(s string, format string) !Time
|
||||
fn parse_iso8601(s string) !Time
|
||||
fn parse_rfc2822(s string) !Time
|
||||
fn parse_rfc3339(s string) !Time
|
||||
fn portable_timegm(t &C.tm) i64
|
||||
fn since(t Time) Duration
|
||||
fn sleep(duration Duration)
|
||||
fn sys_mono_now() u64
|
||||
fn ticks() i64
|
||||
fn unix(epoch i64) Time
|
||||
fn unix2(epoch i64, microsecond int) Time
|
||||
fn unix_microsecond(epoch i64, microsecond int) Time
|
||||
fn unix_nanosecond(abs_unix_timestamp i64, nanosecond int) Time
|
||||
fn utc() Time
|
||||
fn Time.new(t Time) Time
|
||||
type Duration = i64
|
||||
fn (d Duration) days() f64
|
||||
fn (d Duration) debug() string
|
||||
fn (d Duration) hours() f64
|
||||
fn (d Duration) microseconds() i64
|
||||
fn (d Duration) milliseconds() i64
|
||||
fn (d Duration) minutes() f64
|
||||
fn (d Duration) nanoseconds() i64
|
||||
fn (d Duration) seconds() f64
|
||||
fn (d Duration) str() string
|
||||
fn (d Duration) sys_milliseconds() int
|
||||
fn (d Duration) timespec() C.timespec
|
||||
enum FormatDate {
|
||||
ddmmyy
|
||||
ddmmyyyy
|
||||
mmddyy
|
||||
mmddyyyy
|
||||
mmmd
|
||||
mmmdd
|
||||
mmmddyy
|
||||
mmmddyyyy
|
||||
no_date
|
||||
yyyymmdd
|
||||
yymmdd
|
||||
}
|
||||
enum FormatDelimiter {
|
||||
dot
|
||||
hyphen
|
||||
slash
|
||||
space
|
||||
no_delimiter
|
||||
}
|
||||
enum FormatTime {
|
||||
hhmm12
|
||||
hhmm24
|
||||
hhmmss12
|
||||
hhmmss24
|
||||
hhmmss24_milli
|
||||
hhmmss24_micro
|
||||
hhmmss24_nano
|
||||
no_time
|
||||
}
|
||||
struct C.mach_timebase_info_data_t {
|
||||
numer u32
|
||||
denom u32
|
||||
}
|
||||
struct C.timespec {
|
||||
pub mut:
|
||||
tv_sec i64
|
||||
tv_nsec i64
|
||||
}
|
||||
struct C.timeval {
|
||||
pub:
|
||||
tv_sec u64
|
||||
tv_usec u64
|
||||
}
|
||||
struct C.tm {
|
||||
pub mut:
|
||||
tm_sec int
|
||||
tm_min int
|
||||
tm_hour int
|
||||
tm_mday int
|
||||
tm_mon int
|
||||
tm_year int
|
||||
tm_wday int
|
||||
tm_yday int
|
||||
tm_isdst int
|
||||
tm_gmtoff int
|
||||
}
|
||||
struct StopWatch {
|
||||
mut:
|
||||
elapsed u64
|
||||
pub mut:
|
||||
start u64
|
||||
end u64
|
||||
}
|
||||
fn (mut t StopWatch) start()
|
||||
fn (mut t StopWatch) restart()
|
||||
fn (mut t StopWatch) stop()
|
||||
fn (mut t StopWatch) pause()
|
||||
fn (t StopWatch) elapsed() Duration
|
||||
struct StopWatchOptions {
|
||||
pub:
|
||||
auto_start bool = true
|
||||
}
|
||||
struct Time {
|
||||
unix i64
|
||||
pub:
|
||||
year int
|
||||
month int
|
||||
day int
|
||||
hour int
|
||||
minute int
|
||||
second int
|
||||
nanosecond int
|
||||
is_local bool // used to make time.now().local().local() == time.now().local()
|
||||
|
||||
microsecond int @[deprecated: 'use t.nanosecond / 1000 instead'; deprecated_after: '2023-08-05']
|
||||
}
|
||||
fn (lhs Time) - (rhs Time) Duration
|
||||
fn (t1 Time) < (t2 Time) bool
|
||||
fn (t1 Time) == (t2 Time) bool
|
||||
fn (t Time) add(duration_in_nanosecond Duration) Time
|
||||
fn (t Time) add_days(days int) Time
|
||||
fn (t Time) add_seconds(seconds int) Time
|
||||
fn (t Time) as_local() Time
|
||||
fn (t Time) as_utc() Time
|
||||
fn (t Time) clean() string
|
||||
fn (t Time) clean12() string
|
||||
fn (t Time) custom_format(s string) string
|
||||
fn (t Time) day_of_week() int
|
||||
fn (t Time) days_from_unix_epoch() int
|
||||
fn (t Time) ddmmy() string
|
||||
fn (t Time) debug() string
|
||||
fn (t Time) format() string
|
||||
fn (t Time) format_rfc3339() string
|
||||
fn (t Time) format_rfc3339_nano() string
|
||||
fn (t Time) format_ss() string
|
||||
fn (t Time) format_ss_micro() string
|
||||
fn (t Time) format_ss_milli() string
|
||||
fn (t Time) format_ss_nano() string
|
||||
fn (t Time) get_fmt_date_str(fmt_dlmtr FormatDelimiter, fmt_date FormatDate) string
|
||||
fn (t Time) get_fmt_str(fmt_dlmtr FormatDelimiter, fmt_time FormatTime, fmt_date FormatDate) string
|
||||
fn (t Time) get_fmt_time_str(fmt_time FormatTime) string
|
||||
fn (t Time) hhmm() string
|
||||
fn (t Time) hhmm12() string
|
||||
fn (t Time) hhmmss() string
|
||||
fn (t Time) http_header_string() string
|
||||
fn (t Time) is_utc() bool
|
||||
fn (t Time) local() Time
|
||||
fn (t Time) local_to_utc() Time
|
||||
fn (t Time) long_weekday_str() string
|
||||
fn (t Time) md() string
|
||||
fn (t Time) relative() string
|
||||
fn (t Time) relative_short() string
|
||||
fn (t Time) smonth() string
|
||||
fn (t Time) str() string
|
||||
fn (t Time) strftime(fmt string) string
|
||||
fn (t Time) unix() i64
|
||||
fn (t Time) unix_micro() i64
|
||||
fn (t Time) unix_milli() i64
|
||||
fn (t Time) unix_nano() i64
|
||||
fn (t Time) unix_time() i64
|
||||
fn (t Time) unix_time_micro() i64
|
||||
fn (t Time) unix_time_milli() i64
|
||||
fn (t Time) unix_time_nano() i64
|
||||
fn (t Time) utc_string() string
|
||||
fn (u Time) utc_to_local() Time
|
||||
fn (t Time) weekday_str() string
|
||||
fn (t Time) year_day() int
|
||||
fn (t Time) ymmdd() string
|
||||
struct TimeParseError {
|
||||
Error
|
||||
code int
|
||||
message string
|
||||
}
|
||||
fn (err TimeParseError) msg() string
|
||||
|
||||
```
|
||||
43
aiprompts/ui console chalk.md
Normal file
43
aiprompts/ui console chalk.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# module ui.console.chalk
|
||||
|
||||
|
||||
Chalk offers functions:- `console.color_fg(text string, color string)` - To change the foreground color.
|
||||
- `console.color_bg(text string, color string)` - To change the background color.
|
||||
- `console.style(text string, style string)` - To change the text style.
|
||||
|
||||
Example:
|
||||
|
||||
```vlang
|
||||
import freeflowuniverse.crystallib.ui.console
|
||||
|
||||
# basic usage
|
||||
println('I am really ' + console.color_fg('happy', 'green'))
|
||||
|
||||
# you can also nest them
|
||||
println('I am really ' + console.color_fg(console.style('ANGRY', 'bold'), 'red'))
|
||||
```
|
||||
|
||||
Available colors:- black
|
||||
- red
|
||||
- green
|
||||
- yellow
|
||||
- blue
|
||||
- magenta
|
||||
- cyan
|
||||
- default
|
||||
- light_gray
|
||||
- dark_gray
|
||||
- light_red
|
||||
- light_green
|
||||
- light_yellow
|
||||
- light_blue
|
||||
- light_magenta
|
||||
- light_cyan
|
||||
- white
|
||||
|
||||
Available styles:- bold
|
||||
- dim
|
||||
- underline
|
||||
- blink
|
||||
- reverse
|
||||
- hidden
|
||||
205
aiprompts/ui console.md
Normal file
205
aiprompts/ui console.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# module ui.console
|
||||
|
||||
has mechanisms to print better to console, see the methods below
|
||||
|
||||
import as
|
||||
|
||||
```vlang
|
||||
import freeflowuniverse.crystallib.ui.console
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Methods
|
||||
|
||||
```v
|
||||
|
||||
fn clear()
|
||||
//reset the console screen
|
||||
|
||||
fn color_bg(c BackgroundColor) string
|
||||
// will give ansi codes to change background color . dont forget to call reset to change back to normal
|
||||
|
||||
fn color_fg(c ForegroundColor) string
|
||||
// will give ansi codes to change foreground color . don't forget to call reset to change back to normal
|
||||
|
||||
struct PrintArgs {
|
||||
pub mut:
|
||||
foreground ForegroundColor
|
||||
background BackgroundColor
|
||||
text string
|
||||
style Style
|
||||
reset_before bool = true
|
||||
reset_after bool = true
|
||||
}
|
||||
|
||||
fn cprint(args PrintArgs)
|
||||
// print with colors, reset...
|
||||
// ```
|
||||
// foreground ForegroundColor
|
||||
// background BackgroundColor
|
||||
// text string
|
||||
// style Style
|
||||
// reset_before bool = true
|
||||
// reset_after bool = true
|
||||
// ```
|
||||
|
||||
fn cprintln(args_ PrintArgs)
|
||||
|
||||
fn expand(txt_ string, l int, with string) string
|
||||
// expand text till length l, with string which is normally ' '
|
||||
|
||||
fn lf()
|
||||
line feed
|
||||
|
||||
fn new() UIConsole
|
||||
|
||||
fn print_array(arr [][]string, delimiter string, sort bool)
|
||||
// print 2 dimensional array, delimeter is between columns
|
||||
|
||||
fn print_debug(i IPrintable)
|
||||
|
||||
fn print_debug_title(title string, txt string)
|
||||
|
||||
fn print_green(txt string)
|
||||
|
||||
fn print_header(txt string)
|
||||
|
||||
fn print_item(txt string)
|
||||
|
||||
fn print_lf(nr int)
|
||||
|
||||
fn print_stderr(txt string)
|
||||
|
||||
fn print_stdout(txt string)
|
||||
|
||||
fn reset() string
|
||||
|
||||
fn silent_get() bool
|
||||
|
||||
fn silent_set()
|
||||
|
||||
fn silent_unset()
|
||||
|
||||
fn style(c Style) string
|
||||
// will give ansi codes to change style . don't forget to call reset to change back to normal
|
||||
|
||||
fn trim(c_ string) string
|
||||
|
||||
```
|
||||
|
||||
## Console Object
|
||||
|
||||
Is used to ask feedback to users
|
||||
|
||||
|
||||
```v
|
||||
|
||||
struct UIConsole {
|
||||
pub mut:
|
||||
x_max int = 80
|
||||
y_max int = 60
|
||||
prev_lf bool
|
||||
prev_title bool
|
||||
prev_item bool
|
||||
}
|
||||
|
||||
//DropDownArgs:
|
||||
// - description string
|
||||
// - items []string
|
||||
// - warning string
|
||||
// - clear bool = true
|
||||
|
||||
|
||||
fn (mut c UIConsole) ask_dropdown_int(args_ DropDownArgs) !int
|
||||
// return the dropdown as an int
|
||||
|
||||
fn (mut c UIConsole) ask_dropdown_multiple(args_ DropDownArgs) ![]string
|
||||
// result can be multiple, aloso can select all description string items []string warning string clear bool = true
|
||||
|
||||
fn (mut c UIConsole) ask_dropdown(args DropDownArgs) !string
|
||||
// will return the string as given as response description
|
||||
|
||||
// QuestionArgs:
|
||||
// - description string
|
||||
// - question string
|
||||
// - warning: string (if it goes wrong, which message to use)
|
||||
// - reset bool = true
|
||||
// - regex: to check what result need to be part of
|
||||
// - minlen: min nr of chars
|
||||
|
||||
fn (mut c UIConsole) ask_question(args QuestionArgs) !string
|
||||
|
||||
fn (mut c UIConsole) ask_time(args QuestionArgs) !string
|
||||
|
||||
fn (mut c UIConsole) ask_date(args QuestionArgs) !string
|
||||
|
||||
fn (mut c UIConsole) ask_yesno(args YesNoArgs) !bool
|
||||
// yes is true, no is false
|
||||
// args:
|
||||
// - description string
|
||||
// - question string
|
||||
// - warning string
|
||||
// - clear bool = true
|
||||
|
||||
fn (mut c UIConsole) reset()
|
||||
|
||||
fn (mut c UIConsole) status() string
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## enums
|
||||
|
||||
|
||||
```v
|
||||
enum BackgroundColor {
|
||||
default_color = 49 // 'default' is a reserved keyword in V
|
||||
black = 40
|
||||
red = 41
|
||||
green = 42
|
||||
yellow = 43
|
||||
blue = 44
|
||||
magenta = 45
|
||||
cyan = 46
|
||||
light_gray = 47
|
||||
dark_gray = 100
|
||||
light_red = 101
|
||||
light_green = 102
|
||||
light_yellow = 103
|
||||
light_blue = 104
|
||||
light_magenta = 105
|
||||
light_cyan = 106
|
||||
white = 107
|
||||
}
|
||||
enum ForegroundColor {
|
||||
default_color = 39 // 'default' is a reserved keyword in V
|
||||
white = 97
|
||||
black = 30
|
||||
red = 31
|
||||
green = 32
|
||||
yellow = 33
|
||||
blue = 34
|
||||
magenta = 35
|
||||
cyan = 36
|
||||
light_gray = 37
|
||||
dark_gray = 90
|
||||
light_red = 91
|
||||
light_green = 92
|
||||
light_yellow = 93
|
||||
light_blue = 94
|
||||
light_magenta = 95
|
||||
light_cyan = 96
|
||||
}
|
||||
enum Style {
|
||||
normal = 99
|
||||
bold = 1
|
||||
dim = 2
|
||||
underline = 4
|
||||
blink = 5
|
||||
reverse = 7
|
||||
hidden = 8
|
||||
}
|
||||
|
||||
```
|
||||
2403
aiprompts/v_manual_advanced.md
Normal file
2403
aiprompts/v_manual_advanced.md
Normal file
File diff suppressed because it is too large
Load Diff
1221
aiprompts/veb.md
Normal file
1221
aiprompts/veb.md
Normal file
File diff suppressed because it is too large
Load Diff
215
aiprompts/veb_assets.md
Normal file
215
aiprompts/veb_assets.md
Normal file
@@ -0,0 +1,215 @@
|
||||
module veb.assets
|
||||
# Assets
|
||||
|
||||
The asset manager for veb. You can use this asset manager to minify CSS and JavaScript files, combine them into a single file and to make sure the asset you're using exists.
|
||||
|
||||
## Usage
|
||||
|
||||
Add `AssetManager` to your App struct to use the asset manager.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v
|
||||
module main
|
||||
|
||||
import veb
|
||||
import veb.assets
|
||||
|
||||
pub struct Context {
|
||||
veb.Context
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
pub mut:
|
||||
am assets.AssetManager
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut app := &App{}
|
||||
veb.run[App, Context](mut app, 8080)
|
||||
}
|
||||
```
|
||||
|
||||
### Including assets
|
||||
|
||||
If you want to include an asset in your templates you can use the `include` method. First pass the type of asset (css or js), then specify the "include name" of an asset.
|
||||
|
||||
**Example:**
|
||||
|
||||
```html
|
||||
@{app.am.include(.css, 'main.css')}
|
||||
```
|
||||
|
||||
Will generate
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="/main.css" />
|
||||
```
|
||||
|
||||
### Adding assets
|
||||
|
||||
To add an asset use the `add` method. You must specify the path of the asset and what its include name will be: the name that you will use in templates.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
// add a css file at the path "css/main.css" and set its include name to "main.css"
|
||||
app.am.add(.css, 'css/main.css', 'main.css')
|
||||
```
|
||||
|
||||
### Minify and Combine assets
|
||||
|
||||
If you want to minify each asset you must set the `minify` field and specify the cache folder. Each assest you add is minifed and outputted in `cache_dir`.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
pub struct App {
|
||||
pub mut:
|
||||
am assets.AssetManager = assets.AssetManager{
|
||||
cache_dir: 'dist'
|
||||
minify: true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To combine the all currently added assets into a single file you must call the `combine` method and specify which asset type you want to combine.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
// `combine` returns the path of the minified file
|
||||
minified_file := app.am.combine(.css)!
|
||||
```
|
||||
|
||||
### Handle folders
|
||||
|
||||
You can use the asset manger in combination with veb's `StaticHandler` to serve assets in a folder as static assets.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
pub struct App {
|
||||
veb.StaticHandler
|
||||
pub mut:
|
||||
am assets.AssetManager
|
||||
}
|
||||
```
|
||||
|
||||
Let's say we have the following folder structure:
|
||||
|
||||
```
|
||||
assets/
|
||||
├── css/
|
||||
│ └── main.css
|
||||
└── js/
|
||||
└── main.js
|
||||
```
|
||||
|
||||
We can tell the asset manager to add all assets in the `static` folder
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
fn main() {
|
||||
mut app := &App{}
|
||||
// add all assets in the "assets" folder
|
||||
app.am.handle_assets('assets')!
|
||||
// serve all files in the "assets" folder as static files
|
||||
app.handle_static('assets', false)!
|
||||
// start the app
|
||||
veb.run[App, Context](mut app, 8080)
|
||||
}
|
||||
```
|
||||
|
||||
The include name of each minified asset will be set to its relative path, so if you want to include `main.css` in your template you would write `@{app.am.include('css/main.css')}`
|
||||
|
||||
#### Minify
|
||||
|
||||
If you add an asset folder and want to minify those assets you can call the `cleanup_cache` method to remove old files from the cache folder that are no longer needed.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
pub struct App {
|
||||
veb.StaticHandler
|
||||
pub mut:
|
||||
am assets.AssetManager = assets.AssetManager{
|
||||
cache_dir: 'dist'
|
||||
minify: true
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut app := &App{}
|
||||
// add all assets in the "assets" folder
|
||||
app.am.handle_assets('assets')!
|
||||
// remove all old cached files from the cache folder
|
||||
app.am.cleanup_cache()!
|
||||
// serve all files in the "assets" folder as static files
|
||||
app.handle_static('assets', false)!
|
||||
// start the app
|
||||
veb.run[App, Context](mut app, 8080)
|
||||
}
|
||||
```
|
||||
|
||||
#### Prefix the include name
|
||||
|
||||
You can add a custom prefix to the include name of assets when adding a folder.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
// add all assets in the "assets" folder
|
||||
app.am.handle_assets_at('assets', 'static')!
|
||||
```
|
||||
|
||||
Now if you want to include `main.css` you would write ``@{app.am.include('static/css/main.css')}`
|
||||
|
||||
fn minify_css(css string) string
|
||||
Todo: implement proper minification
|
||||
fn minify_js(js string) string
|
||||
Todo: implement proper minification
|
||||
enum AssetType {
|
||||
css
|
||||
js
|
||||
all
|
||||
}
|
||||
struct Asset {
|
||||
pub:
|
||||
kind AssetType
|
||||
file_path string
|
||||
last_modified time.Time
|
||||
include_name string
|
||||
}
|
||||
struct AssetManager {
|
||||
mut:
|
||||
css []Asset
|
||||
js []Asset
|
||||
cached_file_names []string
|
||||
pub mut:
|
||||
// when true assets will be minified
|
||||
minify bool
|
||||
// the directory to store the cached/combined files
|
||||
cache_dir string
|
||||
// how a combined file should be named. For example for css the extension '.css'
|
||||
// will be added to the end of `combined_file_name`
|
||||
combined_file_name string = 'combined'
|
||||
}
|
||||
fn (mut am AssetManager) handle_assets(directory_path string) !
|
||||
handle_assets recursively walks `directory_path` and adds any assets to the asset manager
|
||||
fn (mut am AssetManager) handle_assets_at(directory_path string, prepend string) !
|
||||
handle_assets_at recursively walks `directory_path` and adds any assets to the asset manager. The include name of assets are prefixed with `prepend`
|
||||
fn (am AssetManager) get_assets(asset_type AssetType) []Asset
|
||||
get all assets of type `asset_type`
|
||||
fn (mut am AssetManager) add(asset_type AssetType, file_path string, include_name string) !
|
||||
add an asset to the asset manager
|
||||
fn (mut am AssetManager) cleanup_cache() !
|
||||
cleanup_cache removes all files in the cache directory that aren't cached at the time this function is called
|
||||
fn (am AssetManager) exists(asset_type AssetType, include_name string) bool
|
||||
check if an asset is already added to the asset manager
|
||||
fn (am AssetManager) include(asset_type AssetType, include_name string) veb.RawHtml
|
||||
include css/js files in your veb app from templates
|
||||
Example: @{app.am.include(.css, 'main.css')}
|
||||
fn (mut am AssetManager) combine(asset_type AssetType) !string
|
||||
combine assets of type `asset_type` into a single file and return the outputted file path. If you call `combine` with asset type `all` the function will return an empty string, the minified files will be available at `combined_file_name`.`asset_type`
|
||||
128
aiprompts/veb_auth.md
Normal file
128
aiprompts/veb_auth.md
Normal file
@@ -0,0 +1,128 @@
|
||||
module veb.auth
|
||||
|
||||
## Description
|
||||
|
||||
`veb.auth` is a module that helps with common logic required for authentication.
|
||||
|
||||
It allows to easily generate hashed and salted passwords and to compare password hashes.
|
||||
|
||||
It also handles authentication tokens, including DB table creation and insertion. All DBs are supported.
|
||||
|
||||
## Usage
|
||||
|
||||
```v
|
||||
import veb
|
||||
import db.pg
|
||||
import veb.auth
|
||||
|
||||
pub struct App {
|
||||
veb.StaticHandler
|
||||
pub mut:
|
||||
db pg.DB
|
||||
auth auth.Auth[pg.DB] // or auth.Auth[sqlite.DB] etc
|
||||
}
|
||||
|
||||
const port = 8081
|
||||
|
||||
pub struct Context {
|
||||
veb.Context
|
||||
current_user User
|
||||
}
|
||||
|
||||
struct User {
|
||||
id int @[primary; sql: serial]
|
||||
name string
|
||||
password_hash string
|
||||
salt string
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut app := &App{
|
||||
db: pg.connect(host: 'localhost', user: 'postgres', password: '', dbname: 'postgres')!
|
||||
}
|
||||
app.auth = auth.new(app.db)
|
||||
veb.run[App, Context](mut app, port)
|
||||
}
|
||||
|
||||
@[post]
|
||||
pub fn (mut app App) register_user(mut ctx Context, name string, password string) veb.Result {
|
||||
salt := auth.generate_salt()
|
||||
new_user := User{
|
||||
name: name
|
||||
password_hash: auth.hash_password_with_salt(password, salt)
|
||||
salt: salt
|
||||
}
|
||||
sql app.db {
|
||||
insert new_user into User
|
||||
} or {}
|
||||
|
||||
// Get new user ID (until RETURNING is supported by ORM)
|
||||
if x := app.find_user_by_name(name) {
|
||||
// Generate and insert the token using user ID
|
||||
token := app.auth.add_token(x.id) or { '' }
|
||||
// Authenticate the user by adding the token to the cookies
|
||||
ctx.set_cookie(name: 'token', value: token)
|
||||
}
|
||||
|
||||
return ctx.redirect('/')
|
||||
}
|
||||
|
||||
@[post]
|
||||
pub fn (mut app App) login_post(mut ctx Context, name string, password string) veb.Result {
|
||||
user := app.find_user_by_name(name) or {
|
||||
ctx.error('Bad credentials')
|
||||
return ctx.redirect('/login')
|
||||
}
|
||||
// Verify user password using veb.auth
|
||||
if !auth.compare_password_with_hash(password, user.salt, user.password_hash) {
|
||||
ctx.error('Bad credentials')
|
||||
return ctx.redirect('/login')
|
||||
}
|
||||
// Find the user token in the Token table
|
||||
token := app.auth.add_token(user.id) or { '' }
|
||||
// Authenticate the user by adding the token to the cookies
|
||||
ctx.set_cookie(name: 'token', value: token)
|
||||
return ctx.redirect('/')
|
||||
}
|
||||
|
||||
pub fn (mut app App) find_user_by_name(name string) ?User {
|
||||
// ... db query
|
||||
return User{}
|
||||
}
|
||||
```
|
||||
|
||||
## Security considerations
|
||||
|
||||
`hash_password_with_salt` and its related functions use `sha256` for hashing with a single iteration. This is not secure for production use, and you should use a more secure hashing algorithm and multiple iterations.
|
||||
|
||||
See also:- [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html)
|
||||
|
||||
|
||||
fn compare_password_with_hash(plain_text_password string, salt string, hashed string) bool
|
||||
fn generate_salt() string
|
||||
fn hash_password_with_salt(plain_text_password string, salt string) string
|
||||
fn new[T](db T) Auth[T]
|
||||
fn set_rand_crypto_safe_seed()
|
||||
fn (mut app Auth[T]) add_token(user_id int) !string
|
||||
fn (mut app App) add_token(user_id int, ip string) !string {
|
||||
fn (app &Auth[T]) find_token(value string) ?Token
|
||||
fn (mut app Auth[T]) delete_tokens(user_id int) !
|
||||
struct Auth[T] {
|
||||
db T
|
||||
// pub:
|
||||
// salt string
|
||||
}
|
||||
struct Request {
|
||||
pub:
|
||||
client_id string
|
||||
client_secret string
|
||||
code string
|
||||
state string
|
||||
}
|
||||
struct Token {
|
||||
pub:
|
||||
id int @[primary; sql: serial]
|
||||
user_id int
|
||||
value string
|
||||
// ip string
|
||||
}
|
||||
257
aiprompts/veb_csrf.md
Normal file
257
aiprompts/veb_csrf.md
Normal file
@@ -0,0 +1,257 @@
|
||||
> module veb.csrf
|
||||
|
||||
# Cross-Site Request Forgery (CSRF) protection
|
||||
|
||||
This module implements the [double submit cookie][owasp] technique to protect routes from CSRF attacks.
|
||||
|
||||
CSRF is a type of attack that occurs when a malicious program/website (and others) causes a user's web browser to perform an action without them knowing. A web browser automatically sends cookies to a website when it performs a request, including session cookies. So if a user is authenticated on your website the website can not distinguish a forged request by a legitimate request.
|
||||
|
||||
## When to not add CSRF-protection
|
||||
|
||||
If you are creating a service that is intended to be used by other servers e.g. an API, you probably don't want CSRF-protection. An alternative would be to send an Authorization token in, and only in, an HTTP-header (like JSON Web Tokens). If you do that your website isn't vulnerable to CSRF-attacks.
|
||||
|
||||
## Usage
|
||||
|
||||
To enable CSRF-protection for your veb app you must embed the `CsrfContext` struct on your `Context` struct. You must also provide configuration options (see [configuration & security](#configuration--security-considerations)).
|
||||
|
||||
**Example:**
|
||||
|
||||
```v
|
||||
import veb
|
||||
import veb.csrf
|
||||
|
||||
pub struct Context {
|
||||
veb.Context
|
||||
csrf.CsrfContext
|
||||
}
|
||||
```
|
||||
|
||||
Change `secret` and `allowed_hosts` in a production environment!
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
const csrf_config := csrf.CsrfConfig{
|
||||
secret: 'my-secret'
|
||||
allowed_hosts: ['*']
|
||||
}
|
||||
```
|
||||
|
||||
### Middleware
|
||||
|
||||
Enable CSRF protection for all routes, or a certain route(s) by using veb's middleware.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
pub struct App {
|
||||
veb.Middleware[Context]
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut app := &App{}
|
||||
// register the CSRF middleware and pass our configuration
|
||||
// protect a specific route
|
||||
app.route_use('/login', csrf.middleware[Context](csrf_config))
|
||||
veb.run[App, Context](mut app, 8080)
|
||||
}
|
||||
```
|
||||
|
||||
### Setting the token
|
||||
|
||||
For the CSRF-protection to work we have to generate an anti-CSRF token and set it as an hidden input field on any form that will be submitted to the route we want to protect.
|
||||
|
||||
**Example:** _main.v_
|
||||
|
||||
```v ignore
|
||||
fn (app &App) index(mut ctx) veb.Result {
|
||||
// this function will set a cookie header and generate a CSRF token
|
||||
ctx.set_csrf_token(mut ctx)
|
||||
return $veb.html()
|
||||
}
|
||||
|
||||
@[post]
|
||||
fn (app &App) login(mut ctx, password string) veb.Result {
|
||||
// implement your own password validation here
|
||||
if password == 'password' {
|
||||
return ctx.text('You are logged in!')
|
||||
} else {
|
||||
return ctx.text('Invalid password!')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
_templates/index.html_
|
||||
|
||||
```html
|
||||
<h1>Log in</h1>
|
||||
<form method="POST" action="/login">
|
||||
@{ctx.csrf_token_input()}
|
||||
<label for="password">Password:</label>
|
||||
<input type="text" name="password" id="password" />
|
||||
<button type="submit">Log in</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
If we run the app with `v run main.v` and navigate to `http://localhost:8080/` we will see the login form and we can login using the password "password".
|
||||
|
||||
If we remove the hidden input, by removing the line `@{ctx.csrf_token_input()}` from our html code we will see an error message indicating that the CSRF token is not set or invalid! By default the CSRF module sends an HTTP-403 response when a token is invalid, if you want to send a custom response see the [advanced usage](#advanced-usage) section.
|
||||
|
||||
> **Note:** > Please read the security and configuration section! If you configure > the CSRF module in an unsafe way, the protection will be useless.
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
If you want more control over what routes are protected or what action you want to do when a CSRF-token is invalid, you can call `csrf.protect` yourself whenever you want to protect a route against CSRF attacks. This function returns `false` if the current CSRF token and cookie combination is not valid.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
@[post]
|
||||
fn (app &App) login(mut ctx, password string) veb.Result {
|
||||
if csrf.protect(mut ctx, csrf_config) == false {
|
||||
// CSRF verification failed!
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Obtaining the anti-CSRF token
|
||||
|
||||
When `set_csrf_token` is called the token is stored in the `csrf_token` field. You access this field directly to use it in an input field, or call `csrf_token_input`.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
fn (app &App) index(mut ctx) veb.Result {
|
||||
token := ctx.set_csrf_token(mut ctx)
|
||||
}
|
||||
```
|
||||
|
||||
### Clearing the anti-CSRF token
|
||||
|
||||
If you want to remove the anti-CSRF token and the cookie header you can call `clear_csrf_token`
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
ctx.clear_csrf_token()
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
This module implements the [double submit cookie][owasp] technique: a random token is generated, the CSRF-token. The hmac of this token and the secret key is stored in a cookie.
|
||||
|
||||
When a request is made, the CSRF-token should be placed inside a HTML form element. The CSRF-token the hmac of the CSRF-token in the formdata is compared to the cookie. If the values match, the request is accepted.
|
||||
|
||||
This approach has the advantage of being stateless: there is no need to store tokens on the server side and validate them. The token and cookie are bound cryptographically to each other so an attacker would need to know both values in order to make a CSRF-attack succeed. That is why is it important to **not leak the CSRF-token** via an url, or some other way. This is way by default the `HTTPOnly` flag on the cookie is set to true. See [client side CSRF][client-side-csrf] for more information.
|
||||
|
||||
This is a high level overview of the implementation.
|
||||
|
||||
## Configuration & Security Considerations
|
||||
|
||||
### The secret key
|
||||
|
||||
The secret key should be a random string that is not easily guessable.
|
||||
|
||||
### Sessions
|
||||
|
||||
If your app supports some kind of user sessions, it is recommended to cryptographically bind the CSRF-token to the users' session. You can do that by providing the name of the session ID cookie. If an attacker changes the session ID in the cookie, in the token or both the hmac will be different and the request will be rejected.
|
||||
|
||||
**Example**:
|
||||
|
||||
```v ignore
|
||||
csrf_config = csrf.CsrfConfig{
|
||||
// ...
|
||||
session_cookie: 'my_session_id_cookie_name'
|
||||
}
|
||||
```
|
||||
|
||||
### Safe Methods
|
||||
|
||||
The HTTP methods `GET`, `OPTIONS`, `HEAD` are considered [safe methods][mozilla-safe-methods] meaning they should not alter the state of an application. If a request with a "safe method" is made, the csrf protection will be skipped.
|
||||
|
||||
You can change which methods are considered safe by changing `CsrfConfig.safe_methods`.
|
||||
|
||||
### Allowed Hosts
|
||||
|
||||
By default, both the http Origin and Referer headers are checked and matched strictly to the values in `allowed_hosts`. That means that you need to include each subdomain.
|
||||
|
||||
If the value of `allowed_hosts` contains the wildcard: `'*'` the headers will not be checked.
|
||||
|
||||
#### Domain name matching
|
||||
|
||||
The following configuration will not allow requests made from `test.example.com`, only from `example.com`.
|
||||
|
||||
**Example**
|
||||
|
||||
```v ignore
|
||||
config := csrf.CsrfConfig{
|
||||
secret: '...'
|
||||
allowed_hosts: ['example.com']
|
||||
}
|
||||
```
|
||||
|
||||
#### Referer, Origin header check
|
||||
|
||||
In some cases (like if your server is behind a proxy), the Origin or Referer header will not be present. If that is your case you can set `check_origin_and_referer` to `false`. Request will now be accepted when the Origin _or_ Referer header is valid.
|
||||
|
||||
### Share csrf cookie with subdomains
|
||||
|
||||
If you need to share the CSRF-token cookie with subdomains, you can set `same_site` to `.same_site_lax_mode`.
|
||||
|
||||
## Configuration
|
||||
|
||||
All configuration options are defined in `CsrfConfig`.
|
||||
|
||||
[//]: # 'Sources' [owasp]: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie [client-side-csrf]: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#client-side-csrf [mozilla-safe-methods]: https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP
|
||||
|
||||
fn middleware[T](config CsrfConfig) veb.MiddlewareOptions[T]
|
||||
middleware returns a handler that you can use with veb's middleware
|
||||
fn protect(mut ctx veb.Context, config &CsrfConfig) bool
|
||||
protect returns false and sends an http 401 response when the csrf verification fails. protect will always return true if the current request method is in `config.safe_methods`.
|
||||
fn set_token(mut ctx veb.Context, config &CsrfConfig) string
|
||||
set_token returns the csrftoken and sets an encrypted cookie with the hmac of `config.get_secret` and the csrftoken
|
||||
struct CsrfConfig {
|
||||
pub:
|
||||
secret string
|
||||
// how long the random part of the csrf-token should be
|
||||
nonce_length int = 64
|
||||
// HTTP "safe" methods meaning they shouldn't alter state.
|
||||
// If a request with any of these methods is made, `protect` will always return true
|
||||
// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.1
|
||||
safe_methods []http.Method = [.get, .head, .options]
|
||||
// which hosts are allowed, enforced by checking the Origin and Referer header
|
||||
// if allowed_hosts contains '*' the check will be skipped.
|
||||
// Subdomains need to be included separately: a request from `"sub.example.com"`
|
||||
// will be rejected when `allowed_host = ['example.com']`.
|
||||
allowed_hosts []string
|
||||
// if set to true both the Referer and Origin headers must match `allowed_hosts`
|
||||
// else if either one is valid the request is accepted
|
||||
check_origin_and_referer bool = true
|
||||
// the name of the csrf-token in the hidden html input
|
||||
token_name string = 'csrftoken'
|
||||
// the name of the cookie that contains the session id
|
||||
session_cookie string
|
||||
// cookie options
|
||||
cookie_name string = 'csrftoken'
|
||||
same_site http.SameSite = .same_site_strict_mode
|
||||
cookie_path string = '/'
|
||||
// how long the cookie stays valid in seconds. Default is 30 days
|
||||
max_age int = 60 * 60 * 24 * 30
|
||||
cookie_domain string
|
||||
// whether the cookie can be send only over HTTPS
|
||||
secure bool
|
||||
}
|
||||
struct CsrfContext {
|
||||
pub mut:
|
||||
config CsrfConfig
|
||||
exempt bool
|
||||
// the csrftoken that should be placed in an html form
|
||||
csrf_token string
|
||||
}
|
||||
fn (mut ctx CsrfContext) set_csrf_token[T](mut user_context T) string
|
||||
set_token generates a new csrf_token and adds a Cookie to the response
|
||||
fn (ctx &CsrfContext) clear_csrf_token[T](mut user_context T)
|
||||
clear the csrf token and cookie header from the context
|
||||
fn (ctx &CsrfContext) csrf_token_input() veb.RawHtml
|
||||
csrf_token_input returns an HTML hidden input containing the csrf token
|
||||
83
aiprompts/veb_sse.md
Normal file
83
aiprompts/veb_sse.md
Normal file
@@ -0,0 +1,83 @@
|
||||
module veb.sse
|
||||
|
||||
# Server Sent Events
|
||||
|
||||
This module implements the server side of `Server Sent Events`, SSE. See [mozilla SSE][mozilla_sse] as well as [whatwg][whatwg html spec] for detailed description of the protocol, and a simple web browser client example.
|
||||
|
||||
## Usage
|
||||
|
||||
With SSE we want to keep the connection open, so we are able to keep sending events to the client. But if we hold the connection open indefinitely veb isn't able to process any other requests.
|
||||
|
||||
We can let veb know that it can continue processing other requests and that we will handle the connection ourself by calling `ctx.takeover_conn()` and returning an empty result with `veb.no_result()`. veb will not close the connection and we can handle the connection in a separate thread.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
import veb.sse
|
||||
|
||||
// endpoint handler for SSE connections
|
||||
fn (app &App) sse(mut ctx Context) veb.Result {
|
||||
// let veb know that the connection should not be closed
|
||||
ctx.takeover_conn()
|
||||
// handle the connection in a new thread
|
||||
spawn handle_sse_conn(mut ctx)
|
||||
// we will send a custom response ourself, so we can safely return an empty result
|
||||
return veb.no_result()
|
||||
}
|
||||
|
||||
fn handle_sse_conn(mut ctx Context) {
|
||||
// pass veb.Context
|
||||
mut sse_conn := sse.start_connection(mut ctx.Context)
|
||||
|
||||
// send a message every second 3 times
|
||||
for _ in 0.. 3 {
|
||||
time.sleep(time.second)
|
||||
sse_conn.send_message(data: 'ping') or { break }
|
||||
}
|
||||
// close the SSE connection
|
||||
sse_conn.close()
|
||||
}
|
||||
```
|
||||
|
||||
Javascript code:
|
||||
|
||||
```js
|
||||
const eventSource = new EventSource('/sse');
|
||||
|
||||
eventSource.addEventListener('message', (event) => {
|
||||
console.log('received message:', event.data);
|
||||
});
|
||||
|
||||
eventSource.addEventListener('close', () => {
|
||||
console.log('closing the connection');
|
||||
// prevent browser from reconnecting
|
||||
eventSource.close();
|
||||
});
|
||||
```
|
||||
|
||||
[mozilla_sse]: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events [whatwg html spec]: https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events
|
||||
|
||||
fn start_connection(mut ctx veb.Context) &SSEConnection
|
||||
start an SSE connection
|
||||
struct SSEConnection {
|
||||
pub mut:
|
||||
conn &net.TcpConn @[required]
|
||||
}
|
||||
fn (mut sse SSEConnection) send_message(message SSEMessage) !
|
||||
send_message sends a single message to the http client that listens for SSE. It does not close the connection, so you can use it many times in a loop.
|
||||
fn (mut sse SSEConnection) close()
|
||||
send a 'close' event and close the tcp connection.
|
||||
|
||||
|
||||
struct SSEMessage {
|
||||
pub mut:
|
||||
id string
|
||||
event string
|
||||
data string
|
||||
retry int
|
||||
}
|
||||
|
||||
|
||||
This module implements the server side of `Server Sent Events`. See https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event_stream_format as well as https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events for detailed description of the protocol, and a simple web browser client example.
|
||||
|
||||
> Event stream format > The event stream is a simple stream of text data which must be encoded using UTF-8. > Messages in the event stream are separated by a pair of newline characters. > A colon as the first character of a line is in essence a comment, and is ignored. > Note: The comment line can be used to prevent connections from timing out; > a server can send a comment periodically to keep the connection alive. > > Each message consists of one or more lines of text listing the fields for that message. > Each field is represented by the field name, followed by a colon, followed by the text > data for that field's value.
|
||||
907
aiprompts/vlang webserver veb instructions.md
Normal file
907
aiprompts/vlang webserver veb instructions.md
Normal file
@@ -0,0 +1,907 @@
|
||||
# veb - the V Web Server
|
||||
|
||||
A simple yet powerful web server with built-in routing, parameter handling, templating, and other
|
||||
features.
|
||||
## Quick Start
|
||||
|
||||
Run your veb app with a live reload via `v -d veb_livereload watch run .`
|
||||
|
||||
Now modifying any file in your web app (whether it's a .v file with the backend logic
|
||||
or a compiled .html template file) will
|
||||
result in an instant refresh of your app
|
||||
in the browser. No need to quit the app, rebuild it, and refresh the page in the browser!
|
||||
|
||||
## Deploying veb apps
|
||||
|
||||
All the code, including HTML templates, is in one binary file. That's all you need to deploy.
|
||||
Use the `-prod` flag when building for production.
|
||||
|
||||
## Getting Started
|
||||
|
||||
To start, you must import the module `veb` and define a structure which will
|
||||
represent your app and a structure which will represent the context of a request.
|
||||
These structures must be declared with the `pub` keyword.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v
|
||||
module main
|
||||
|
||||
import veb
|
||||
|
||||
pub struct User {
|
||||
pub mut:
|
||||
name string
|
||||
id int
|
||||
}
|
||||
|
||||
// Our context struct must embed `veb.Context`!
|
||||
pub struct Context {
|
||||
veb.Context
|
||||
pub mut:
|
||||
// In the context struct we store data that could be different
|
||||
// for each request. Like a User struct or a session id
|
||||
user User
|
||||
session_id string
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
pub:
|
||||
// In the app struct we store data that should be accessible by all endpoints.
|
||||
// For example, a database or configuration values.
|
||||
secret_key string
|
||||
}
|
||||
|
||||
// This is how endpoints are defined in veb. This is the index route
|
||||
pub fn (app &App) index(mut ctx Context) veb.Result {
|
||||
return ctx.text('Hello V! The secret key is "${app.secret_key}"')
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut app := &App{
|
||||
secret_key: 'secret'
|
||||
}
|
||||
// Pass the App and context type and start the web server on port 8080
|
||||
veb.run[App, Context](mut app, 8080)
|
||||
}
|
||||
```
|
||||
|
||||
You can use the `App` struct for data you want to keep during the lifetime of your program,
|
||||
or for data that you want to share between different routes.
|
||||
|
||||
A new `Context` struct is created every time a request is received,
|
||||
so it can contain different data for each request.
|
||||
|
||||
## Defining endpoints
|
||||
|
||||
To add endpoints to your web server, you must extend the `App` struct.
|
||||
For routing you can either use auto-mapping of function names or specify the path as an attribute.
|
||||
The function expects a parameter of your Context type and a response of the type `veb.Result`.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
// This endpoint can be accessed via http://server:port/hello
|
||||
pub fn (app &App) hello(mut ctx Context) veb.Result {
|
||||
return ctx.text('Hello')
|
||||
}
|
||||
|
||||
// This endpoint can be accessed via http://server:port/foo
|
||||
@['/foo']
|
||||
pub fn (app &App) world(mut ctx Context) veb.Result {
|
||||
return ctx.text('World')
|
||||
}
|
||||
```
|
||||
|
||||
### HTTP verbs
|
||||
|
||||
To use any HTTP verbs (or methods, as they are properly called),
|
||||
such as `@[post]`, `@[get]`, `@[put]`, `@[patch]` or `@[delete]`
|
||||
you can simply add the attribute before the function definition.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
// only GET requests to http://server:port/world are handled by this method
|
||||
@[get]
|
||||
pub fn (app &App) world(mut ctx Context) veb.Result {
|
||||
return ctx.text('World')
|
||||
}
|
||||
|
||||
// only POST requests to http://server:port/product/create are handled by this method
|
||||
@['/product/create'; post]
|
||||
pub fn (app &App) create_product(mut ctx Context) veb.Result {
|
||||
return ctx.text('product')
|
||||
}
|
||||
```
|
||||
|
||||
By default, endpoints are marked as GET requests only. It is also possible to
|
||||
add multiple HTTP verbs per endpoint.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
// only GET and POST requests to http://server:port/login are handled by this method
|
||||
@['/login'; get; post]
|
||||
pub fn (app &App) login(mut ctx Context) veb.Result {
|
||||
if ctx.req.method == .get {
|
||||
// show the login page on a GET request
|
||||
return ctx.html('<h1>Login page</h1><p>todo: make form</p>')
|
||||
} else {
|
||||
// request method is POST
|
||||
password := ctx.form['password']
|
||||
// validate password length
|
||||
if password.len < 12 {
|
||||
return ctx.text('password is too weak!')
|
||||
} else {
|
||||
// we receive a POST request, so we want to explicitly tell the browser
|
||||
// to send a GET request to the profile page.
|
||||
return ctx.redirect('/profile')
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Routes with Parameters
|
||||
|
||||
Parameters are passed directly to an endpoint route using the colon sign `:`. The route
|
||||
parameters are passed as arguments. V will cast the parameter to any of V's primitive types
|
||||
(`string`, `int` etc,).
|
||||
|
||||
To pass a parameter to an endpoint, you simply define it inside an attribute, e. g.
|
||||
`@['/hello/:user]`.
|
||||
After it is defined in the attribute, you have to add it as a function parameter.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
// V will pass the parameter 'user' as a string
|
||||
vvvv
|
||||
@['/hello/:user'] vvvv
|
||||
pub fn (app &App) hello_user(mut ctx Context, user string) veb.Result {
|
||||
return ctx.text('Hello ${user}')
|
||||
}
|
||||
|
||||
// V will pass the parameter 'id' as an int
|
||||
vv
|
||||
@['/document/:id'] vv
|
||||
pub fn (app &App) get_document(mut ctx Context, id int) veb.Result {
|
||||
return ctx.text('Hello ${user}')
|
||||
}
|
||||
```
|
||||
|
||||
If we visit http://localhost:port/hello/vaesel we would see the text `Hello vaesel`.
|
||||
|
||||
### Routes with Parameter Arrays
|
||||
|
||||
If you want multiple parameters in your route and if you want to parse the parameters
|
||||
yourself, or you want a wildcard route, you can add `...` after the `:` and name,
|
||||
e.g. `@['/:path...']`.
|
||||
|
||||
This will match all routes after `'/'`. For example, the url `/path/to/test` would give
|
||||
`path = '/path/to/test'`.
|
||||
|
||||
```v ignore
|
||||
vvv
|
||||
@['/:path...'] vvvv
|
||||
pub fn (app &App) wildcard(mut ctx Context, path string) veb.Result {
|
||||
return ctx.text('URL path = "${path}"')
|
||||
}
|
||||
```
|
||||
|
||||
### Query, Form and Files
|
||||
|
||||
You have direct access to query values by accessing the `query` field on your context struct.
|
||||
You are also able to access any formdata or files that were sent
|
||||
with the request with the fields `.form` and `.files` respectively.
|
||||
|
||||
In the following example, visiting http://localhost:port/user?name=veb we
|
||||
will see the text `Hello veb!`. And if we access the route without the `name` parameter,
|
||||
http://localhost:port/user, we will see the text `no user was found`,
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
@['/user'; get]
|
||||
pub fn (app &App) get_user_by_id(mut ctx Context) veb.Result {
|
||||
user_name := ctx.query['name'] or {
|
||||
// we can exit early and send a different response if no `name` parameter was passed
|
||||
return ctx.text('no user was found')
|
||||
}
|
||||
|
||||
return ctx.text('Hello ${user_name}!')
|
||||
}
|
||||
```
|
||||
|
||||
### Host
|
||||
|
||||
To restrict an endpoint to a specific host, you can use the `host` attribute
|
||||
followed by a colon `:` and the host name. You can test the Host feature locally
|
||||
by adding a host to the "hosts" file of your device.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
@['/'; host: 'example.com']
|
||||
pub fn (app &App) hello_web(mut ctx Context) veb.Result {
|
||||
return app.text('Hello World')
|
||||
}
|
||||
|
||||
@['/'; host: 'api.example.org']
|
||||
pub fn (app &App) hello_api(mut ctx Context) veb.Result {
|
||||
return ctx.text('Hello API')
|
||||
}
|
||||
|
||||
// define the handler without a host attribute last if you have conflicting paths.
|
||||
@['/']
|
||||
pub fn (app &App) hello_others(mut ctx Context) veb.Result {
|
||||
return ctx.text('Hello Others')
|
||||
}
|
||||
```
|
||||
|
||||
You can also [create a controller](#controller-with-hostname) to handle all requests from a specific
|
||||
host in one app struct.
|
||||
|
||||
### Route Matching Order
|
||||
|
||||
veb will match routes in the order that you define endpoints.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
@['/:path']
|
||||
pub fn (app &App) with_parameter(mut ctx Context, path string) veb.Result {
|
||||
return ctx.text('from with_parameter, path: "${path}"')
|
||||
}
|
||||
|
||||
@['/normal']
|
||||
pub fn (app &App) normal(mut ctx Context) veb.Result {
|
||||
return ctx.text('from normal')
|
||||
}
|
||||
```
|
||||
|
||||
In this example we defined an endpoint with a parameter first. If we access our app
|
||||
on the url http://localhost:port/normal we will not see `from normal`, but
|
||||
`from with_parameter, path: "normal"`.
|
||||
|
||||
### Custom not found page
|
||||
|
||||
You can implement a `not_found` endpoint that is called when a request is made, and no
|
||||
matching route is found to replace the default HTTP 404 not found page. This route
|
||||
has to be defined on our Context struct.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
pub fn (mut ctx Context) not_found() veb.Result {
|
||||
// set HTTP status 404
|
||||
ctx.res.set_status(.not_found)
|
||||
return ctx.html('<h1>Page not found!</h1>')
|
||||
}
|
||||
```
|
||||
|
||||
## Static files and website
|
||||
|
||||
veb also provides a way of handling static files. We can mount a folder at the root
|
||||
of our web app, or at a custom route. To start using static files we have to embed
|
||||
`veb.StaticHandler` on our app struct.
|
||||
|
||||
**Example:**
|
||||
|
||||
Let's say you have the following file structure:
|
||||
|
||||
```
|
||||
.
|
||||
├── static/
|
||||
│ ├── css/
|
||||
│ │ └── main.css
|
||||
│ └── js/
|
||||
│ └── main.js
|
||||
└── main.v
|
||||
```
|
||||
|
||||
If we want all the documents inside the `static` sub-directory to be publicly accessible, we can
|
||||
use `handle_static`.
|
||||
|
||||
> **Note:**
|
||||
> veb will recursively search the folder you mount; all the files inside that folder
|
||||
> will be publicly available.
|
||||
|
||||
_main.v_
|
||||
|
||||
```v
|
||||
module main
|
||||
|
||||
import veb
|
||||
|
||||
pub struct Context {
|
||||
veb.Context
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
veb.StaticHandler
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut app := &App{}
|
||||
|
||||
app.handle_static('static', false)!
|
||||
|
||||
veb.run[App, Context](mut app, 8080)
|
||||
}
|
||||
```
|
||||
|
||||
If we start the app with `v run main.v` we can access our `main.css` file at
|
||||
http://localhost:8080/static/css/main.css
|
||||
|
||||
### Mounting folders at specific locations
|
||||
|
||||
In the previous example the folder `static` was mounted at `/static`. We could also choose
|
||||
to mount the static folder at the root of our app: everything inside the `static` folder
|
||||
is available at `/`.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
// change the second argument to `true` to mount a folder at the app root
|
||||
app.handle_static('static', true)!
|
||||
```
|
||||
|
||||
We can now access `main.css` directly at http://localhost:8080/css/main.css.
|
||||
|
||||
If a request is made to the root of a static folder, veb will look for an
|
||||
`index.html` or `ìndex.htm` file and serve it if available.
|
||||
Thus, it's also a good way to host a complete website.
|
||||
An example is available [here](/examples/veb/static_website).
|
||||
|
||||
It is also possible to mount the `static` folder at a custom path.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
// mount the folder 'static' at path '/public', the path has to start with '/'
|
||||
app.mount_static_folder_at('static', '/public')
|
||||
```
|
||||
|
||||
If we run our app the `main.css` file is available at http://localhost:8080/public/main.css
|
||||
|
||||
### Adding a single static asset
|
||||
|
||||
If you don't want to mount an entire folder, but only a single file, you can use `serve_static`.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
// serve the `main.css` file at '/path/main.css'
|
||||
app.serve_static('/path/main.css', 'static/css/main.css')!
|
||||
```
|
||||
|
||||
### Dealing with MIME types
|
||||
|
||||
By default, veb will map the extension of a file to a MIME type. If any of your static file's
|
||||
extensions do not have a default MIME type in veb, veb will throw an error and you
|
||||
have to add your MIME type to `.static_mime_types` yourself.
|
||||
|
||||
**Example:**
|
||||
|
||||
Given the following file structure:
|
||||
|
||||
```
|
||||
.
|
||||
├── static/
|
||||
│ └── file.what
|
||||
└── main.v
|
||||
```
|
||||
|
||||
```v ignore
|
||||
app.handle_static('static', true)!
|
||||
```
|
||||
|
||||
This code will throw an error, because veb has no default MIME type for a `.what` file extension.
|
||||
|
||||
```
|
||||
unknown MIME type for file extension ".what"
|
||||
```
|
||||
|
||||
To fix this we have to provide a MIME type for the `.what` file extension:
|
||||
|
||||
```v ignore
|
||||
app.static_mime_types['.what'] = 'txt/plain'
|
||||
app.handle_static('static', true)!
|
||||
```
|
||||
|
||||
## Middleware
|
||||
|
||||
Middleware in web development is (loosely defined) a hidden layer that sits between
|
||||
what a user requests (the HTTP Request) and what a user sees (the HTTP Response).
|
||||
We can use this middleware layer to provide "hidden" functionality to our apps endpoints.
|
||||
|
||||
To use veb's middleware we have to embed `veb.Middleware` on our app struct and provide
|
||||
the type of which context struct should be used.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
pub struct App {
|
||||
veb.Middleware[Context]
|
||||
}
|
||||
```
|
||||
|
||||
### Use case
|
||||
|
||||
We could, for example, get the cookies for an HTTP request and check if the user has already
|
||||
accepted our cookie policy. Let's modify our Context struct to store whether the user has
|
||||
accepted our policy or not.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
pub struct Context {
|
||||
veb.Context
|
||||
pub mut:
|
||||
has_accepted_cookies bool
|
||||
}
|
||||
```
|
||||
|
||||
In veb middleware functions take a `mut` parameter with the type of your context struct
|
||||
and must return `bool`. We have full access to modify our Context struct!
|
||||
|
||||
The return value indicates to veb whether it can continue or has to stop. If we send a
|
||||
response to the client in a middleware function veb has to stop, so we return `false`.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
pub fn check_cookie_policy(mut ctx Context) bool {
|
||||
// get the cookie
|
||||
cookie_value := ctx.get_cookie('accepted_cookies') or { '' }
|
||||
// check if the cookie has been set
|
||||
if cookie_value == 'true' {
|
||||
ctx.has_accepted_cookies = true
|
||||
}
|
||||
// we don't send a response, so we must return true
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
We can check this value in an endpoint and return a different response.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
@['/only-cookies']
|
||||
pub fn (app &App) only_cookie_route(mut ctx Context) veb.Result {
|
||||
if ctx.has_accepted_cookies {
|
||||
return ctx.text('Welcome!')
|
||||
} else {
|
||||
return ctx.text('You must accept the cookie policy!')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There is one thing left for our middleware to work: we have to register our `only_cookie_route`
|
||||
function as middleware for our app. We must do this after the app is created and before the
|
||||
app is started.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
fn main() {
|
||||
mut app := &App{}
|
||||
|
||||
// register middleware for all routes
|
||||
app.use(handler: check_cookie_policy)
|
||||
|
||||
// Pass the App and context type and start the web server on port 8080
|
||||
veb.run[App, Context](mut app, 8080)
|
||||
}
|
||||
```
|
||||
|
||||
### Types of middleware
|
||||
|
||||
In the previous example we used so called "global" middleware. This type of middleware
|
||||
applies to every endpoint defined on our app struct; global. It is also possible
|
||||
to register middleware for only a certain route(s).
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
// register middleware only for the route '/auth'
|
||||
app.route_use('/auth', handler: auth_middleware)
|
||||
// register middleware only for the route '/documents/' with a parameter
|
||||
// e.g. '/documents/5'
|
||||
app.route_use('/documents/:id')
|
||||
// register middleware with a parameter array. The middleware will be registered
|
||||
// for all routes that start with '/user/' e.g. '/user/profile/update'
|
||||
app.route_use('/user/:path...')
|
||||
```
|
||||
|
||||
### Evaluation moment
|
||||
|
||||
By default, the registered middleware functions are executed *before* a method on your
|
||||
app struct is called. You can also change this behaviour to execute middleware functions
|
||||
*after* a method on your app struct is called, but before the response is sent!
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
pub fn modify_headers(mut ctx Context) bool {
|
||||
// add Content-Language: 'en-US' header to each response
|
||||
ctx.res.header.add(.content_language, 'en-US')
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
```v ignore
|
||||
app.use(handler: modify_headers, after: true)
|
||||
```
|
||||
|
||||
#### When to use which type
|
||||
|
||||
You could use "before" middleware to check and modify the HTTP request and you could use
|
||||
"after" middleware to validate the HTTP response that will be sent or do some cleanup.
|
||||
|
||||
Anything you can do in "before" middleware, you can do in "after" middleware.
|
||||
|
||||
### Evaluation order
|
||||
|
||||
veb will handle requests in the following order:
|
||||
|
||||
1. Execute global "before" middleware
|
||||
2. Execute "before" middleware that matches the requested route
|
||||
3. Execute the endpoint handler on your app struct
|
||||
4. Execute global "after" middleware
|
||||
5. Execute "after" middleware that matches the requested route
|
||||
|
||||
In each step, except for step `3`, veb will evaluate the middleware in the order that
|
||||
they are registered; when you call `app.use` or `app.route_use`.
|
||||
|
||||
### Early exit
|
||||
|
||||
If any middleware sends a response (and thus must return `false`) veb will not execute any
|
||||
other middleware, or the endpoint method, and immediately send the response.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
pub fn early_exit(mut ctx Context) bool {
|
||||
ctx.text('early exit')
|
||||
// we send a response from middleware, so we have to return false
|
||||
return false
|
||||
}
|
||||
|
||||
pub fn logger(mut ctx Context) bool {
|
||||
println('received request for "${ctx.req.url}"')
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
```v ignore
|
||||
app.use(handler: early_exit)
|
||||
app.use(handler: logger)
|
||||
```
|
||||
|
||||
Because we register `early_exit` before `logger` our logging middleware will never be executed!
|
||||
|
||||
## Controllers
|
||||
|
||||
Controllers can be used to split up your app logic so you are able to have one struct
|
||||
per "route group". E.g. a struct `Admin` for urls starting with `'/admin'` and a struct `Foo`
|
||||
for urls starting with `'/foo'`.
|
||||
|
||||
To use controllers we have to embed `veb.Controller` on
|
||||
our app struct and when we register a controller we also have to specify
|
||||
what the type of the context struct will be. That means that it is possible
|
||||
to have a different context struct for each controller and the main app struct.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v
|
||||
module main
|
||||
|
||||
import veb
|
||||
|
||||
pub struct Context {
|
||||
veb.Context
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
veb.Controller
|
||||
}
|
||||
|
||||
// this endpoint will be available at '/'
|
||||
pub fn (app &App) index(mut ctx Context) veb.Result {
|
||||
return ctx.text('from app')
|
||||
}
|
||||
|
||||
pub struct Admin {}
|
||||
|
||||
// this endpoint will be available at '/admin/'
|
||||
pub fn (app &Admin) index(mut ctx Context) veb.Result {
|
||||
return ctx.text('from admin')
|
||||
}
|
||||
|
||||
pub struct Foo {}
|
||||
|
||||
// this endpoint will be available at '/foo/'
|
||||
pub fn (app &Foo) index(mut ctx Context) veb.Result {
|
||||
return ctx.text('from foo')
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut app := &App{}
|
||||
|
||||
// register the controllers the same way as how we start a veb app
|
||||
mut admin_app := &Admin{}
|
||||
app.register_controller[Admin, Context]('/admin', mut admin_app)!
|
||||
|
||||
mut foo_app := &Foo{}
|
||||
app.register_controller[Foo, Context]('/foo', mut foo_app)!
|
||||
|
||||
veb.run[App, Context](mut app, 8080)
|
||||
}
|
||||
```
|
||||
|
||||
You can do everything with a controller struct as with a regular `App` struct.
|
||||
Register middleware, add static files and you can even register other controllers!
|
||||
|
||||
### Routing
|
||||
|
||||
Any route inside a controller struct is treated as a relative route to its controller namespace.
|
||||
|
||||
```v ignore
|
||||
@['/path']
|
||||
pub fn (app &Admin) path(mut ctx Context) veb.Result {
|
||||
return ctx.text('Admin')
|
||||
}
|
||||
```
|
||||
|
||||
When we registered the controller with
|
||||
`app.register_controller[Admin, Context]('/admin', mut admin_app)!`
|
||||
we told veb that the namespace of that controller is `'/admin'` so in this example we would
|
||||
see the text "Admin" if we navigate to the url `'/admin/path'`.
|
||||
|
||||
veb doesn't support duplicate routes, so if we add the following
|
||||
route to the example the code will produce an error.
|
||||
|
||||
```v ignore
|
||||
@['/admin/path']
|
||||
pub fn (app &App) admin_path(mut ctx Context) veb.Result {
|
||||
return ctx.text('Admin overwrite')
|
||||
}
|
||||
```
|
||||
|
||||
There will be an error, because the controller `Admin` handles all routes starting with
|
||||
`'/admin'`: the endpoint `admin_path` is unreachable.
|
||||
|
||||
### Controller with hostname
|
||||
|
||||
You can also set a host for a controller. All requests coming to that host will be handled
|
||||
by the controller.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
struct Example {}
|
||||
|
||||
// You can only access this route at example.com: http://example.com/
|
||||
pub fn (app &Example) index(mut ctx Context) veb.Result {
|
||||
return ctx.text('Example')
|
||||
}
|
||||
```
|
||||
|
||||
```v ignore
|
||||
mut example_app := &Example{}
|
||||
// set the controllers hostname to 'example.com' and handle all routes starting with '/',
|
||||
// we handle requests with any route to 'example.com'
|
||||
app.register_controller[Example, Context]('example.com', '/', mut example_app)!
|
||||
```
|
||||
|
||||
## Context Methods
|
||||
|
||||
veb has a number of utility methods that make it easier to handle requests and send responses.
|
||||
These methods are available on `veb.Context` and directly on your own context struct if you
|
||||
embed `veb.Context`. Below are some of the most used methods, look at the
|
||||
[standard library documentation](https://modules.vlang.io/) to see them all.
|
||||
|
||||
### Request methods
|
||||
|
||||
You can directly access the HTTP request on the `.req` field.
|
||||
|
||||
#### Get request headers
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
pub fn (app &App) index(mut ctx Context) veb.Result {
|
||||
content_length := ctx.get_header(.content_length) or { '0' }
|
||||
// get custom header
|
||||
custom_header := ctx.get_custom_header('X-HEADER') or { '' }
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Get a cookie
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
pub fn (app &App) index(mut ctx Context) veb.Result {
|
||||
cookie_val := ctx.get_cookie('token') or { '' }
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Response methods
|
||||
|
||||
You can directly modify the HTTP response by changing the `res` field,
|
||||
which is of the type `http.Response`.
|
||||
|
||||
#### Send response with different MIME types
|
||||
|
||||
```v ignore
|
||||
// send response HTTP_OK with content-type `text/html`
|
||||
ctx.html('<h1>Hello world!</h1>')
|
||||
// send response HTTP_OK with content-type `text/plain`
|
||||
ctx.text('Hello world!')
|
||||
// stringify the object and send response HTTP_OK with content-type `application/json`
|
||||
ctx.json(User{
|
||||
name: 'test'
|
||||
age: 20
|
||||
})
|
||||
```
|
||||
|
||||
#### Sending files
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
pub fn (app &App) file_response(mut ctx Context) veb.Result {
|
||||
// send the file 'image.png' in folder 'data' to the user
|
||||
return ctx.file('data/image.png')
|
||||
}
|
||||
```
|
||||
|
||||
#### Set response headers
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
pub fn (app &App) index(mut ctx Context) veb.Result {
|
||||
ctx.set_header(.accept, 'text/html')
|
||||
// set custom header
|
||||
ctx.set_custom_header('X-HEADER', 'my-value')!
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Set a cookie
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
pub fn (app &App) index(mut ctx Context) veb.Result {
|
||||
ctx.set_cookie(http.Cookie{
|
||||
name: 'token'
|
||||
value: 'true'
|
||||
path: '/'
|
||||
secure: true
|
||||
http_only: true
|
||||
})
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Redirect
|
||||
|
||||
You must pass the type of redirect to veb:
|
||||
|
||||
- `moved_permanently` HTTP code 301
|
||||
- `found` HTTP code 302
|
||||
- `see_other` HTTP code 303
|
||||
- `temporary_redirect` HTTP code 307
|
||||
- `permanent_redirect` HTTP code 308
|
||||
|
||||
**Common use cases:**
|
||||
|
||||
If you want to change the request method, for example when you receive a post request and
|
||||
want to redirect to another page via a GET request, you should use `see_other`. If you want
|
||||
the HTTP method to stay the same, you should use `found` generally speaking.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
pub fn (app &App) index(mut ctx Context) veb.Result {
|
||||
token := ctx.get_cookie('token') or { '' }
|
||||
if token == '' {
|
||||
// redirect the user to '/login' if the 'token' cookie is not set
|
||||
// we explicitly tell the browser to send a GET request
|
||||
return ctx.redirect('/login', typ: .see_other)
|
||||
} else {
|
||||
return ctx.text('Welcome!')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Sending error responses
|
||||
|
||||
**Example:**
|
||||
|
||||
```v ignore
|
||||
pub fn (app &App) login(mut ctx Context) veb.Result {
|
||||
if username := ctx.form['username'] {
|
||||
return ctx.text('Hello "${username}"')
|
||||
} else {
|
||||
// send an HTTP 400 Bad Request response with a message
|
||||
return ctx.request_error('missing form value "username"')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also use `ctx.server_error(msg string)` to send an HTTP 500 internal server
|
||||
error with a message.
|
||||
|
||||
## Advanced usage
|
||||
|
||||
If you need more control over the TCP connection with a client, for example when
|
||||
you want to keep the connection open. You can call `ctx.takeover_conn`.
|
||||
|
||||
When this function is called you are free to do anything you want with the TCP
|
||||
connection and veb will not interfere. This means that we are responsible for
|
||||
sending a response over the connection and closing it.
|
||||
|
||||
### Empty Result
|
||||
|
||||
Sometimes you want to send the response in another thread, for example when using
|
||||
[Server Sent Events](sse/README.md). When you are sure that a response will be sent
|
||||
over the TCP connection you can return `veb.no_result()`. This function does nothing
|
||||
and returns an empty `veb.Result` struct, letting veb know that we sent a response ourselves.
|
||||
|
||||
> **Note:**
|
||||
> It is important to call `ctx.takeover_conn` before you spawn a thread
|
||||
|
||||
**Example:**
|
||||
|
||||
```v
|
||||
module main
|
||||
|
||||
import net
|
||||
import time
|
||||
import veb
|
||||
|
||||
pub struct Context {
|
||||
veb.Context
|
||||
}
|
||||
|
||||
pub struct App {}
|
||||
|
||||
pub fn (app &App) index(mut ctx Context) veb.Result {
|
||||
return ctx.text('hello!')
|
||||
}
|
||||
|
||||
@['/long']
|
||||
pub fn (app &App) long_response(mut ctx Context) veb.Result {
|
||||
// let veb know that the connection should not be closed
|
||||
ctx.takeover_conn()
|
||||
// use spawn to handle the connection in another thread
|
||||
// if we don't the whole web server will block for 10 seconds,
|
||||
// since veb is singlethreaded
|
||||
spawn handle_connection(mut ctx.conn)
|
||||
// we will send a custom response ourselves, so we can safely return an empty result
|
||||
return veb.no_result()
|
||||
}
|
||||
|
||||
fn handle_connection(mut conn net.TcpConn) {
|
||||
defer {
|
||||
conn.close() or {}
|
||||
}
|
||||
// block for 10 second
|
||||
time.sleep(time.second * 10)
|
||||
conn.write_string('HTTP/1.1 200 OK\r\nContent-type: text/html\r\nContent-length: 15\r\n\r\nHello takeover!') or {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut app := &App{}
|
||||
veb.run[App, Context](mut app, 8080)
|
||||
}
|
||||
```
|
||||
21
aiprompts/vshell example script instructions.md
Normal file
21
aiprompts/vshell example script instructions.md
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
# how to run the vshell example scripts
|
||||
|
||||
this is how we want example scripts to be, see the first line
|
||||
|
||||
```vlang
|
||||
#!/usr/bin/env -S v -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.crystallib.installers.sysadmintools.daguserver
|
||||
|
||||
mut ds := daguserver.get()!
|
||||
|
||||
println(ds)
|
||||
```
|
||||
|
||||
the files are in ~/code/github/freeflowuniverse/crystallib/examples for crystallib
|
||||
|
||||
## important instructions
|
||||
|
||||
- never use fn main() in a .vsh script
|
||||
|
||||
165
aiprompts/vtemplates.md
Normal file
165
aiprompts/vtemplates.md
Normal file
@@ -0,0 +1,165 @@
|
||||
V allows for easily using text templates, expanded at compile time to
|
||||
V functions, that efficiently produce text output. This is especially
|
||||
useful for templated HTML views, but the mechanism is general enough
|
||||
to be used for other kinds of text output also.
|
||||
|
||||
# Template directives
|
||||
|
||||
Each template directive begins with an `@` sign.
|
||||
Some directives contain a `{}` block, others only have `''` (string) parameters.
|
||||
|
||||
Newlines on the beginning and end are ignored in `{}` blocks,
|
||||
otherwise this (see [if](#if) for this syntax):
|
||||
|
||||
```html
|
||||
@if bool_val {
|
||||
<span>This is shown if bool_val is true</span>
|
||||
}
|
||||
```
|
||||
|
||||
... would output:
|
||||
|
||||
```html
|
||||
|
||||
<span>This is shown if bool_val is true</span>
|
||||
|
||||
```
|
||||
|
||||
... which is less readable.
|
||||
|
||||
## if
|
||||
|
||||
The if directive, consists of three parts, the `@if` tag, the condition (same syntax like in V)
|
||||
and the `{}` block, where you can write html, which will be rendered if the condition is true:
|
||||
|
||||
```
|
||||
@if <condition> {}
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
```html
|
||||
@if bool_val {
|
||||
<span>This is shown if bool_val is true</span>
|
||||
}
|
||||
```
|
||||
|
||||
One-liner:
|
||||
|
||||
```html
|
||||
@if bool_val { <span>This is shown if bool_val is true</span> }
|
||||
```
|
||||
|
||||
The first example would result in:
|
||||
|
||||
```html
|
||||
<span>This is shown if bool_val is true</span>
|
||||
```
|
||||
|
||||
... while the one-liner results in:
|
||||
|
||||
```html
|
||||
<span>This is shown if bool_val is true</span>
|
||||
```
|
||||
|
||||
## for
|
||||
|
||||
The for directive consists of three parts, the `@for` tag,
|
||||
the condition (same syntax like in V) and the `{}` block,
|
||||
where you can write text, rendered for each iteration of the loop:
|
||||
|
||||
```
|
||||
@for <condition> {}
|
||||
```
|
||||
|
||||
### Example for @for
|
||||
|
||||
```html
|
||||
@for i, val in my_vals {
|
||||
<span>$i - $val</span>
|
||||
}
|
||||
```
|
||||
|
||||
One-liner:
|
||||
|
||||
```html
|
||||
@for i, val in my_vals { <span>$i - $val</span> }
|
||||
```
|
||||
|
||||
The first example would result in:
|
||||
|
||||
```html
|
||||
<span>0 - "First"</span>
|
||||
<span>1 - "Second"</span>
|
||||
<span>2 - "Third"</span>
|
||||
...
|
||||
```
|
||||
|
||||
... while the one-liner results in:
|
||||
|
||||
```html
|
||||
<span>0 - "First"</span>
|
||||
<span>1 - "Second"</span>
|
||||
<span>2 - "Third"</span>
|
||||
...
|
||||
```
|
||||
|
||||
You can also write (and all other for condition syntaxes that are allowed in V):
|
||||
|
||||
```html
|
||||
@for i = 0; i < 5; i++ {
|
||||
<span>$i</span>
|
||||
}
|
||||
```
|
||||
|
||||
## include
|
||||
|
||||
The include directive is for including other html files (which will be processed as well)
|
||||
and consists of two parts, the `@include` tag and a following `'<path>'` string.
|
||||
The path parameter is relative to the template file being called.
|
||||
|
||||
### Example for the folder structure of a project using templates:
|
||||
|
||||
```
|
||||
Project root
|
||||
/templates
|
||||
- index.html
|
||||
/headers
|
||||
- base.html
|
||||
```
|
||||
|
||||
`index.html`
|
||||
|
||||
```html
|
||||
|
||||
<div>@include 'header/base'</div>
|
||||
```
|
||||
|
||||
> Note that there shouldn't be a file suffix,
|
||||
> it is automatically appended and only allows `html` files.
|
||||
|
||||
|
||||
## js
|
||||
|
||||
The js directive consists of two parts, the `@js` tag and `'<path>'` string,
|
||||
where you can insert your src
|
||||
|
||||
```
|
||||
@js '<url>'
|
||||
```
|
||||
|
||||
### Example for the @js directive:
|
||||
|
||||
```html
|
||||
@js 'myscripts.js'
|
||||
```
|
||||
|
||||
# Variables
|
||||
|
||||
All variables, which are declared before the $tmpl can be used through the `@{my_var}` syntax.
|
||||
It's also possible to use properties of structs here like `@{my_struct.prop}`.
|
||||
|
||||
# Escaping
|
||||
|
||||
The `@` symbol starts a template directive. If you need to use `@` as a regular
|
||||
character within a template, escape it by using a double `@` like this: `@@`.
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import os
|
||||
import flag
|
||||
@@ -12,7 +12,7 @@ fp.skip_executable()
|
||||
|
||||
mut path := fp.string('path', `p`, "", 'Path where to generate a module, if not mentioned will scan over all installers & clients.\nif . then will be path we are on.')
|
||||
reset := fp.bool('reset', `r`, false, 'If we want to reset')
|
||||
interactive := fp.bool('interactive', `i`, true, 'If we want to work interactive')
|
||||
interactive := fp.bool('interactive', `i`, false, 'If we want to work interactive')
|
||||
scan := fp.bool('scan', `s`, false, 'If we want to scan')
|
||||
help_requested := fp.bool('help', `h`, false, 'Show help message')
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ os.symlink('${abs_dir_of_script}/lib', '${os.home_dir()}/.vmodules/freeflowunive
|
||||
println('Herolib installation completed successfully!')
|
||||
|
||||
// Add vtest alias
|
||||
addtoscript('alias vtest=', 'alias vtest=\'v -stats -enable-globals -n -w -gc none -no-retry-compilation -cc tcc test\' %') or {
|
||||
addtoscript('alias vtest=', 'alias vtest=\'v -stats -enable-globals -n -w -cg -gc none -no-retry-compilation -cc tcc test\' %') or {
|
||||
eprintln('Failed to add vtest alias: ${err}')
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ pub const version = '1.0.0'
|
||||
const singleton = false
|
||||
const default = true
|
||||
|
||||
// TODO: THIS IS EXAMPLE CODE AND NEEDS TO BE CHANGED IN LINE TO STRUCT BELOW, IS STRUCTURED AS HEROSCRIPT
|
||||
pub fn heroscript_default() string {
|
||||
mail_from := os.getenv_opt('MAIL_FROM') or { 'info@example.com' }
|
||||
mail_password := os.getenv_opt('MAIL_PASSWORD') or { 'secretpassword' }
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
!!hero_code.generate_client
|
||||
name: "mycelium"
|
||||
classname: "Mycelium"
|
||||
hasconfig: true
|
||||
singleton: true
|
||||
default: true
|
||||
title: ""
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
module mycelium
|
||||
|
||||
import net.http
|
||||
import json
|
||||
import freeflowuniverse.herolib.core.httpconnection
|
||||
|
||||
const server_url = 'http://localhost:8989/api/v1/messages'
|
||||
|
||||
pub struct MessageDestination {
|
||||
pub:
|
||||
@@ -36,74 +35,87 @@ pub:
|
||||
msg_len string @[json: 'msgLen']
|
||||
}
|
||||
|
||||
pub fn send_msg(pk string, payload string, wait bool) !InboundMessage {
|
||||
mut url := server_url
|
||||
if wait {
|
||||
url = '${url}?reply_timeout=120'
|
||||
pub fn (mut self Mycelium) connection() !&httpconnection.HTTPConnection {
|
||||
mut c := self.conn or {
|
||||
mut c2 := httpconnection.new(
|
||||
name: 'mycelium'
|
||||
url: self.server_url
|
||||
retry: 3
|
||||
)!
|
||||
c2
|
||||
}
|
||||
msg_req := PushMessageBody{
|
||||
dst: MessageDestination{
|
||||
pk: pk
|
||||
}
|
||||
payload: payload
|
||||
}
|
||||
mut req := http.new_request(http.Method.post, url, json.encode(msg_req))
|
||||
req.add_custom_header('content-type', 'application/json')!
|
||||
if wait {
|
||||
req.read_timeout = 1200000000000
|
||||
}
|
||||
res := req.do()!
|
||||
msg := json.decode(InboundMessage, res.body)!
|
||||
return msg
|
||||
return c
|
||||
}
|
||||
|
||||
pub fn receive_msg(wait bool) !InboundMessage {
|
||||
mut url := server_url
|
||||
if wait {
|
||||
url = '${url}?timeout=60'
|
||||
pub fn (mut self Mycelium) send_msg(pk string, payload string, wait bool) !InboundMessage {
|
||||
mut conn := self.connection()!
|
||||
mut params := {
|
||||
'dst': json.encode(MessageDestination{pk: pk})
|
||||
'payload': payload
|
||||
}
|
||||
mut req := http.new_request(http.Method.get, url, '')
|
||||
mut prefix := ''
|
||||
if wait {
|
||||
req.read_timeout = 600000000000
|
||||
prefix = '?reply_timeout=120'
|
||||
}
|
||||
res := req.do()!
|
||||
msg := json.decode(InboundMessage, res.body)!
|
||||
return msg
|
||||
return conn.post_json_generic[InboundMessage](
|
||||
method: .post
|
||||
prefix: prefix
|
||||
params: params
|
||||
dataformat: .json
|
||||
)!
|
||||
}
|
||||
|
||||
pub fn receive_msg_opt(wait bool) ?InboundMessage {
|
||||
mut url := server_url
|
||||
pub fn (mut self Mycelium) receive_msg(wait bool) !InboundMessage {
|
||||
mut conn := self.connection()!
|
||||
mut prefix := ''
|
||||
if wait {
|
||||
url = '${url}?timeout=60'
|
||||
prefix = '?timeout=60'
|
||||
}
|
||||
mut req := http.new_request(http.Method.get, url, '')
|
||||
return conn.get_json_generic[InboundMessage](
|
||||
method: .get
|
||||
prefix: prefix
|
||||
dataformat: .json
|
||||
)!
|
||||
}
|
||||
|
||||
pub fn (mut self Mycelium) receive_msg_opt(wait bool) ?InboundMessage {
|
||||
mut conn := self.connection()!
|
||||
mut prefix := ''
|
||||
if wait {
|
||||
req.read_timeout = 600000000000
|
||||
prefix = '?timeout=60'
|
||||
}
|
||||
res := req.do() or { panic(error) }
|
||||
if res.status_code == 204 {
|
||||
res := conn.get_json_generic[InboundMessage](
|
||||
method: .get
|
||||
prefix: prefix
|
||||
dataformat: .json
|
||||
) or {
|
||||
if err.msg().contains('204') {
|
||||
return none
|
||||
}
|
||||
msg := json.decode(InboundMessage, res.body) or { panic(err) }
|
||||
return msg
|
||||
}
|
||||
|
||||
pub fn get_msg_status(id string) !MessageStatusResponse {
|
||||
mut url := '${server_url}/status/${id}'
|
||||
res := http.get(url)!
|
||||
msg_res := json.decode(MessageStatusResponse, res.body)!
|
||||
return msg_res
|
||||
}
|
||||
|
||||
pub fn reply_msg(id string, pk string, payload string) !http.Status {
|
||||
mut url := '${server_url}/reply/${id}'
|
||||
msg_req := PushMessageBody{
|
||||
dst: MessageDestination{
|
||||
pk: pk
|
||||
panic(err)
|
||||
}
|
||||
payload: payload
|
||||
}
|
||||
|
||||
res := http.post_json(url, json.encode(msg_req))!
|
||||
return res.status()
|
||||
return res
|
||||
}
|
||||
|
||||
pub fn (mut self Mycelium) get_msg_status(id string) !MessageStatusResponse {
|
||||
mut conn := self.connection()!
|
||||
return conn.get_json_generic[MessageStatusResponse](
|
||||
method: .get
|
||||
prefix: 'status/${id}'
|
||||
dataformat: .json
|
||||
)!
|
||||
}
|
||||
|
||||
pub fn (mut self Mycelium) reply_msg(id string, pk string, payload string) ! {
|
||||
mut conn := self.connection()!
|
||||
mut params := {
|
||||
'dst': json.encode(MessageDestination{pk: pk})
|
||||
'payload': payload
|
||||
}
|
||||
conn.post_json_generic[json.Any](
|
||||
method: .post
|
||||
prefix: 'reply/${id}'
|
||||
params: params
|
||||
dataformat: .json
|
||||
)!
|
||||
}
|
||||
|
||||
@@ -19,8 +19,94 @@ pub mut:
|
||||
name string
|
||||
}
|
||||
|
||||
fn args_get (args_ ArgsGet) ArgsGet {
|
||||
mut model:=args_
|
||||
if model.name == ""{
|
||||
model.name = mycelium_default
|
||||
}
|
||||
if model.name == ""{
|
||||
model.name = "default"
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
pub fn get(args_ ArgsGet) !&Mycelium {
|
||||
return &Mycelium{}
|
||||
mut model := args_get(args_)
|
||||
if !(model.name in mycelium_global) {
|
||||
if model.name=="default"{
|
||||
if ! config_exists(model){
|
||||
if default{
|
||||
config_save(model)!
|
||||
}
|
||||
}
|
||||
config_load(model)!
|
||||
}
|
||||
}
|
||||
return mycelium_global[model.name] or {
|
||||
println(mycelium_global)
|
||||
panic("could not get config for mycelium with name:${model.name}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn config_exists(args_ ArgsGet) bool {
|
||||
mut model := args_get(args_)
|
||||
mut context:=base.context() or { panic("bug") }
|
||||
return context.hero_config_exists("mycelium",model.name)
|
||||
}
|
||||
|
||||
fn config_load(args_ ArgsGet) ! {
|
||||
mut model := args_get(args_)
|
||||
mut context:=base.context()!
|
||||
mut heroscript := context.hero_config_get("mycelium",model.name)!
|
||||
play(heroscript:heroscript)!
|
||||
}
|
||||
|
||||
fn config_save(args_ ArgsGet) ! {
|
||||
mut model := args_get(args_)
|
||||
mut context:=base.context()!
|
||||
context.hero_config_set("mycelium",model.name,heroscript_default()!)!
|
||||
}
|
||||
|
||||
|
||||
fn set(o Mycelium)! {
|
||||
mut o2:=obj_init(o)!
|
||||
mycelium_global[o.name] = &o2
|
||||
mycelium_default = o.name
|
||||
}
|
||||
|
||||
|
||||
@[params]
|
||||
pub struct PlayArgs {
|
||||
pub mut:
|
||||
heroscript string //if filled in then plbook will be made out of it
|
||||
plbook ?playbook.PlayBook
|
||||
reset bool
|
||||
}
|
||||
|
||||
pub fn play(args_ PlayArgs) ! {
|
||||
|
||||
mut model:=args_
|
||||
|
||||
if model.heroscript == "" {
|
||||
model.heroscript = heroscript_default()!
|
||||
}
|
||||
mut plbook := model.plbook or {
|
||||
playbook.new(text: model.heroscript)!
|
||||
}
|
||||
|
||||
mut install_actions := plbook.find(filter: 'mycelium.configure')!
|
||||
if install_actions.len > 0 {
|
||||
for install_action in install_actions {
|
||||
mut p := install_action.params
|
||||
mycfg:=cfg_play(p)!
|
||||
console.print_debug("install action mycelium.configure\n${mycfg}")
|
||||
set(mycfg)!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,25 +1,37 @@
|
||||
module mycelium
|
||||
import freeflowuniverse.herolib.data.paramsparser
|
||||
import freeflowuniverse.herolib.core.httpconnection
|
||||
import os
|
||||
|
||||
pub const version = '1.14.3'
|
||||
pub const version = '0.0.0'
|
||||
const singleton = true
|
||||
const default = true
|
||||
|
||||
pub fn heroscript_default() !string {
|
||||
heroscript:="
|
||||
!!mycelium.configure
|
||||
name:'mycelium'
|
||||
"
|
||||
return heroscript
|
||||
|
||||
}
|
||||
|
||||
//THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
|
||||
|
||||
$[heap]
|
||||
pub struct Mycelium {
|
||||
pub mut:
|
||||
name string = 'default'
|
||||
mail_from string
|
||||
mail_password string @[secret]
|
||||
mail_port int
|
||||
mail_server string
|
||||
mail_username string
|
||||
server_url string
|
||||
conn ?&httpconnection.HTTPConnection
|
||||
}
|
||||
|
||||
fn cfg_play(p paramsparser.Params) ! {
|
||||
mut mycfg := Mycelium{
|
||||
name: p.get_default('name', 'default')!
|
||||
server_url: p.get_default('server_url', 'http://localhost:8989/api/v1/messages')!
|
||||
}
|
||||
set(mycfg)!
|
||||
}
|
||||
|
||||
|
||||
fn obj_init(obj_ Mycelium)!Mycelium{
|
||||
@@ -27,6 +39,3 @@ fn obj_init(obj_ Mycelium)!Mycelium{
|
||||
mut obj:=obj_
|
||||
return obj
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,30 +1,141 @@
|
||||
# mycelium
|
||||
# Mycelium Client
|
||||
|
||||
|
|
||||
A V client library for interacting with the Mycelium messaging system. This client provides functionality for sending, receiving, and managing messages through a Mycelium server.
|
||||
|
||||
To get started
|
||||
## Configuration
|
||||
|
||||
```vlang
|
||||
|
||||
|
||||
import freeflowuniverse.herolib.clients. mycelium
|
||||
|
||||
mut client:= mycelium.get()!
|
||||
|
||||
client...
|
||||
The client can be configured either through V code or using heroscript.
|
||||
|
||||
### V Code Configuration
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.clients.mycelium
|
||||
|
||||
mut client := mycelium.get()!
|
||||
|
||||
// By default connects to http://localhost:8989/api/v1/messages
|
||||
// To use a different server:
|
||||
mut client := mycelium.get(name: "custom", server_url: "http://myserver:8989/api/v1/messages")!
|
||||
```
|
||||
|
||||
## example heroscript
|
||||
### Heroscript Configuration
|
||||
|
||||
```hero
|
||||
!!mycelium.configure
|
||||
secret: '...'
|
||||
host: 'localhost'
|
||||
port: 8888
|
||||
name:'custom' # optional, defaults to 'default'
|
||||
server_url:'http://myserver:8989/api/v1/messages' # optional, defaults to localhost:8989
|
||||
```
|
||||
|
||||
Note: Configuration is not needed if using a locally running Mycelium server with default settings.
|
||||
|
||||
## Example Script
|
||||
|
||||
Save as `mycelium_example.vsh`:
|
||||
|
||||
```v
|
||||
#!/usr/bin/env -S v -n -w -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.clients.mycelium
|
||||
|
||||
// Initialize client
|
||||
mut client := mycelium.get()!
|
||||
|
||||
// Send a message and wait for reply
|
||||
msg := client.send_msg(
|
||||
pk: "recipient_public_key"
|
||||
payload: "Hello!"
|
||||
wait: true // wait for reply (timeout 120s)
|
||||
)!
|
||||
println('Message sent with ID: ${msg.id}')
|
||||
|
||||
// Check message status
|
||||
status := client.get_msg_status(msg.id)!
|
||||
println('Message status: ${status.state}')
|
||||
|
||||
// Receive messages with timeout
|
||||
if incoming := client.receive_msg_opt(wait: true) {
|
||||
println('Received message: ${incoming.payload}')
|
||||
println('From: ${incoming.src_pk}')
|
||||
|
||||
// Reply to the message
|
||||
client.reply_msg(
|
||||
id: incoming.id
|
||||
pk: incoming.src_pk
|
||||
payload: "Got your message!"
|
||||
)!
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Sending Messages
|
||||
|
||||
```v
|
||||
// Send a message to a specific public key
|
||||
// wait=true means wait for reply (timeout 120s)
|
||||
msg := client.send_msg(pk: "recipient_public_key", payload: "Hello!", wait: true)!
|
||||
|
||||
// Get status of a sent message
|
||||
status := client.get_msg_status(id: "message_id")!
|
||||
```
|
||||
|
||||
### Receiving Messages
|
||||
|
||||
```v
|
||||
// Receive a message (non-blocking)
|
||||
msg := client.receive_msg(wait: false)!
|
||||
|
||||
// Receive a message with timeout (blocking for 60s)
|
||||
msg := client.receive_msg(wait: true)!
|
||||
|
||||
// Receive a message (returns none if no message available)
|
||||
if msg := client.receive_msg_opt(wait: false) {
|
||||
println('Received: ${msg.payload}')
|
||||
}
|
||||
```
|
||||
|
||||
### Replying to Messages
|
||||
|
||||
```v
|
||||
// Reply to a specific message
|
||||
client.reply_msg(
|
||||
id: "original_message_id",
|
||||
pk: "sender_public_key",
|
||||
payload: "Reply message"
|
||||
)!
|
||||
```
|
||||
|
||||
## Message Types
|
||||
|
||||
### InboundMessage
|
||||
```v
|
||||
struct InboundMessage {
|
||||
id string
|
||||
src_ip string
|
||||
src_pk string
|
||||
dst_ip string
|
||||
dst_pk string
|
||||
payload string
|
||||
}
|
||||
```
|
||||
|
||||
### MessageStatusResponse
|
||||
```v
|
||||
struct MessageStatusResponse {
|
||||
id string
|
||||
dst string
|
||||
state string
|
||||
created string
|
||||
deadline string
|
||||
msg_len string
|
||||
}
|
||||
```
|
||||
|
||||
## Heroscript Complete Example
|
||||
|
||||
```hero
|
||||
!!mycelium.configure
|
||||
name:'mycelium'
|
||||
server_url:'http://localhost:8989/api/v1/messages'
|
||||
|
||||
# More heroscript commands can be added here as the API expands
|
||||
|
||||
@@ -39,7 +39,7 @@ pub fn ask(path string) ! {
|
||||
if model.hasconfig {
|
||||
model.singleton = !myconsole.ask_yesno(
|
||||
description: 'Can there be multiple instances (normally yes)?'
|
||||
default: model.singleton
|
||||
default: !model.singleton
|
||||
)!
|
||||
if model.cat == .installer {
|
||||
model.templates = myconsole.ask_yesno(
|
||||
@@ -64,6 +64,12 @@ pub fn ask(path string) ! {
|
||||
)!
|
||||
}
|
||||
|
||||
// if true{
|
||||
// println(model)
|
||||
// panic("Sdsd")
|
||||
// }
|
||||
|
||||
|
||||
gen_model_set(GenerateArgs{model: model, path: path})!
|
||||
|
||||
}
|
||||
|
||||
@@ -37,6 +37,11 @@ pub fn do(args_ GenerateArgs) ! {
|
||||
m
|
||||
}
|
||||
|
||||
if model.classname == "" {
|
||||
args.interactive = true
|
||||
|
||||
}
|
||||
|
||||
if create{
|
||||
if args.path == '' {
|
||||
return error("need to specify path fo ${args_} because we asked to create .heroscript ")
|
||||
@@ -57,6 +62,8 @@ pub fn do(args_ GenerateArgs) ! {
|
||||
if args.interactive{
|
||||
ask(args.path)!
|
||||
args.model = gen_model_get(args.path, false)!
|
||||
}else{
|
||||
args.model = model
|
||||
}
|
||||
|
||||
console.print_debug(args)
|
||||
|
||||
@@ -9,7 +9,7 @@ fn generate(args GenerateArgs) ! {
|
||||
console.print_debug('generate code for path: ${args.path}')
|
||||
|
||||
//as used in the templates
|
||||
model := args.model or { panic('bug') }
|
||||
model := args.model or { panic('bug no model specified in generate') }
|
||||
|
||||
mut path_actions := pathlib.get(args.path + '/${model.name}_actions.v')
|
||||
if args.reset {
|
||||
|
||||
@@ -18,7 +18,7 @@ pub mut:
|
||||
templates bool // means we will use templates in the installer, client doesn't do this'
|
||||
reset bool // regenerate all, dangerous !!!
|
||||
interactive bool //if we want to ask
|
||||
startupmanager bool = true
|
||||
startupmanager bool
|
||||
build bool
|
||||
hasconfig bool
|
||||
cat Cat = .client
|
||||
@@ -33,6 +33,7 @@ pub enum Cat {
|
||||
|
||||
|
||||
pub fn gen_model_set(args GenerateArgs) ! {
|
||||
console.print_debug("Code generator set: ${args}")
|
||||
model := args.model or { return error('model is none') }
|
||||
heroscript_templ := match model.cat {
|
||||
.client { $tmpl('templates/heroscript_client') }
|
||||
@@ -111,6 +112,8 @@ pub fn gen_model_get(path string, create bool) !GenModel {
|
||||
model.name = os.base(path).to_lower()
|
||||
}
|
||||
|
||||
console.print_debug("Code generator get: ${model}")
|
||||
|
||||
return model
|
||||
// return GenModel{}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ pub mut:
|
||||
// scan over a set of directories call the play where
|
||||
pub fn scan(args ScannerArgs) ! {
|
||||
|
||||
console.print_debug("Code generator scan: ${args.path}")
|
||||
|
||||
if args.path == "" {
|
||||
scan(path:"${os.home_dir()}/code/github/freeflowuniverse/herolib/lib/installers")!
|
||||
scan(path:"${os.home_dir()}/code/github/freeflowuniverse/herolib/lib/clients")!
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
!!hero_code.generate_client
|
||||
name: "${model.name}"
|
||||
classname: "${model.classname}"
|
||||
config: ${model.hasconfig}
|
||||
hasconfig: ${model.hasconfig}
|
||||
singleton: ${model.singleton}
|
||||
default: ${model.default}
|
||||
title: "${model.title}|
|
||||
title: "${model.title}"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
!!hero_code.generate_installer
|
||||
name: "${model.name}"
|
||||
classname: "${model.classname}"
|
||||
config: ${model.hasconfig}
|
||||
hasconfig: ${model.hasconfig}
|
||||
singleton: ${model.singleton}
|
||||
default: ${model.default}
|
||||
title: "${model.title}"
|
||||
|
||||
@@ -2,7 +2,7 @@ module ${model.name}
|
||||
import freeflowuniverse.herolib.data.paramsparser
|
||||
import os
|
||||
|
||||
pub const version = '1.14.3'
|
||||
pub const version = '0.0.0'
|
||||
const singleton = ${model.singleton}
|
||||
const default = ${model.default}
|
||||
|
||||
|
||||
@@ -22,6 +22,27 @@ mut conn := HTTPConnection{
|
||||
}
|
||||
```
|
||||
|
||||
## important: How to use it on a management class e.g. an installe or a client
|
||||
|
||||
```v
|
||||
// e.g. HetznerManager is the object on which we want to have an http client
|
||||
|
||||
pub fn (mut h HetznerManager) connection() !&httpconnection.HTTPConnection {
|
||||
mut c := h.conn or {
|
||||
mut c2 := httpconnection.new(
|
||||
name: 'hetzner_${h.name}'
|
||||
url: h.baseurl
|
||||
cache: true
|
||||
retry: 3
|
||||
)!
|
||||
c2.basic_auth(h.user, h.password)
|
||||
c2
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### GET Request with JSON Response
|
||||
|
||||
@@ -47,6 +47,7 @@ pub fn (mut c UIConsole) ask_question(args QuestionArgs) !string {
|
||||
description: args.description
|
||||
warning: 'Min lenght of answer is: ${args.minlen}'
|
||||
question: args.question
|
||||
default: args.default
|
||||
)
|
||||
}
|
||||
return choice
|
||||
|
||||
@@ -31,7 +31,7 @@ pub fn (mut c UIConsole) ask_yesno(args YesNoArgs) !bool {
|
||||
print_debug('${question} (y/n) : ')
|
||||
choice := os.get_raw_line().trim(' \n').to_lower()
|
||||
if choice.trim_space() == "" {
|
||||
return true
|
||||
return args.default
|
||||
}
|
||||
if choice.starts_with('y') {
|
||||
return true
|
||||
@@ -50,5 +50,6 @@ pub fn (mut c UIConsole) ask_yesno(args YesNoArgs) !bool {
|
||||
question: args.question
|
||||
warning: "Please choose 'y' or 'n', then enter."
|
||||
reset: true
|
||||
default: args.default
|
||||
)
|
||||
}
|
||||
|
||||
117
lib/virt/cloudhypervisor/chv_factory.v
Normal file
117
lib/virt/cloudhypervisor/chv_factory.v
Normal file
@@ -0,0 +1,117 @@
|
||||
module cloudhypervisor
|
||||
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.installers.virt.cloudhypervisor as cloudhypervisorinstaller
|
||||
import os
|
||||
|
||||
@[heap]
|
||||
pub struct CloudHypervisorFactory {
|
||||
pub mut:
|
||||
debug bool
|
||||
}
|
||||
|
||||
pub fn new() !CloudHypervisorFactory {
|
||||
mut inst := cloudhypervisorinstaller.get()!
|
||||
inst.install()!
|
||||
return CloudHypervisorFactory{}
|
||||
}
|
||||
|
||||
// pub fn (mut lf CloudHypervisorFactory) vm_get_all() ![]VM {
|
||||
// mut vms := []VM{}
|
||||
// for vm in raw.list()! {
|
||||
// // console.print_debug(vm)
|
||||
// mut vm2 := VM{
|
||||
// name: vm.name
|
||||
// dir: vm.dir
|
||||
// arch: vm.arch
|
||||
// cpus: vm.cpus
|
||||
// memory: vm.memory / 1000000
|
||||
// disk: vm.disk / 1000000
|
||||
// ssh_local_port: vm.ssh_local_port
|
||||
// ssh_address: vm.ssh_address
|
||||
// identity_file: vm.identity_file
|
||||
// factory: &lf
|
||||
// }
|
||||
// match vm.status {
|
||||
// 'Running' {
|
||||
// vm2.status = .running
|
||||
// }
|
||||
// 'Stopped' {
|
||||
// vm2.status = .stopped
|
||||
// }
|
||||
// else {
|
||||
// console.print_debug(vm.status)
|
||||
// panic('unknown status')
|
||||
// }
|
||||
// }
|
||||
// vms << vm2
|
||||
// }
|
||||
// return vms
|
||||
// }
|
||||
|
||||
// pub fn (mut lf CloudHypervisorFactory) vm_get(name string) !VM {
|
||||
// for vm in lf.vm_get_all()! {
|
||||
// if vm.name.to_lower() == name.to_lower() {
|
||||
// return vm
|
||||
// }
|
||||
// }
|
||||
// return error("Couldn't find vm with name: ${name}")
|
||||
// }
|
||||
|
||||
// pub fn (mut lf CloudHypervisorFactory) vm_exists(name string) !bool {
|
||||
// for vm in lf.vm_list()! {
|
||||
// if vm.to_lower() == name.to_lower() {
|
||||
// return true
|
||||
// }
|
||||
// }
|
||||
// return false
|
||||
// }
|
||||
|
||||
// pub fn (mut lf CloudHypervisorFactory) vm_stop_all() ! {
|
||||
// for mut vm in lf.vm_get_all()! {
|
||||
// vm.stop()!
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn (mut lf CloudHypervisorFactory) vm_delete_all() ! {
|
||||
// for mut vm in lf.vm_get_all()! {
|
||||
// vm.delete()!
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn (mut lf CloudHypervisorFactory) vm_list() ![]string {
|
||||
// cmd := "qemuctl list -f '{{.Name}}'"
|
||||
// res := os.execute(cmd)
|
||||
// mut vms := []string{}
|
||||
// if res.exit_code > 0 {
|
||||
// return error('could not stop qemu vm.\n${res}')
|
||||
// }
|
||||
// for line in res.output.split_into_lines() {
|
||||
// if line.trim_space() == '' {
|
||||
// continue
|
||||
// }
|
||||
// vms << line.trim_space()
|
||||
// }
|
||||
// return vms
|
||||
// }
|
||||
|
||||
// pub fn (mut lf CloudHypervisorFactory) vm_stop(name string) ! {
|
||||
// console.print_header('vm: ${name} stop')
|
||||
// cmd := 'qemuctl stop ${name}'
|
||||
// os.execute(cmd)
|
||||
// // if res.exit_code > 0 {
|
||||
// // return error('could not delete qemu vm.\n${res}')
|
||||
// // }
|
||||
// cmd2 := 'qemuctl stop ${name} -f'
|
||||
// os.execute(cmd2)
|
||||
// }
|
||||
|
||||
// pub fn (mut lf CloudHypervisorFactory) vm_delete(name string) ! {
|
||||
// console.print_header('vm: ${name} delete')
|
||||
// lf.vm_stop(name)!
|
||||
// cmd := 'qemuctl delete ${name} -f'
|
||||
// res := os.execute(cmd)
|
||||
// if res.exit_code > 0 {
|
||||
// return error('could not delete qemu vm.\n${res}')
|
||||
// }
|
||||
// }
|
||||
12
lib/virt/docker/docker_build_args.v
Normal file
12
lib/virt/docker/docker_build_args.v
Normal file
@@ -0,0 +1,12 @@
|
||||
module docker
|
||||
|
||||
import freeflowuniverse.herolib.data.paramsparser { Params }
|
||||
|
||||
@[params]
|
||||
pub struct BuildArgs {
|
||||
pub mut:
|
||||
reset bool // resets the docker state, will build again
|
||||
strict bool // question: ?
|
||||
engine &DockerEngine
|
||||
args Params
|
||||
}
|
||||
84
lib/virt/docker/docker_compose.v
Normal file
84
lib/virt/docker/docker_compose.v
Normal file
@@ -0,0 +1,84 @@
|
||||
module docker
|
||||
|
||||
import freeflowuniverse.herolib.data.paramsparser { Params }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.osal { exec, file_write }
|
||||
import os
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
@[heap]
|
||||
pub struct DockerComposeRecipe {
|
||||
pub mut:
|
||||
name string
|
||||
content string
|
||||
params Params
|
||||
engine &DockerEngine @[str: skip]
|
||||
items []&ComposeService
|
||||
path string
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct ComposeArgs {
|
||||
pub mut:
|
||||
name string @[required]
|
||||
params string
|
||||
composepath string // can be empty, if empty will be buildpath/compose
|
||||
}
|
||||
|
||||
pub fn (mut e DockerEngine) compose_new(args_ ComposeArgs) DockerComposeRecipe {
|
||||
mut args := args_
|
||||
if args.name == '' {
|
||||
panic('name cannot be empty.')
|
||||
}
|
||||
if args.composepath == '' {
|
||||
args.composepath = '${e.buildpath}/compose'
|
||||
}
|
||||
return DockerComposeRecipe{
|
||||
engine: &e
|
||||
name: args.name
|
||||
path: args.composepath + '/${args.name}'
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut b DockerComposeRecipe) stop() ! {
|
||||
// TODO:
|
||||
}
|
||||
|
||||
pub fn (mut b DockerComposeRecipe) delete() ! {
|
||||
b.stop()!
|
||||
exec(cmd: 'rm -rf ${b.path} && mkdir -p ${b.path}', stdout: false)!
|
||||
}
|
||||
|
||||
fn (mut b DockerComposeRecipe) render() ! {
|
||||
if b.items.len == 0 {
|
||||
return error('Cannot find items for the docker compose in service: ')
|
||||
}
|
||||
b.content = 'version: "3.9"\n'
|
||||
b.content += 'services:\n'
|
||||
for mut item in b.items {
|
||||
c := item.render()!
|
||||
b.content += texttools.indent(c, ' ')
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut b DockerComposeRecipe) start() ! {
|
||||
b.render()!
|
||||
console.print_debug(b)
|
||||
console.print_debug(' start compose file in: ${b.path}')
|
||||
os.mkdir_all(b.path)!
|
||||
file_write('${b.path}/docker-compose.yml', b.content)!
|
||||
for composeitem in b.items {
|
||||
for item in composeitem.files {
|
||||
filename := item.path.all_after_first('/')
|
||||
file_write('${b.path}/${filename}', item.to_string())!
|
||||
}
|
||||
}
|
||||
|
||||
cmd := '
|
||||
set -ex
|
||||
cd ${b.path}
|
||||
docker compose up -d
|
||||
'
|
||||
|
||||
exec(cmd: cmd)!
|
||||
}
|
||||
141
lib/virt/docker/docker_compose_service.v
Normal file
141
lib/virt/docker/docker_compose_service.v
Normal file
@@ -0,0 +1,141 @@
|
||||
module docker
|
||||
|
||||
import v.embed_file
|
||||
import freeflowuniverse.herolib.data.paramsparser { Params }
|
||||
|
||||
@[heap]
|
||||
pub struct ComposeService {
|
||||
pub mut:
|
||||
name string
|
||||
content string // optional
|
||||
params Params
|
||||
files []embed_file.EmbedFileData
|
||||
recipe &DockerComposeRecipe @[str: skip]
|
||||
render bool = true
|
||||
env map[string]string
|
||||
ports []PortMap
|
||||
volumes []VolumeMap
|
||||
restart bool
|
||||
image string
|
||||
}
|
||||
|
||||
pub struct PortMap {
|
||||
pub:
|
||||
indocker int
|
||||
host int
|
||||
}
|
||||
|
||||
pub struct VolumeMap {
|
||||
pub:
|
||||
hostpath string
|
||||
containerpath string
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct ComposeServiceArgs {
|
||||
pub:
|
||||
name string @[required]
|
||||
image string @[required]
|
||||
params string
|
||||
content string // optional, if this is set then will not render
|
||||
}
|
||||
|
||||
pub fn (mut recipe DockerComposeRecipe) service_new(args ComposeServiceArgs) !&ComposeService {
|
||||
if args.name == '' {
|
||||
return error('name cannot be empty.')
|
||||
}
|
||||
mut cs := ComposeService{
|
||||
recipe: &recipe
|
||||
name: args.name
|
||||
content: args.content
|
||||
image: args.image
|
||||
}
|
||||
if args.content.len > 0 {
|
||||
cs.render = false
|
||||
}
|
||||
recipe.items << &cs
|
||||
return recipe.items.last()
|
||||
}
|
||||
|
||||
// example:
|
||||
// registry:
|
||||
// restart: always
|
||||
// image: registry:2
|
||||
// ports:
|
||||
// - 5000:5000
|
||||
// environment:
|
||||
// REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt
|
||||
// REGISTRY_HTTP_TLS_KEY: /certs/domain.key
|
||||
// REGISTRY_AUTH: htpasswd
|
||||
// REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
|
||||
// REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
|
||||
// volumes:
|
||||
// - ${registry.datapath}/data:/var/lib/registry
|
||||
// - ${registry.datapath}/certs:/certs
|
||||
// - ${registry.datapath}/auth:/auth
|
||||
|
||||
// render the recipe to a compose file
|
||||
fn (mut b ComposeService) render() !string {
|
||||
mut out := '${b.name}:\n'
|
||||
if b.render == false {
|
||||
return b.content
|
||||
}
|
||||
out += ' image: ${b.image}\n'
|
||||
if b.restart {
|
||||
out += ' restart: always\n'
|
||||
}
|
||||
if b.ports.len > 0 {
|
||||
out += ' ports:\n'
|
||||
for port in b.ports {
|
||||
out += ' - ${port.indocker}:${port.host}\n'
|
||||
}
|
||||
}
|
||||
if b.env.len > 0 {
|
||||
out += ' environment:\n'
|
||||
for ekey, eval in b.env {
|
||||
out += ' ${ekey.to_upper()}: ${eval}\n'
|
||||
}
|
||||
}
|
||||
if b.volumes.len > 0 {
|
||||
out += ' volumes:\n'
|
||||
for volitem in b.volumes {
|
||||
out += ' - ${volitem.hostpath}:${volitem.containerpath}\n'
|
||||
}
|
||||
}
|
||||
b.content = out
|
||||
return out
|
||||
}
|
||||
|
||||
// add an environment variable to the service
|
||||
pub fn (mut cs ComposeService) env_add(key string, val string) {
|
||||
cs.env[key] = val
|
||||
}
|
||||
|
||||
// make sure the service always restarts
|
||||
pub fn (mut cs ComposeService) restart_set() {
|
||||
cs.restart = true
|
||||
}
|
||||
|
||||
// make sure the service always restarts
|
||||
pub fn (mut cs ComposeService) port_expose(indocker int, host int) ! {
|
||||
// TODO: should check if hostport is free
|
||||
mp := PortMap{
|
||||
indocker: indocker
|
||||
host: host
|
||||
}
|
||||
cs.ports << mp
|
||||
}
|
||||
|
||||
// make sure the service always restarts
|
||||
pub fn (mut cs ComposeService) volume_add(hostpath string, containerpath string) ! {
|
||||
mut vm := VolumeMap{
|
||||
hostpath: hostpath
|
||||
containerpath: containerpath
|
||||
}
|
||||
vm.check()!
|
||||
cs.volumes << vm
|
||||
}
|
||||
|
||||
pub fn (mut vm VolumeMap) check() ! {
|
||||
// TODO: see if it exists on remote host
|
||||
}
|
||||
134
lib/virt/docker/docker_container.v
Normal file
134
lib/virt/docker/docker_container.v
Normal file
@@ -0,0 +1,134 @@
|
||||
module docker
|
||||
|
||||
import time
|
||||
import freeflowuniverse.herolib.osal { exec }
|
||||
import freeflowuniverse.herolib.data.ipaddress { IPAddress }
|
||||
import freeflowuniverse.herolib.virt.utils
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
// pub enum DockerContainerStatus {
|
||||
// up
|
||||
// down
|
||||
// restarting
|
||||
// paused
|
||||
// dead
|
||||
// created
|
||||
// }
|
||||
|
||||
// need to fill in what is relevant
|
||||
@[heap]
|
||||
pub struct DockerContainer {
|
||||
pub mut:
|
||||
id string
|
||||
name string
|
||||
created time.Time
|
||||
ssh_enabled bool // if yes make sure ssh is enabled to the container
|
||||
ipaddr IPAddress
|
||||
forwarded_ports []string
|
||||
mounts []utils.ContainerVolume
|
||||
ssh_port int // ssh port on node that is used to get ssh
|
||||
ports []string
|
||||
networks []string
|
||||
labels map[string]string @[str: skip]
|
||||
image &DockerImage @[str: skip]
|
||||
engine &DockerEngine @[str: skip]
|
||||
status utils.ContainerStatus
|
||||
memsize int // in MB
|
||||
command string
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct DockerContainerCreateArgs {
|
||||
name string
|
||||
hostname string
|
||||
forwarded_ports []string // ["80:9000/tcp", "1000, 10000/udp"]
|
||||
mounted_volumes []string // ["/root:/root", ]
|
||||
env map[string]string // map of environment variables that will be passed to the container
|
||||
privileged bool
|
||||
remove_when_done bool = true // remove the container when it shuts down
|
||||
pub mut:
|
||||
image_repo string
|
||||
image_tag string
|
||||
command string = '/bin/bash'
|
||||
}
|
||||
|
||||
// create/start container (first need to get a dockercontainer before we can start)
|
||||
pub fn (mut container DockerContainer) start() ! {
|
||||
exec(cmd: 'docker start ${container.id}')!
|
||||
container.status = utils.ContainerStatus.up
|
||||
}
|
||||
|
||||
// delete docker container
|
||||
pub fn (mut container DockerContainer) halt() ! {
|
||||
osal.execute_stdout('docker stop ${container.id}') or { '' }
|
||||
container.status = utils.ContainerStatus.down
|
||||
}
|
||||
|
||||
// delete docker container
|
||||
pub fn (mut container DockerContainer) delete() ! {
|
||||
console.print_debug(' CONTAINER DELETE: ${container.name}')
|
||||
|
||||
exec(cmd: 'docker rm ${container.id} -f', stdout: false)!
|
||||
mut x := 0
|
||||
for container2 in container.engine.containers {
|
||||
if container2.name == container.name {
|
||||
container.engine.containers.delete(x)
|
||||
}
|
||||
x += 1
|
||||
}
|
||||
}
|
||||
|
||||
// save the docker container to image
|
||||
pub fn (mut container DockerContainer) save2image(image_repo string, image_tag string) !string {
|
||||
id := osal.execute_stdout('docker commit ${container.id} ${image_repo}:${image_tag}')!
|
||||
container.image.id = id.trim(' ')
|
||||
return id
|
||||
}
|
||||
|
||||
// export docker to tgz
|
||||
pub fn (mut container DockerContainer) export(path string) ! {
|
||||
exec(cmd: 'docker export ${container.id} > ${path}')!
|
||||
}
|
||||
|
||||
// // open ssh shell to the cobtainer
|
||||
// pub fn (mut container DockerContainer) ssh_shell(cmd string) ! {
|
||||
// container.engine.node.shell(cmd)!
|
||||
// }
|
||||
|
||||
@[params]
|
||||
pub struct DockerShellArgs {
|
||||
pub mut:
|
||||
cmd string
|
||||
}
|
||||
|
||||
// open shell to the container using docker, is interactive, cannot use in script
|
||||
pub fn (mut container DockerContainer) shell(args DockerShellArgs) ! {
|
||||
mut cmd := ''
|
||||
if args.cmd.len == 0 {
|
||||
cmd = 'docker exec -ti ${container.id} /bin/bash'
|
||||
} else {
|
||||
cmd = "docker exec -ti ${container.id} /bin/bash -c '${args.cmd}'"
|
||||
}
|
||||
exec(cmd: cmd, shell: true, debug: true)!
|
||||
}
|
||||
|
||||
pub fn (mut container DockerContainer) execute(cmd_ string, silent bool) ! {
|
||||
cmd := 'docker exec ${container.id} ${cmd_}'
|
||||
exec(cmd: cmd, stdout: !silent)!
|
||||
}
|
||||
|
||||
// pub fn (mut container DockerContainer) ssh_enable() ! {
|
||||
// // mut docker_pubkey := pubkey
|
||||
// // cmd = "docker exec $container.id sh -c 'echo \"$docker_pubkey\" >> ~/.ssh/authorized_keys'"
|
||||
|
||||
// // if container.engine.node.executor is builder.ExecutorSSH {
|
||||
// // mut sshkey := container.engine.node.executor.info()['sshkey'] + '.pub'
|
||||
// // sshkey = os.read_file(sshkey) or { panic(err) }
|
||||
// // // add pub sshkey on authorized keys of node and container
|
||||
// // cmd = "echo \"$sshkey\" >> ~/.ssh/authorized_keys && docker exec $container.id sh -c 'echo \"$docker_pubkey\" >> ~/.ssh/authorized_keys && echo \"$sshkey\" >> ~/.ssh/authorized_keys'"
|
||||
// // }
|
||||
|
||||
// // wait making sure container started correctly
|
||||
// // time.sleep_ms(100 * time.millisecond)
|
||||
// // container.engine.node.executor.exec(cmd) !
|
||||
// }
|
||||
49
lib/virt/docker/docker_container_create.v
Normal file
49
lib/virt/docker/docker_container_create.v
Normal file
@@ -0,0 +1,49 @@
|
||||
module docker
|
||||
|
||||
import freeflowuniverse.herolib.osal { exec }
|
||||
|
||||
pub fn (mut e DockerEngine) container_create(args DockerContainerCreateArgs) !&DockerContainer {
|
||||
mut ports := ''
|
||||
mut mounts := ''
|
||||
mut env := ''
|
||||
mut command := args.command
|
||||
|
||||
for var, value in args.env {
|
||||
env += '-e ${var}="${value}"'
|
||||
}
|
||||
|
||||
for port in args.forwarded_ports {
|
||||
ports = ports + '-p ${port} '
|
||||
}
|
||||
|
||||
for mount in args.mounted_volumes {
|
||||
mounts += '-v ${mount} '
|
||||
}
|
||||
mut image := '${args.image_repo}'
|
||||
|
||||
if args.image_tag != '' {
|
||||
image = image + ':${args.image_tag}'
|
||||
}
|
||||
|
||||
if image == 'threefold' || image == 'threefold:latest' || image == '' {
|
||||
image = 'threefoldtech/grid3_ubuntu_dev'
|
||||
command = '/usr/local/bin/boot.sh'
|
||||
}
|
||||
|
||||
privileged := if args.privileged { '--privileged' } else { '' }
|
||||
|
||||
// if forwarded ports passed in the args not containing mapping tp ssh (22) create one
|
||||
if !contains_ssh_port(args.forwarded_ports) {
|
||||
// find random free port in the node
|
||||
mut port := e.get_free_port() or { panic('No free port.') }
|
||||
ports += '-p ${port}:22/tcp'
|
||||
}
|
||||
|
||||
exec(
|
||||
cmd: 'docker run --hostname ${args.hostname} ${privileged} --sysctl net.ipv6.conf.all.disable_ipv6=0 --name ${args.name} ${ports} ${env} ${mounts} -d -t ${image} ${command}'
|
||||
)!
|
||||
// Have to reload the containers as container_get works from memory
|
||||
e.containers_load()!
|
||||
mut container := e.container_get(name: args.name)!
|
||||
return container
|
||||
}
|
||||
259
lib/virt/docker/docker_engine.v
Normal file
259
lib/virt/docker/docker_engine.v
Normal file
@@ -0,0 +1,259 @@
|
||||
module docker
|
||||
|
||||
import freeflowuniverse.herolib.osal { cputype, exec, platform }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.virt.utils
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
// import freeflowuniverse.herolib.installers.swarm
|
||||
|
||||
// https://docs.docker.com/reference/
|
||||
|
||||
@[heap]
|
||||
pub struct DockerEngine {
|
||||
name string
|
||||
pub mut:
|
||||
sshkeys_allowed []string // all keys here have access over ssh into the machine, when ssh enabled
|
||||
images []DockerImage
|
||||
containers []DockerContainer
|
||||
buildpath string
|
||||
localonly bool
|
||||
cache bool = true
|
||||
push bool
|
||||
platform []BuildPlatformType // used to build
|
||||
registries []DockerRegistry // one or more supported DockerRegistries
|
||||
prefix string
|
||||
}
|
||||
|
||||
pub enum BuildPlatformType {
|
||||
linux_arm64
|
||||
linux_amd64
|
||||
}
|
||||
|
||||
// check docker has been installed & enabled on node
|
||||
pub fn (mut e DockerEngine) init() ! {
|
||||
if e.buildpath == '' {
|
||||
e.buildpath = '/tmp/builder'
|
||||
exec(cmd: 'mkdir -p ${e.buildpath}', stdout: false)!
|
||||
}
|
||||
if e.platform == [] {
|
||||
if platform() == .ubuntu && cputype() == .intel {
|
||||
e.platform = [.linux_amd64]
|
||||
} else if platform() == .osx && cputype() == .arm {
|
||||
e.platform = [.linux_arm64]
|
||||
} else {
|
||||
return error('only implemented ubuntu on amd and osx on arm for now for docker engine.')
|
||||
}
|
||||
}
|
||||
e.load()!
|
||||
}
|
||||
|
||||
// reload the state from system
|
||||
pub fn (mut e DockerEngine) load() ! {
|
||||
e.images_load()!
|
||||
e.containers_load()!
|
||||
}
|
||||
|
||||
// load all containers, they can be consulted in e.containers
|
||||
// see obj: DockerContainer as result in e.containers
|
||||
pub fn (mut e DockerEngine) containers_load() ! {
|
||||
e.containers = []DockerContainer{}
|
||||
mut ljob := exec(
|
||||
// we used || because sometimes the command has | in it and this will ruin all subsequent columns
|
||||
cmd: "docker ps -a --no-trunc --format '{{.ID}}||{{.Names}}||{{.Image}}||{{.Command}}||{{.CreatedAt}}||{{.Ports}}||{{.State}}||{{.Size}}||{{.Mounts}}||{{.Networks}}||{{.Labels}}'"
|
||||
ignore_error_codes: [6]
|
||||
stdout: false
|
||||
)!
|
||||
lines := ljob.output
|
||||
for line in lines {
|
||||
if line.trim_space() == '' {
|
||||
continue
|
||||
}
|
||||
fields := line.split('||').map(utils.clear_str)
|
||||
if fields.len < 11 {
|
||||
panic('docker ps needs to output 11 parts.\n${fields}')
|
||||
}
|
||||
id := fields[0]
|
||||
mut container := DockerContainer{
|
||||
engine: &e
|
||||
image: e.image_get(id: fields[2])!
|
||||
}
|
||||
|
||||
container.id = id
|
||||
container.name = texttools.name_fix(fields[1])
|
||||
container.command = fields[3]
|
||||
container.created = utils.parse_time(fields[4])!
|
||||
container.ports = utils.parse_ports(fields[5])!
|
||||
container.status = utils.parse_container_state(fields[6])!
|
||||
container.memsize = utils.parse_size_mb(fields[7])!
|
||||
container.mounts = utils.parse_mounts(fields[8])!
|
||||
container.networks = utils.parse_networks(fields[9])!
|
||||
container.labels = utils.parse_labels(fields[10])!
|
||||
container.ssh_enabled = utils.contains_ssh_port(container.ports)
|
||||
// console.print_debug(container)
|
||||
e.containers << container
|
||||
}
|
||||
}
|
||||
|
||||
// EXISTS, GET
|
||||
|
||||
@[params]
|
||||
pub struct ContainerGetArgs {
|
||||
pub mut:
|
||||
name string
|
||||
id string
|
||||
image_id string
|
||||
// tag string
|
||||
// digest string
|
||||
}
|
||||
|
||||
pub struct ContainerGetError {
|
||||
Error
|
||||
pub:
|
||||
args ContainerGetArgs
|
||||
notfound bool
|
||||
toomany bool
|
||||
}
|
||||
|
||||
pub fn (err ContainerGetError) msg() string {
|
||||
if err.notfound {
|
||||
return 'Could not find image with args:\n${err.args}'
|
||||
}
|
||||
if err.toomany {
|
||||
return 'Found more than 1 container with args:\n${err.args}'
|
||||
}
|
||||
panic('unknown error for ContainerGetError')
|
||||
}
|
||||
|
||||
pub fn (err ContainerGetError) code() int {
|
||||
if err.notfound {
|
||||
return 1
|
||||
}
|
||||
if err.toomany {
|
||||
return 2
|
||||
}
|
||||
panic('unknown error for ContainerGetError')
|
||||
}
|
||||
|
||||
// get containers from memory
|
||||
// params:
|
||||
// name string (can also be a glob e.g. use *,? and [])
|
||||
// id string
|
||||
// image_id string
|
||||
pub fn (mut e DockerEngine) containers_get(args_ ContainerGetArgs) ![]&DockerContainer {
|
||||
mut args := args_
|
||||
args.name = texttools.name_fix(args.name)
|
||||
mut res := []&DockerContainer{}
|
||||
for _, c in e.containers {
|
||||
if args.name.contains('*') || args.name.contains('?') || args.name.contains('[') {
|
||||
if c.name.match_glob(args.name) {
|
||||
res << &c
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if c.name == args.name || c.id == args.id {
|
||||
res << &c
|
||||
continue
|
||||
}
|
||||
}
|
||||
if args.image_id.len > 0 && c.image.id == args.image_id {
|
||||
res << &c
|
||||
}
|
||||
}
|
||||
if res.len == 0 {
|
||||
return ContainerGetError{
|
||||
args: args
|
||||
notfound: true
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// get container from memory, can use match_glob see https://modules.vlang.io/index.html#string.match_glob
|
||||
pub fn (mut e DockerEngine) container_get(args_ ContainerGetArgs) !&DockerContainer {
|
||||
mut args := args_
|
||||
args.name = texttools.name_fix(args.name)
|
||||
mut res := e.containers_get(args)!
|
||||
if res.len > 1 {
|
||||
return ContainerGetError{
|
||||
args: args
|
||||
notfound: true
|
||||
}
|
||||
}
|
||||
return res[0]
|
||||
}
|
||||
|
||||
pub fn (mut e DockerEngine) container_exists(args ContainerGetArgs) !bool {
|
||||
e.container_get(args) or {
|
||||
if err.code() == 1 {
|
||||
return false
|
||||
}
|
||||
return err
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
pub fn (mut e DockerEngine) container_delete(args ContainerGetArgs) ! {
|
||||
mut c := e.container_get(args)!
|
||||
c.delete()!
|
||||
e.load()!
|
||||
}
|
||||
|
||||
// remove one or more container
|
||||
pub fn (mut e DockerEngine) containers_delete(args ContainerGetArgs) ! {
|
||||
mut cs := e.containers_get(args)!
|
||||
for mut c in cs {
|
||||
c.delete()!
|
||||
}
|
||||
e.load()!
|
||||
}
|
||||
|
||||
// import a container into an image, run docker container with it
|
||||
// image_repo examples ['myimage', 'myimage:latest']
|
||||
// if DockerContainerCreateArgs contains a name, container will be created and restarted
|
||||
pub fn (mut e DockerEngine) container_import(path string, mut args DockerContainerCreateArgs) !&DockerContainer {
|
||||
mut image := args.image_repo
|
||||
if args.image_tag != '' {
|
||||
image = image + ':${args.image_tag}'
|
||||
}
|
||||
|
||||
exec(cmd: 'docker import ${path} ${image}', stdout: false)!
|
||||
// make sure we start from loaded image
|
||||
return e.container_create(args)
|
||||
}
|
||||
|
||||
// reset all images & containers, CAREFUL!
|
||||
pub fn (mut e DockerEngine) reset_all() ! {
|
||||
for mut container in e.containers.clone() {
|
||||
container.delete()!
|
||||
}
|
||||
for mut image in e.images.clone() {
|
||||
image.delete(true)!
|
||||
}
|
||||
exec(cmd: 'docker image prune -a -f', stdout: false) or { panic(err) }
|
||||
exec(cmd: 'docker builder prune -a -f', stdout: false) or { panic(err) }
|
||||
osal.done_reset()!
|
||||
e.load()!
|
||||
}
|
||||
|
||||
// Get free port
|
||||
pub fn (mut e DockerEngine) get_free_port() ?int {
|
||||
mut used_ports := []int{}
|
||||
mut range := []int{}
|
||||
|
||||
for c in e.containers {
|
||||
for p in c.forwarded_ports {
|
||||
used_ports << p.split(':')[0].int()
|
||||
}
|
||||
}
|
||||
|
||||
for i in 20000 .. 40000 {
|
||||
if i !in used_ports {
|
||||
range << i
|
||||
}
|
||||
}
|
||||
// arrays.shuffle<int>(mut range, 0)
|
||||
if range.len == 0 {
|
||||
return none
|
||||
}
|
||||
return range[0]
|
||||
}
|
||||
26
lib/virt/docker/docker_factory.v
Normal file
26
lib/virt/docker/docker_factory.v
Normal file
@@ -0,0 +1,26 @@
|
||||
module docker
|
||||
|
||||
@[params]
|
||||
pub struct DockerEngineArgs {
|
||||
pub mut:
|
||||
sshkeys_allowed []string // ssh keys which will be inserted when a docker gets deployed (is not implemented yet)
|
||||
name string = 'default'
|
||||
localonly bool // do you build for local utilization only
|
||||
prefix string // e.g. despiegk/ or myimage registry-host:5000/despiegk/) is added to the name when pushing
|
||||
}
|
||||
|
||||
// if sshkeys_allowed empty array will check the local machine for loaded sshkeys
|
||||
pub fn new(args DockerEngineArgs) !DockerEngine {
|
||||
mut args2 := args
|
||||
if args2.name == '' {
|
||||
args2.name = 'local'
|
||||
}
|
||||
mut de := DockerEngine{
|
||||
name: args2.name
|
||||
sshkeys_allowed: args2.sshkeys_allowed
|
||||
prefix: args.prefix
|
||||
localonly: args.localonly
|
||||
}
|
||||
de.init()!
|
||||
return de
|
||||
}
|
||||
134
lib/virt/docker/docker_image.v
Normal file
134
lib/virt/docker/docker_image.v
Normal file
@@ -0,0 +1,134 @@
|
||||
module docker
|
||||
|
||||
import time
|
||||
import freeflowuniverse.herolib.osal { exec }
|
||||
import freeflowuniverse.herolib.virt.utils
|
||||
|
||||
@[heap]
|
||||
pub struct DockerImage {
|
||||
pub mut:
|
||||
repo string
|
||||
id string
|
||||
tag string
|
||||
digest string
|
||||
size int // size in MB
|
||||
created time.Time
|
||||
engine &DockerEngine @[str: skip]
|
||||
}
|
||||
|
||||
// delete docker image
|
||||
pub fn (mut image DockerImage) delete(force bool) ! {
|
||||
mut forcestr := ''
|
||||
if force {
|
||||
forcestr = '-f'
|
||||
}
|
||||
exec(cmd: 'docker rmi ${image.id} ${forcestr}', stdout: false)!
|
||||
mut x := 0
|
||||
for image2 in image.engine.images {
|
||||
if image2.id == image.id {
|
||||
image.engine.images.delete(x)
|
||||
}
|
||||
x += 1
|
||||
}
|
||||
}
|
||||
|
||||
// export docker image to tar.gz
|
||||
pub fn (mut image DockerImage) export(path string) !string {
|
||||
exec(cmd: 'docker save ${image.id} > ${path}', stdout: false)!
|
||||
return ''
|
||||
}
|
||||
|
||||
// import docker image back into the local env
|
||||
pub fn (mut image DockerImage) load(path string) ! {
|
||||
exec(cmd: 'docker load < ${path}', stdout: false)!
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct ImageGetArgs {
|
||||
pub:
|
||||
repo string
|
||||
tag string
|
||||
digest string
|
||||
id string
|
||||
}
|
||||
|
||||
pub struct ImageGetError {
|
||||
Error
|
||||
pub:
|
||||
args ImageGetArgs
|
||||
notfound bool
|
||||
toomany bool
|
||||
}
|
||||
|
||||
pub fn (err ImageGetError) msg() string {
|
||||
if err.notfound {
|
||||
return 'Could not find image with args:\n${err.args}'
|
||||
}
|
||||
if err.toomany {
|
||||
return 'Found more than 1 image with args:\n${err.args}'
|
||||
}
|
||||
panic('unknown error for ImageGetError')
|
||||
}
|
||||
|
||||
pub fn (err ImageGetError) code() int {
|
||||
if err.notfound {
|
||||
return 1
|
||||
}
|
||||
if err.toomany {
|
||||
return 2
|
||||
}
|
||||
panic('unknown error for ImageGetError')
|
||||
}
|
||||
|
||||
// find image based on repo and optional tag
|
||||
// args:
|
||||
// repo string
|
||||
// tag string
|
||||
// digest string
|
||||
// id string
|
||||
pub fn (mut e DockerEngine) image_get(args ImageGetArgs) !&DockerImage {
|
||||
mut counter := 0
|
||||
mut result_digest := ''
|
||||
for i in e.images {
|
||||
if args.digest == args.digest {
|
||||
return &i
|
||||
}
|
||||
if args.digest != '' {
|
||||
continue
|
||||
}
|
||||
if args.repo != '' && i.repo != args.repo {
|
||||
continue
|
||||
}
|
||||
if args.tag != '' && i.tag != args.tag {
|
||||
continue
|
||||
}
|
||||
if args.id != '' && i.id != args.id {
|
||||
continue
|
||||
}
|
||||
result_digest = i.digest
|
||||
counter += 1
|
||||
}
|
||||
if counter > 0 {
|
||||
return ImageGetError{
|
||||
args: args
|
||||
toomany: true
|
||||
}
|
||||
}
|
||||
if counter == 0 {
|
||||
return ImageGetError{
|
||||
args: args
|
||||
notfound: true
|
||||
}
|
||||
}
|
||||
return e.image_get(digest: result_digest)!
|
||||
}
|
||||
|
||||
pub fn (mut e DockerEngine) image_exists(args ImageGetArgs) !bool {
|
||||
e.image_get(args) or {
|
||||
if err.code() == 1 {
|
||||
return false
|
||||
}
|
||||
return err
|
||||
}
|
||||
return true
|
||||
}
|
||||
286
lib/virt/docker/docker_recipe.v
Normal file
286
lib/virt/docker/docker_recipe.v
Normal file
@@ -0,0 +1,286 @@
|
||||
module docker
|
||||
|
||||
import freeflowuniverse.herolib.data.paramsparser { Params }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.osal { exec, file_write }
|
||||
import crypto.md5
|
||||
import v.embed_file
|
||||
import os
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
// only 2 supported for now
|
||||
pub enum PlatformType {
|
||||
alpine
|
||||
ubuntu
|
||||
}
|
||||
|
||||
type RecipeItem = AddFileEmbeddedItem
|
||||
| CmdItem
|
||||
| CopyItem
|
||||
| EntryPointItem
|
||||
| EnvItem
|
||||
| ExposeItem
|
||||
| FromItem
|
||||
| PackageItem
|
||||
| RunItem
|
||||
| VolumeItem
|
||||
| WorkDirItem
|
||||
| ZinitItem
|
||||
|
||||
pub struct RecipeArgs {
|
||||
pub:
|
||||
name string
|
||||
prefix string // string (e.g. despiegk/ or myimage registry-host:5000/despiegk/) is added to the name when pushing
|
||||
// also exists on docker engine level, if not used here will come from the docker engine level
|
||||
tag string //(default is 'latest' but can be a version nr)
|
||||
platform PlatformType
|
||||
zinit bool = true
|
||||
}
|
||||
|
||||
// params
|
||||
// ```
|
||||
// name string
|
||||
// prefix string (e.g. despiegk/ or myimage registry-host:5000/despiegk/) is added to the name when pushing
|
||||
// also exists on docker engine level, if not used here will come from the docker engine level
|
||||
// tag string (default is 'latest' but can be a version nr)
|
||||
// platform PlatformType
|
||||
// zinit bool = true
|
||||
// ```
|
||||
pub fn (mut e DockerEngine) recipe_new(args RecipeArgs) DockerBuilderRecipe {
|
||||
if args.name == '' {
|
||||
panic('name cannot be empty.')
|
||||
}
|
||||
return DockerBuilderRecipe{
|
||||
engine: &e
|
||||
platform: args.platform
|
||||
name: args.name
|
||||
zinit: args.zinit
|
||||
tag: args.tag
|
||||
prefix: args.prefix
|
||||
}
|
||||
}
|
||||
|
||||
@[heap]
|
||||
pub struct DockerBuilderRecipe {
|
||||
pub mut:
|
||||
name string
|
||||
prefix string
|
||||
tag string
|
||||
params Params
|
||||
files []embed_file.EmbedFileData
|
||||
engine &DockerEngine @[str: skip]
|
||||
items []RecipeItem
|
||||
platform PlatformType
|
||||
zinit bool
|
||||
}
|
||||
|
||||
// delete the working directory
|
||||
pub fn (mut b DockerBuilderRecipe) delete() ! {
|
||||
// exec(cmd:)
|
||||
panic('implement')
|
||||
}
|
||||
|
||||
pub fn (mut b DockerBuilderRecipe) render() !string {
|
||||
b.check_from_statement()!
|
||||
b.check_conf_add()!
|
||||
mut items := []string{}
|
||||
mut zinitexists := false
|
||||
for mut item in b.items {
|
||||
item_str := match mut item {
|
||||
FromItem {
|
||||
item.check()!
|
||||
item.render()!
|
||||
}
|
||||
RunItem {
|
||||
item.check()!
|
||||
item.render()!
|
||||
}
|
||||
PackageItem {
|
||||
item.check()!
|
||||
item.render()!
|
||||
}
|
||||
CmdItem {
|
||||
item.check()!
|
||||
item.render()!
|
||||
}
|
||||
AddFileEmbeddedItem {
|
||||
item.check()!
|
||||
item.render()!
|
||||
}
|
||||
EntryPointItem {
|
||||
item.check()!
|
||||
item.render()!
|
||||
}
|
||||
EnvItem {
|
||||
item.check()!
|
||||
item.render()!
|
||||
}
|
||||
WorkDirItem {
|
||||
item.check()!
|
||||
item.render()!
|
||||
}
|
||||
CopyItem {
|
||||
item.check()!
|
||||
item.render()!
|
||||
}
|
||||
ExposeItem {
|
||||
item.check()!
|
||||
item.render()!
|
||||
}
|
||||
VolumeItem {
|
||||
item.check()!
|
||||
item.render()!
|
||||
}
|
||||
ZinitItem {
|
||||
zinitexists = true
|
||||
item.check()!
|
||||
item.render()!
|
||||
}
|
||||
}
|
||||
items << item_str
|
||||
}
|
||||
if zinitexists {
|
||||
// means there is a zinit, we need to add the directory for zinit
|
||||
items << 'COPY zinit /etc/zinit\n'
|
||||
}
|
||||
return items.join('\n')
|
||||
}
|
||||
|
||||
fn (mut b DockerBuilderRecipe) check_from_statement() ! {
|
||||
mut fromfound := false
|
||||
for item3 in b.items {
|
||||
if item3 is FromItem {
|
||||
fromfound = true
|
||||
}
|
||||
}
|
||||
if fromfound == false {
|
||||
// put automatically alpine or ubuntu in
|
||||
// console.print_debug(" *** put automatically alpine or ubuntu in")
|
||||
if b.platform == .alpine {
|
||||
b.items.prepend(FromItem{ recipe: &b, image: 'alpine', tag: 'latest' })
|
||||
} else {
|
||||
b.items.prepend(FromItem{ recipe: &b, image: 'ubuntu', tag: 'latest' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut b DockerBuilderRecipe) check_conf_add() ! {
|
||||
// we need to make sure we insert it after the last FromItem
|
||||
mut lastfromcounter := 0
|
||||
mut counter := 0
|
||||
for item3 in b.items {
|
||||
if item3 is FromItem {
|
||||
lastfromcounter = counter
|
||||
}
|
||||
counter += 1
|
||||
}
|
||||
for item in b.items {
|
||||
if item is AddFileEmbeddedItem {
|
||||
if item.source == 'conf.sh' {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
b.items.insert(lastfromcounter + 1, AddFileEmbeddedItem{
|
||||
recipe: &b
|
||||
source: 'conf.sh'
|
||||
dest: '/conf.sh'
|
||||
check_embed: false
|
||||
})
|
||||
}
|
||||
|
||||
pub fn (mut b DockerBuilderRecipe) path() string {
|
||||
destpath := '${b.engine.buildpath}/${b.name}'
|
||||
return destpath
|
||||
}
|
||||
|
||||
pub fn (mut b DockerBuilderRecipe) build(reset bool) ! {
|
||||
dockerfilecontent := b.render()!
|
||||
|
||||
destpath := b.path()
|
||||
os.mkdir_all(destpath)!
|
||||
file_write('${destpath}/Dockerfile', dockerfilecontent)!
|
||||
for item in b.files {
|
||||
filename := item.path.all_after_first('/')
|
||||
file_write('${destpath}/${filename}', item.to_string())!
|
||||
}
|
||||
confsh := '
|
||||
export NAME="${b.name}"
|
||||
'
|
||||
file_write('${destpath}/conf.sh', texttools.dedent(confsh))!
|
||||
if b.engine.localonly {
|
||||
b.tag = 'local'
|
||||
} else {
|
||||
b.tag = 'latest'
|
||||
}
|
||||
mut bplatform := '--platform='
|
||||
for plat in b.engine.platform {
|
||||
if plat == .linux_arm64 {
|
||||
bplatform += ',linux/arm64'
|
||||
}
|
||||
if plat == .linux_amd64 {
|
||||
bplatform += ',linux/amd64'
|
||||
}
|
||||
bplatform = bplatform.replace('=,', '=').trim(',')
|
||||
}
|
||||
mut nocache := ''
|
||||
if b.engine.cache == false || reset {
|
||||
nocache = '--no-cache'
|
||||
}
|
||||
|
||||
mut bpush := ''
|
||||
if b.engine.push == true {
|
||||
bpush = '--push'
|
||||
}
|
||||
cmd := '
|
||||
set -ex
|
||||
cd ${destpath}
|
||||
docker buildx build . -t ${b.name}:${b.tag} --ssh default=\${SSH_AUTH_SOCK} ${nocache} ${bplatform} ${bpush}
|
||||
'
|
||||
|
||||
mut cmdshell := 'set -ex\ncd ${destpath}\ndocker rm ${b.name} -f > /dev/null 2>&1\ndocker run --name ${b.name} '
|
||||
if b.zinit { // means zinit is in docker
|
||||
cmdshell += '-d'
|
||||
// cmdshell += ' -v ${destpath}/zinit:/etc/zinit \\\n' //we don\t want to add zinit any more
|
||||
} else {
|
||||
cmdshell += '-it \\\n'
|
||||
}
|
||||
cmdshell += ' -v ${destpath}:/src \\\n'
|
||||
cmdshell += ' -v /run/host-services/ssh-auth.sock:/run/host-services/ssh-auth.sock \\\n'
|
||||
cmdshell += ' -e SSH_AUTH_SOCK="/run/host-services/ssh-auth.sock" \\\n'
|
||||
cmdshell += ' --privileged \\\n'
|
||||
cmdshell += ' --hostname ${b.name} ${b.name}:${b.tag}'
|
||||
if b.zinit {
|
||||
cmdshell += '\n\ndocker exec -ti ${b.name} /bin/shell.sh'
|
||||
} else {
|
||||
cmdshell += " '/bin/shell.sh'\n"
|
||||
}
|
||||
cmdshell += '\ndocker rm ${b.name} -f > /dev/null 2>&1\n'
|
||||
// console.print_debug(cmdshell)
|
||||
|
||||
mut tohash := dockerfilecontent + b.name + cmdshell + cmd
|
||||
for mut item in b.items {
|
||||
if mut item is AddFileEmbeddedItem {
|
||||
c := item.getcontent()!
|
||||
tohash += c
|
||||
}
|
||||
}
|
||||
|
||||
image_exists := b.engine.image_exists(repo: b.name)!
|
||||
hashnew := md5.hexhash(tohash)
|
||||
|
||||
if image_exists && reset == false && osal.done_exists('build_${b.name}') {
|
||||
hashlast := osal.done_get('build_${b.name}') or { '' }
|
||||
if hashnew == hashlast {
|
||||
console.print_debug('\n ** BUILD ALREADY DONE FOR ${b.name.to_upper()}\n')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
file_write('${destpath}/shell.sh', cmdshell)!
|
||||
os.chmod('${destpath}/shell.sh', 0o777)!
|
||||
|
||||
exec(scriptpath: '${destpath}/build.sh', cmd: cmd, scriptkeep: true)!
|
||||
|
||||
osal.done_set('build_${b.name}', hashnew)!
|
||||
}
|
||||
65
lib/virt/docker/docker_recipe_addfile_embedded.v
Normal file
65
lib/virt/docker/docker_recipe_addfile_embedded.v
Normal file
@@ -0,0 +1,65 @@
|
||||
module docker
|
||||
|
||||
import freeflowuniverse.herolib.osal { file_read }
|
||||
|
||||
@[params]
|
||||
pub struct AddFileEmbeddedArgs {
|
||||
pub mut:
|
||||
source string // is the filename, needs to be embedded
|
||||
dest string // in the container we're building
|
||||
make_executable bool
|
||||
}
|
||||
|
||||
pub struct AddFileEmbeddedItem {
|
||||
pub mut:
|
||||
source string
|
||||
dest string // in the container we're building
|
||||
recipe &DockerBuilderRecipe @[str: skip]
|
||||
make_executable bool
|
||||
check_embed bool = true
|
||||
}
|
||||
|
||||
// to do something like: 'Add alpine:latest'
|
||||
pub fn (mut b DockerBuilderRecipe) add_file_embedded(args AddFileEmbeddedArgs) ! {
|
||||
mut item := AddFileEmbeddedItem{
|
||||
source: args.source
|
||||
dest: args.dest
|
||||
make_executable: args.make_executable
|
||||
recipe: &b
|
||||
}
|
||||
if item.source == '' {
|
||||
return error('source cant be empty, \n${b}')
|
||||
}
|
||||
if item.dest == '' {
|
||||
return error('dest cant be empty, \n${b}')
|
||||
}
|
||||
b.items << item
|
||||
}
|
||||
|
||||
pub fn (mut i AddFileEmbeddedItem) check() ! {
|
||||
if i.check_embed == false {
|
||||
return
|
||||
}
|
||||
for fileitem in i.recipe.files {
|
||||
filename := fileitem.path.all_after_first('/')
|
||||
if filename == i.source {
|
||||
return
|
||||
}
|
||||
}
|
||||
return error('Could not find filename: ${i.source} in embedded files. \n ${i}')
|
||||
}
|
||||
|
||||
pub fn (mut i AddFileEmbeddedItem) render() !string {
|
||||
mut out := 'ADD ${i.source} ${i.dest}'
|
||||
if i.make_executable {
|
||||
out += '\nRUN chmod +x ${i.dest}\n'
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// get code from the file added
|
||||
fn (mut i AddFileEmbeddedItem) getcontent() !string {
|
||||
srcpath := '${i.recipe.engine.buildpath}/${i.recipe.name}/${i.source}'
|
||||
content := file_read(srcpath)!
|
||||
return content
|
||||
}
|
||||
38
lib/virt/docker/docker_recipe_cmd.v
Normal file
38
lib/virt/docker/docker_recipe_cmd.v
Normal file
@@ -0,0 +1,38 @@
|
||||
module docker
|
||||
|
||||
@[params]
|
||||
pub struct CmdArgs {
|
||||
pub mut:
|
||||
cmd string
|
||||
}
|
||||
|
||||
pub struct CmdItem {
|
||||
pub mut:
|
||||
cmd string
|
||||
recipe &DockerBuilderRecipe @[str: skip]
|
||||
}
|
||||
|
||||
// add run command to docker, is the cmd which is run when docker get's built
|
||||
pub fn (mut b DockerBuilderRecipe) add_cmd(args CmdArgs) ! {
|
||||
mut item := CmdItem{
|
||||
cmd: args.cmd
|
||||
recipe: &b
|
||||
}
|
||||
if item.cmd == '' {
|
||||
return error('cmd cannot be empty, \n${b}')
|
||||
}
|
||||
b.items << item
|
||||
}
|
||||
|
||||
pub fn (mut i CmdItem) check() ! {
|
||||
// TODO checks to see if is valid
|
||||
}
|
||||
|
||||
pub fn (mut i CmdItem) render() !string {
|
||||
// todo: need to be able to deal with argement e.g. bash /bin/shell.sh this needs to be 2 elements
|
||||
mut cmds := i.cmd.fields()
|
||||
for mut cmd_ in cmds {
|
||||
cmd_ = "\"${cmd_}\""
|
||||
}
|
||||
return 'CMD [${cmds.join(', ')}]'
|
||||
}
|
||||
52
lib/virt/docker/docker_recipe_code.v
Normal file
52
lib/virt/docker/docker_recipe_code.v
Normal file
@@ -0,0 +1,52 @@
|
||||
module docker
|
||||
|
||||
import freeflowuniverse.herolib.develop.gittools
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
// import freeflowuniverse.herolib.core.pathlib
|
||||
|
||||
@[params]
|
||||
pub struct CodeGetArgs {
|
||||
pub mut:
|
||||
url string // e.g. https://github.com/vlang/v
|
||||
// other example url := 'https://github.com/threefoldfoundation/www_examplesite/tree/development/manual'
|
||||
pull bool
|
||||
reset bool
|
||||
name string
|
||||
dest string // where does the directory need to be checked out to
|
||||
}
|
||||
|
||||
// checkout a code repository on right location
|
||||
pub fn (mut r DockerBuilderRecipe) add_codeget(args_ CodeGetArgs) ! {
|
||||
mut args := args_
|
||||
mut gs := gittools.get(coderoot: '${r.path()}/code')!
|
||||
|
||||
locator := gs.locator_new(args.url)!
|
||||
|
||||
mut gr := gs.repo_get(locator: locator, pull: args.pull, reset: args.reset)!
|
||||
|
||||
if args.name == '' {
|
||||
args.name = gr.addr.name
|
||||
}
|
||||
|
||||
if args.dest == '' {
|
||||
args.dest = '/code/${args.name}'
|
||||
}
|
||||
|
||||
// gs.repos_print(filter: '')
|
||||
// console.print_debug(gr)
|
||||
// this will show the exact path of the manual
|
||||
// console.print_debug(gr.path_content_get())
|
||||
|
||||
// mut gitaddr := gs.addr_get_from_url(url: url)!
|
||||
|
||||
if args.dest.len < 2 {
|
||||
return error("dest is to short (min 3): now '${args.dest}'")
|
||||
}
|
||||
|
||||
commonpath := gr.path_relative()
|
||||
if commonpath.contains('..') {
|
||||
panic('bug should not be')
|
||||
}
|
||||
|
||||
r.add_copy(source: commonpath, dest: args.dest)!
|
||||
}
|
||||
73
lib/virt/docker/docker_recipe_code_gobuilder.v
Normal file
73
lib/virt/docker/docker_recipe_code_gobuilder.v
Normal file
@@ -0,0 +1,73 @@
|
||||
module docker
|
||||
|
||||
pub fn (mut r DockerBuilderRecipe) add_gobuilder() ! {
|
||||
r.add_package(name: 'musl-dev,gcc, g++, go, make')!
|
||||
r.add_env('GOPATH', '/app')!
|
||||
r.add_workdir(workdir: '/app')!
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct GoBuildArgs {
|
||||
pub mut:
|
||||
url string // e.g. https://github.com/valeriansaliou/sonic
|
||||
pull bool
|
||||
reset bool
|
||||
buildcmd string = 'go run build.go '
|
||||
copycmd string
|
||||
name string
|
||||
}
|
||||
|
||||
// do a build of a go package .
|
||||
// will get the code, pull and/or reset .
|
||||
// will the pull all dependencies .
|
||||
// will do the build
|
||||
// DEBUG TRICK: put debug flag on, which will not execute the build cmd .
|
||||
// you can go to /tmp/build/buildname and do shell.sh to debug
|
||||
pub fn (mut r DockerBuilderRecipe) add_gobuild_from_code(args GoBuildArgs) ! {
|
||||
r.add_codeget(url: args.url, name: args.name, reset: args.reset, pull: args.pull)!
|
||||
|
||||
if args.name == '' {
|
||||
return error('name needs to be specified.')
|
||||
}
|
||||
|
||||
r.add_run(
|
||||
cmd: '
|
||||
cd /code/${args.name}
|
||||
${args.buildcmd}
|
||||
${args.copycmd}
|
||||
'
|
||||
)!
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct GoPackageArgs {
|
||||
pub mut:
|
||||
name string // can be comma separated, can also be url e.g. github.com/caddyserver/xcaddy/cmd/xcaddy@latest
|
||||
postcmd string // normally empty
|
||||
}
|
||||
|
||||
// install go components
|
||||
pub fn (mut r DockerBuilderRecipe) add_go_package(args GoPackageArgs) ! {
|
||||
if args.name == '' {
|
||||
return error('name cannot be empty, name can be comma separated')
|
||||
}
|
||||
mut names := []string{}
|
||||
if args.name.contains(',') {
|
||||
for item2 in args.name.split(',') {
|
||||
names << item2.trim_space()
|
||||
}
|
||||
} else {
|
||||
if args.name != '' {
|
||||
names << args.name.trim_space()
|
||||
}
|
||||
}
|
||||
|
||||
for name in names {
|
||||
r.add_run(
|
||||
cmd: '
|
||||
go install ${name}
|
||||
${args.postcmd}
|
||||
'
|
||||
)!
|
||||
}
|
||||
}
|
||||
79
lib/virt/docker/docker_recipe_code_rustbuilder.v
Normal file
79
lib/virt/docker/docker_recipe_code_rustbuilder.v
Normal file
@@ -0,0 +1,79 @@
|
||||
module docker
|
||||
|
||||
// import freeflowuniverse.herolib.develop.gittools
|
||||
// import freeflowuniverse.herolib.core.pathlib
|
||||
|
||||
@[params]
|
||||
pub struct RustBuildArgs {
|
||||
pub mut:
|
||||
url string // e.g. https://github.com/valeriansaliou/sonic
|
||||
pull bool
|
||||
reset bool
|
||||
buildcmd string = 'cargo build --release'
|
||||
copycmd string
|
||||
debug bool // to be able to easily debug the intermediate step
|
||||
name string
|
||||
}
|
||||
|
||||
// do a build of a rust package .
|
||||
// will get the code, pull and/or reset .
|
||||
// will the pull all dependencies .
|
||||
// will do the build
|
||||
// DEBUG TRICK: put debug flag on, which will not execute the build cmd .
|
||||
// you can go to /tmp/build/buildname and do shell.sh to debug
|
||||
pub fn (mut r DockerBuilderRecipe) add_rustbuild_from_code(args RustBuildArgs) ! {
|
||||
r.add_codeget(url: args.url, name: args.name, reset: args.reset, pull: args.pull)!
|
||||
|
||||
r.add_run(
|
||||
cmd: '
|
||||
source ~/.cargo/env
|
||||
cd /code/${args.name}
|
||||
cargo update --dry-run
|
||||
'
|
||||
)!
|
||||
|
||||
if !args.debug {
|
||||
r.add_run(
|
||||
cmd: '
|
||||
source ~/.cargo/env
|
||||
cd /code/${args.name}
|
||||
${args.buildcmd}
|
||||
${args.copycmd}
|
||||
'
|
||||
)!
|
||||
}
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct RustPackageArgs {
|
||||
pub mut:
|
||||
name string // can be comma separated
|
||||
copycmd string // normally empty
|
||||
}
|
||||
|
||||
// use cargo install to install rust components
|
||||
pub fn (mut r DockerBuilderRecipe) add_rust_package(args RustPackageArgs) ! {
|
||||
if args.name == '' {
|
||||
return error('name cannot be empty, name can be comma separated')
|
||||
}
|
||||
mut names := []string{}
|
||||
if args.name.contains(',') {
|
||||
for item2 in args.name.split(',') {
|
||||
names << item2.trim_space()
|
||||
}
|
||||
} else {
|
||||
if args.name != '' {
|
||||
names << args.name.trim_space()
|
||||
}
|
||||
}
|
||||
|
||||
for name in names {
|
||||
r.add_run(
|
||||
cmd: '
|
||||
source ~/.cargo/env
|
||||
cargo install ${name}
|
||||
${args.copycmd}
|
||||
'
|
||||
)!
|
||||
}
|
||||
}
|
||||
52
lib/virt/docker/docker_recipe_copy.v
Normal file
52
lib/virt/docker/docker_recipe_copy.v
Normal file
@@ -0,0 +1,52 @@
|
||||
module docker
|
||||
|
||||
@[params]
|
||||
pub struct CopyArgs {
|
||||
pub mut:
|
||||
from string
|
||||
source string
|
||||
dest string
|
||||
make_executable bool // if set will make the file copied executable
|
||||
}
|
||||
|
||||
pub struct CopyItem {
|
||||
pub mut:
|
||||
from string
|
||||
source string
|
||||
dest string
|
||||
recipe &DockerBuilderRecipe @[str: skip]
|
||||
make_executable bool // if set will make the file copied executable
|
||||
// check_embed bool = true
|
||||
}
|
||||
|
||||
// to do something like: 'Add alpine:latest'
|
||||
pub fn (mut b DockerBuilderRecipe) add_copy(args CopyArgs) ! {
|
||||
mut item := CopyItem{
|
||||
from: args.from
|
||||
source: args.source
|
||||
dest: args.dest
|
||||
make_executable: args.make_executable
|
||||
recipe: &b
|
||||
}
|
||||
if item.source == '' {
|
||||
return error('source cant be empty, \n${b}')
|
||||
}
|
||||
if item.dest == '' {
|
||||
return error('dest cant be empty, \n${b}')
|
||||
}
|
||||
b.items << item
|
||||
}
|
||||
|
||||
pub fn (mut i CopyItem) check() ! {
|
||||
}
|
||||
|
||||
pub fn (mut i CopyItem) render() !string {
|
||||
mut out := 'COPY ${i.source} ${i.dest}'
|
||||
if i.from != '' {
|
||||
out = 'COPY --from=${i.from} ${i.source} ${i.dest}\n'
|
||||
}
|
||||
if i.make_executable {
|
||||
out += '\nRUN chmod +x ${i.dest}\n'
|
||||
}
|
||||
return out
|
||||
}
|
||||
55
lib/virt/docker/docker_recipe_download.v
Normal file
55
lib/virt/docker/docker_recipe_download.v
Normal file
@@ -0,0 +1,55 @@
|
||||
module docker
|
||||
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import freeflowuniverse.herolib.osal { download }
|
||||
|
||||
@[params]
|
||||
pub struct DownloadArgs {
|
||||
pub mut:
|
||||
name string // unique name which will be used in the cache
|
||||
url string
|
||||
reset bool // will remove
|
||||
minsize_kb u32 = 10 // is always in kb
|
||||
maxsize_kb u32
|
||||
hash string // if hash is known, will verify what hash is
|
||||
dest string // if specified will copy to that destination
|
||||
timeout int = 180
|
||||
retry int = 3
|
||||
}
|
||||
|
||||
// checkout a code repository on right location
|
||||
pub fn (mut r DockerBuilderRecipe) add_download(args_ DownloadArgs) ! {
|
||||
mut args := args_
|
||||
|
||||
if args.dest.len > 0 && args.dest.len < 2 {
|
||||
return error("dest is to short (min 3): now '${args.dest}'")
|
||||
}
|
||||
|
||||
if args.dest.contains('@name') {
|
||||
args.dest = args.dest.replace('@name', args.name)
|
||||
}
|
||||
if args.url.contains('@name') {
|
||||
args.url = args.url.replace('@name', args.name)
|
||||
}
|
||||
|
||||
download_dir := '${r.path()}/downloads'
|
||||
|
||||
mut p := download(
|
||||
url: args.url
|
||||
name: args.name
|
||||
reset: args.reset
|
||||
dest: '${download_dir}/${args.name}'
|
||||
minsize_kb: args.minsize_kb
|
||||
maxsize_kb: args.maxsize_kb
|
||||
timeout: args.timeout
|
||||
retry: args.retry
|
||||
hash: args.hash
|
||||
)!
|
||||
|
||||
commonpath := pathlib.path_relative(r.path(), p.path)!
|
||||
if commonpath.contains('..') {
|
||||
panic('bug should not be')
|
||||
}
|
||||
|
||||
r.add_copy(source: commonpath, dest: args.dest)!
|
||||
}
|
||||
39
lib/virt/docker/docker_recipe_entrypoint.v
Normal file
39
lib/virt/docker/docker_recipe_entrypoint.v
Normal file
@@ -0,0 +1,39 @@
|
||||
module docker
|
||||
|
||||
// import freeflowuniverse.herolib.builder
|
||||
|
||||
@[params]
|
||||
pub struct EntryPointArgs {
|
||||
pub mut:
|
||||
cmd string
|
||||
}
|
||||
|
||||
pub struct EntryPointItem {
|
||||
pub mut:
|
||||
cmd string
|
||||
recipe &DockerBuilderRecipe @[str: skip]
|
||||
}
|
||||
|
||||
pub fn (mut b DockerBuilderRecipe) add_entrypoint(args EntryPointArgs) ! {
|
||||
mut item := EntryPointItem{
|
||||
cmd: args.cmd
|
||||
recipe: &b
|
||||
}
|
||||
if item.cmd == '' {
|
||||
return error('cmd cannot be empty, \n${b}')
|
||||
}
|
||||
b.items << item
|
||||
}
|
||||
|
||||
pub fn (mut i EntryPointItem) check() ! {
|
||||
// TODO checks to see if is valid
|
||||
}
|
||||
|
||||
pub fn (mut i EntryPointItem) render() !string {
|
||||
// todo: need to be able to deal with argement e.g. bash /bin/shell.sh this needs to be 2 elements
|
||||
mut cmds := i.cmd.fields()
|
||||
for mut cmd_ in cmds {
|
||||
cmd_ = "\"${cmd_}\""
|
||||
}
|
||||
return 'ENTRYPOINT [${cmds.join(', ')}]'
|
||||
}
|
||||
29
lib/virt/docker/docker_recipe_env.v
Normal file
29
lib/virt/docker/docker_recipe_env.v
Normal file
@@ -0,0 +1,29 @@
|
||||
module docker
|
||||
|
||||
pub struct EnvItem {
|
||||
pub mut:
|
||||
name string
|
||||
value string
|
||||
}
|
||||
|
||||
pub fn (mut b DockerBuilderRecipe) add_env(name string, val string) ! {
|
||||
if name.len < 3 {
|
||||
return error('min length of name is 3')
|
||||
}
|
||||
if val.len < 3 {
|
||||
return error('min length of val is 3')
|
||||
}
|
||||
mut item := EnvItem{
|
||||
name: name.to_upper()
|
||||
value: val
|
||||
}
|
||||
b.items << item
|
||||
}
|
||||
|
||||
pub fn (mut i EnvItem) check() ! {
|
||||
// nothing much we can do here I guess
|
||||
}
|
||||
|
||||
pub fn (mut i EnvItem) render() !string {
|
||||
return "ENV ${i.name}='${i.value}'"
|
||||
}
|
||||
37
lib/virt/docker/docker_recipe_expose.v
Normal file
37
lib/virt/docker/docker_recipe_expose.v
Normal file
@@ -0,0 +1,37 @@
|
||||
module docker
|
||||
|
||||
@[params]
|
||||
pub struct ExposeARgs {
|
||||
pub mut:
|
||||
ports []string
|
||||
}
|
||||
|
||||
pub struct ExposeItem {
|
||||
pub mut:
|
||||
ports []string
|
||||
recipe &DockerBuilderRecipe @[str: skip]
|
||||
}
|
||||
|
||||
// to do something like: 'Expose 8080/udp'
|
||||
pub fn (mut b DockerBuilderRecipe) add_expose(args ExposeARgs) ! {
|
||||
mut item := ExposeItem{
|
||||
ports: args.ports
|
||||
recipe: &b
|
||||
}
|
||||
b.items << item
|
||||
}
|
||||
|
||||
pub fn (mut i ExposeItem) check() ! {
|
||||
if i.ports.len == 0 {
|
||||
return error('ports list cannot be empty')
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut i ExposeItem) render() !string {
|
||||
mut out := 'EXPOSE'
|
||||
for s in i.ports {
|
||||
out += ' ${s}'
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
59
lib/virt/docker/docker_recipe_from.v
Normal file
59
lib/virt/docker/docker_recipe_from.v
Normal file
@@ -0,0 +1,59 @@
|
||||
module docker
|
||||
|
||||
@[params]
|
||||
pub struct FromArgs {
|
||||
pub mut:
|
||||
image string
|
||||
tag string
|
||||
alias string
|
||||
}
|
||||
|
||||
pub struct FromItem {
|
||||
pub mut:
|
||||
image string
|
||||
tag string
|
||||
recipe &DockerBuilderRecipe @[str: skip]
|
||||
alias string
|
||||
}
|
||||
|
||||
// to do something like: 'FROM alpine:latest'
|
||||
pub fn (mut b DockerBuilderRecipe) add_from(args FromArgs) ! {
|
||||
mut item := FromItem{
|
||||
image: args.image
|
||||
tag: args.tag
|
||||
alias: args.alias
|
||||
recipe: &b
|
||||
}
|
||||
if item.tag == '' {
|
||||
if b.engine.localonly {
|
||||
item.tag = 'local'
|
||||
} else {
|
||||
item.tag = 'latest'
|
||||
}
|
||||
}
|
||||
mut prefix := b.prefix
|
||||
if b.engine.prefix.len > 0 {
|
||||
prefix = b.engine.prefix
|
||||
}
|
||||
if prefix.len > 0 && !(prefix.ends_with('/')) {
|
||||
return error("cannot use prefix if it doesn't end with /, was '${prefix}'")
|
||||
}
|
||||
item.image = '${prefix}${item.image}'
|
||||
if item.image == '' {
|
||||
return error('image name cannot be empty')
|
||||
}
|
||||
|
||||
b.items << item
|
||||
}
|
||||
|
||||
pub fn (mut i FromItem) check() ! {
|
||||
// TODO checks to see if is valid
|
||||
}
|
||||
|
||||
pub fn (mut i FromItem) render() !string {
|
||||
if i.alias == '' {
|
||||
return 'FROM ${i.image}:${i.tag}'
|
||||
} else {
|
||||
return 'FROM ${i.image}:${i.tag} AS ${i.alias}'
|
||||
}
|
||||
}
|
||||
109
lib/virt/docker/docker_recipe_package.v
Normal file
109
lib/virt/docker/docker_recipe_package.v
Normal file
@@ -0,0 +1,109 @@
|
||||
module docker
|
||||
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
@[params]
|
||||
pub struct PackageArgs {
|
||||
pub mut:
|
||||
name string
|
||||
names []string
|
||||
}
|
||||
|
||||
pub struct PackageItem {
|
||||
pub mut:
|
||||
names []string
|
||||
recipe &DockerBuilderRecipe @[str: skip]
|
||||
platform PlatformType
|
||||
}
|
||||
|
||||
// add one of more name (alpine packages), no need to do update, upgrade first,
|
||||
pub fn (mut b DockerBuilderRecipe) add_package(args PackageArgs) ! {
|
||||
mut package := PackageItem{
|
||||
recipe: &b
|
||||
names: args.names
|
||||
platform: b.platform
|
||||
}
|
||||
if args.name == '' && args.names == [] {
|
||||
return error('name or names cannot be empty, name can be comma separated')
|
||||
}
|
||||
if args.name.contains(',') {
|
||||
for item2 in args.name.split(',') {
|
||||
package.names << item2.trim_space()
|
||||
}
|
||||
} else {
|
||||
if args.name != '' {
|
||||
package.names << args.name.trim_space()
|
||||
}
|
||||
}
|
||||
|
||||
b.check_from_statement()!
|
||||
|
||||
// now check if we after each from find an apk upgrade
|
||||
mut updatedone := false
|
||||
for item3 in b.items {
|
||||
if item3 is FromItem {
|
||||
updatedone = false
|
||||
}
|
||||
if item3 is RunItem {
|
||||
if item3.cmd.contains('apk upgrade') {
|
||||
updatedone = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if updatedone == false {
|
||||
if b.platform == .alpine {
|
||||
// means we first need to do an update
|
||||
b.add_run(
|
||||
cmd: '
|
||||
#rm -rf /tmp/*
|
||||
#rm -rf /var/cache/name/*
|
||||
apk update
|
||||
apk upgrade
|
||||
'
|
||||
) or { return error('Failed to add run') }
|
||||
} else if b.platform == .ubuntu {
|
||||
b.add_run(
|
||||
cmd: '
|
||||
apt-get update
|
||||
apt-get install -y apt-transport-https'
|
||||
)!
|
||||
} else {
|
||||
panic('implement for ubuntu')
|
||||
}
|
||||
}
|
||||
|
||||
// lets now check of the package has already not been set before
|
||||
for package0 in b.items {
|
||||
if package0 is PackageItem {
|
||||
for packagename in package0.names {
|
||||
for packagenamecompare in package.names {
|
||||
if packagenamecompare == packagename {
|
||||
// we found a double
|
||||
return error('Cannot add the package again, there is a double. ${packagename} \n${b}')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// console.print_debug(package)
|
||||
if package.names.len == 0 {
|
||||
return error('could not find package names.\n ${b}\nARGS:\n${args}')
|
||||
}
|
||||
b.items << package
|
||||
}
|
||||
|
||||
pub fn (mut i PackageItem) check() ! {
|
||||
// maybe over time we can in redis hold a list of name possible names, so we can check at compile time if its going to work
|
||||
}
|
||||
|
||||
pub fn (mut i PackageItem) render() !string {
|
||||
mut names := ''
|
||||
for name in i.names {
|
||||
names += ' ${name} '
|
||||
}
|
||||
mut pkg_manager := 'apk add --no-cache'
|
||||
if i.platform == .ubuntu {
|
||||
pkg_manager = 'apt-get -y install'
|
||||
}
|
||||
return 'RUN ${pkg_manager} ${names}'
|
||||
}
|
||||
58
lib/virt/docker/docker_recipe_run.v
Normal file
58
lib/virt/docker/docker_recipe_run.v
Normal file
@@ -0,0 +1,58 @@
|
||||
module docker
|
||||
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
@[params]
|
||||
pub struct RunArgs {
|
||||
pub mut:
|
||||
cmd string
|
||||
}
|
||||
|
||||
pub struct RunItem {
|
||||
pub mut:
|
||||
cmd string
|
||||
recipe &DockerBuilderRecipe @[str: skip]
|
||||
}
|
||||
|
||||
// to do something like: 'FROM alpine:latest'
|
||||
pub fn (mut b DockerBuilderRecipe) add_run(args RunArgs) ! {
|
||||
mut item := RunItem{
|
||||
cmd: args.cmd
|
||||
recipe: &b
|
||||
}
|
||||
if item.cmd == '' {
|
||||
return error('cmd name cannot be empty')
|
||||
}
|
||||
b.items << item
|
||||
}
|
||||
|
||||
pub fn (mut i RunItem) check() ! {
|
||||
// nothing much we can do here I guess
|
||||
}
|
||||
|
||||
pub fn (mut i RunItem) render() !string {
|
||||
mut out := []string{}
|
||||
mut first := true
|
||||
i.cmd = texttools.dedent(i.cmd)
|
||||
for mut line in i.cmd.split_into_lines() {
|
||||
line = line.replace('\t', ' ')
|
||||
if line == '' {
|
||||
continue
|
||||
}
|
||||
if line.contains('&&') {
|
||||
return error("don't use && in scripts")
|
||||
}
|
||||
if line.trim_space().starts_with('#') {
|
||||
continue
|
||||
}
|
||||
if first {
|
||||
out << 'RUN ${line} \\'
|
||||
first = false
|
||||
} else {
|
||||
out << ' && ${line} \\'
|
||||
}
|
||||
}
|
||||
mut out2 := out.join_lines()
|
||||
out2 = out2.trim_string_right('\n').trim_string_right('\\')
|
||||
return out2
|
||||
}
|
||||
91
lib/virt/docker/docker_recipe_snippets.v
Normal file
91
lib/virt/docker/docker_recipe_snippets.v
Normal file
@@ -0,0 +1,91 @@
|
||||
module docker
|
||||
|
||||
// import freeflowuniverse.herolib.develop.gittools
|
||||
// import freeflowuniverse.herolib.core.pathlib
|
||||
|
||||
pub fn (mut r DockerBuilderRecipe) add_zinit() ! {
|
||||
mut pkg_manager := 'apk add'
|
||||
if r.platform == .ubuntu {
|
||||
pkg_manager = 'apt install'
|
||||
}
|
||||
r.add_run(
|
||||
cmd: '
|
||||
${pkg_manager} wget
|
||||
wget https://github.com/threefoldtech/zinit/releases/download/v0.2.5/zinit -O /sbin/zinit
|
||||
chmod +x /sbin/zinit
|
||||
touch /etc/environment
|
||||
mkdir -p /etc/zinit/
|
||||
'
|
||||
)!
|
||||
|
||||
r.add_entrypoint(cmd: '/sbin/zinit init --container')!
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct ExecuteArgs {
|
||||
pub mut:
|
||||
source string // is the filename, needs to be embedded
|
||||
debug bool
|
||||
}
|
||||
|
||||
// execute the file as embedded
|
||||
pub fn (mut r DockerBuilderRecipe) execute(args ExecuteArgs) ! {
|
||||
if args.source == '' {
|
||||
return error('source cant be empty, \n ${r}')
|
||||
}
|
||||
path := args.source
|
||||
r.add_file_embedded(source: path, dest: '/tmp/${path}', make_executable: true)!
|
||||
if !args.debug {
|
||||
r.add_run(cmd: '/tmp/${path}')!
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut r DockerBuilderRecipe) add_nodejsbuilder() ! {
|
||||
r.add_package(name: 'nodejs, npm')!
|
||||
}
|
||||
|
||||
pub fn (mut r DockerBuilderRecipe) add_vbuilder() ! {
|
||||
r.add_package(name: 'git, musl-dev, clang, gcc, openssh-client, make')!
|
||||
r.add_run(
|
||||
cmd: "
|
||||
git clone --depth 1 https://github.com/vlang/v /opt/vlang
|
||||
cd /opt/vlang
|
||||
make VFLAGS='-cc gcc'
|
||||
./v -version
|
||||
./v symlink
|
||||
"
|
||||
)!
|
||||
r.add_workdir(workdir: '/opt/vlang')!
|
||||
}
|
||||
|
||||
// add ssh server and init scripts (note: zinit needs to be installed)
|
||||
pub fn (mut r DockerBuilderRecipe) add_sshserver() ! {
|
||||
r.add_package(name: 'openssh-server')!
|
||||
|
||||
r.add_zinit_cmd(
|
||||
name: 'sshd-setup'
|
||||
oneshot: true
|
||||
exec: "
|
||||
mkdir -p /run/sshd
|
||||
ssh-keygen -f /etc/ssh/ssh_host_rsa_key -N '' -t rsa
|
||||
ssh-keygen -f /etc/ssh/ssh_host_dsa_key -N '' -t dsa
|
||||
ssh-keygen -f /etc/ssh/ssh_host_ecdsa_key -N '' -t ecdsa -b 521
|
||||
ssh-keygen -f /etc/ssh/ssh_host_ed25519_key -N '' -t ed25519
|
||||
"
|
||||
)!
|
||||
|
||||
r.add_zinit_cmd(
|
||||
name: 'ssh-keys'
|
||||
after: 'sshd-setup'
|
||||
exec: '
|
||||
if [ ! -d /root/.ssh ]; then
|
||||
mkdir -m 700 /root/.ssh
|
||||
fi
|
||||
|
||||
echo \$SSH_KEY >> /root/.ssh/authorized_keys
|
||||
chmod 600 /root/.ssh/authorized_keys
|
||||
'
|
||||
)!
|
||||
|
||||
r.add_zinit_cmd(name: 'sshd', exec: '/usr/sbin/sshd -D -e', after: 'sshd-setup')!
|
||||
}
|
||||
37
lib/virt/docker/docker_recipe_volume.v
Normal file
37
lib/virt/docker/docker_recipe_volume.v
Normal file
@@ -0,0 +1,37 @@
|
||||
module docker
|
||||
|
||||
@[params]
|
||||
pub struct VolumeArgs {
|
||||
pub mut:
|
||||
mount_points []string
|
||||
}
|
||||
|
||||
pub struct VolumeItem {
|
||||
pub mut:
|
||||
mount_points []string
|
||||
recipe &DockerBuilderRecipe @[str: skip]
|
||||
}
|
||||
|
||||
// to do something like: 'Volume /data'
|
||||
pub fn (mut b DockerBuilderRecipe) add_volume(args VolumeArgs) ! {
|
||||
mut item := VolumeItem{
|
||||
mount_points: args.mount_points
|
||||
recipe: &b
|
||||
}
|
||||
b.items << item
|
||||
}
|
||||
|
||||
pub fn (mut i VolumeItem) check() ! {
|
||||
if i.mount_points.len == 0 {
|
||||
return error('mount points list cannot be empty')
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut i VolumeItem) render() !string {
|
||||
mut out := 'VOLUME'
|
||||
for s in i.mount_points {
|
||||
out += ' ${s}'
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
30
lib/virt/docker/docker_recipe_workdir.v
Normal file
30
lib/virt/docker/docker_recipe_workdir.v
Normal file
@@ -0,0 +1,30 @@
|
||||
module docker
|
||||
|
||||
@[params]
|
||||
pub struct WorkDirArgs {
|
||||
pub mut:
|
||||
workdir string
|
||||
}
|
||||
|
||||
pub struct WorkDirItem {
|
||||
pub mut:
|
||||
workdir string
|
||||
recipe &DockerBuilderRecipe @[str: skip]
|
||||
}
|
||||
|
||||
// to do something like: 'FROM alpine:latest'
|
||||
pub fn (mut b DockerBuilderRecipe) add_workdir(args WorkDirArgs) ! {
|
||||
mut item := WorkDirItem{
|
||||
recipe: &b
|
||||
workdir: args.workdir
|
||||
}
|
||||
b.items << item
|
||||
}
|
||||
|
||||
pub fn (mut i WorkDirItem) check() ! {
|
||||
// nothing much we can do here I guess
|
||||
}
|
||||
|
||||
pub fn (mut i WorkDirItem) render() !string {
|
||||
return 'WORKDIR ${i.workdir}'
|
||||
}
|
||||
35
lib/virt/docker/docker_recipe_writefile.v
Normal file
35
lib/virt/docker/docker_recipe_writefile.v
Normal file
@@ -0,0 +1,35 @@
|
||||
module docker
|
||||
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
|
||||
@[params]
|
||||
pub struct WriteFileArgs {
|
||||
pub mut:
|
||||
name string // such a file needs to have a name
|
||||
content string
|
||||
dest string // where the content will be saved
|
||||
make_executable bool // if true then will be made executable
|
||||
}
|
||||
|
||||
// the content will be written to dest
|
||||
// all trailing spaces will be removed (dedent)
|
||||
pub fn (mut r DockerBuilderRecipe) write_file(args WriteFileArgs) ! {
|
||||
if args.name == '' {
|
||||
return error('name cant be empty, \n ${r}')
|
||||
}
|
||||
if args.content == '' {
|
||||
return error('content cant be empty, \n ${r}')
|
||||
}
|
||||
if args.dest == '' {
|
||||
return error('dest cant be empty, \n ${r}')
|
||||
}
|
||||
mut ff := pathlib.get_file(path: r.path() + '/snippets/${args.name}', create: true)!
|
||||
content := texttools.dedent(args.content)
|
||||
ff.write(content)!
|
||||
r.add_copy(
|
||||
source: 'snippets/${args.name}'
|
||||
dest: args.dest
|
||||
make_executable: args.make_executable
|
||||
)!
|
||||
}
|
||||
130
lib/virt/docker/docker_recipe_zinit.v
Normal file
130
lib/virt/docker/docker_recipe_zinit.v
Normal file
@@ -0,0 +1,130 @@
|
||||
module docker
|
||||
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
pub struct ZinitItem {
|
||||
pub mut:
|
||||
name string
|
||||
exec string // e.g. redis-server --port 7777
|
||||
test string // e.g. redis-cli -p 7777 PING
|
||||
after []string // e.g. copy,tmuxp these are the names of the ones we are depending on
|
||||
log ZinitLogType
|
||||
signal string // could be 'SIGKILL' as well, if not specified then 'SIGTERM'
|
||||
oneshot bool // is the command supposed to stop and only be used one, so not as daemon
|
||||
}
|
||||
|
||||
enum ZinitLogType {
|
||||
ring
|
||||
stdout
|
||||
null
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct ZinitAddArgs {
|
||||
pub mut:
|
||||
// more info see https://github.com/threefoldtech/zinit/tree/master/docs
|
||||
name string
|
||||
exec string // e.g. redis-server --port 7777
|
||||
test string // e.g. redis-cli -p 7777 PING
|
||||
after string // e.g. 'copy,tmuxp' these are the names of the ones we are depending on, is comma separated
|
||||
log ZinitLogType
|
||||
signal string // could be 'SIGKILL' as well, if not specified then 'SIGTERM'
|
||||
oneshot bool // is the command supposed to stop and only be used one, so not as daemon
|
||||
}
|
||||
|
||||
// add a zinit to the docker container
|
||||
// each init will launch a process in the container
|
||||
// the init files are rendered as yaml files in the build directory and added to the docker at the end
|
||||
// how to use zinit see: https://github.com/threefoldtech/zinit/tree/master/docs
|
||||
pub fn (mut b DockerBuilderRecipe) add_zinit_cmd(args ZinitAddArgs) ! {
|
||||
if args.name.len < 3 {
|
||||
return error('min length of name is 3')
|
||||
}
|
||||
if args.exec.len < 3 {
|
||||
return error('min length of val is 3')
|
||||
}
|
||||
// TODO: make sure after is lowercase and trimmed
|
||||
mut item := ZinitItem{
|
||||
name: args.name
|
||||
exec: args.exec
|
||||
test: args.test
|
||||
log: args.log
|
||||
signal: args.signal
|
||||
oneshot: args.oneshot
|
||||
}
|
||||
|
||||
mut afterargs := []string{}
|
||||
|
||||
if args.after.len > 0 {
|
||||
for afteritem in args.after.split(',') {
|
||||
afterargs << afteritem.to_lower().trim_space()
|
||||
}
|
||||
item.after = afterargs
|
||||
}
|
||||
|
||||
b.items << item
|
||||
|
||||
mut zinitfilecontent := ''
|
||||
if args.exec.contains('\n') {
|
||||
// we now suppose this file is a bash file
|
||||
mut content := texttools.dedent(args.exec)
|
||||
if !content.trim_space().starts_with('set ') {
|
||||
content = 'set -ex\n\n${content}'
|
||||
}
|
||||
b.write_file(
|
||||
dest: '/cmds/${args.name}.sh'
|
||||
content: content
|
||||
make_executable: true
|
||||
name: args.name + '.sh'
|
||||
)!
|
||||
zinitfilecontent += 'exec: /bin/bash /cmds/${args.name}.sh\n'
|
||||
} else {
|
||||
zinitfilecontent += 'exec: ${args.exec}\n'
|
||||
}
|
||||
|
||||
if args.test.len > 0 {
|
||||
zinitfilecontent += 'test: ${args.test}\n'
|
||||
}
|
||||
if args.after.len > 0 {
|
||||
zinitfilecontent += 'after:\n'
|
||||
for a in afterargs {
|
||||
zinitfilecontent += ' - ${a}\n'
|
||||
}
|
||||
}
|
||||
if args.signal.len > 0 {
|
||||
zinitfilecontent += 'signal:\n'
|
||||
zinitfilecontent += ' stop: ${args.signal.to_upper()} \n'
|
||||
}
|
||||
if args.log == .stdout {
|
||||
zinitfilecontent += 'log: stdout\n'
|
||||
} else if args.log == .ring {
|
||||
zinitfilecontent += 'log: ring\n'
|
||||
} else if args.log == .null {
|
||||
zinitfilecontent += 'log: null\n'
|
||||
} else {
|
||||
panic('other log')
|
||||
}
|
||||
if args.oneshot {
|
||||
zinitfilecontent += 'oneshot: true\n'
|
||||
}
|
||||
mut ff := pathlib.get_file(path: b.path() + '/zinit/${args.name}.yaml', create: true)!
|
||||
ff.write(zinitfilecontent)!
|
||||
}
|
||||
|
||||
pub fn (mut i ZinitItem) check() ! {
|
||||
// nothing much we can do here I guess
|
||||
}
|
||||
|
||||
pub fn (mut i ZinitItem) render() !string {
|
||||
// nothing to render only in final step
|
||||
return ''
|
||||
}
|
||||
|
||||
// example file format
|
||||
|
||||
// exec: redis-server --port 7777
|
||||
// test: redis-cli -p 7777 PING
|
||||
// after:
|
||||
// - copy
|
||||
// - tmuxp
|
||||
113
lib/virt/docker/docker_registry.v
Normal file
113
lib/virt/docker/docker_registry.v
Normal file
@@ -0,0 +1,113 @@
|
||||
module docker
|
||||
|
||||
import freeflowuniverse.herolib.crypt.openssl
|
||||
import freeflowuniverse.herolib.clients.httpconnection
|
||||
import freeflowuniverse.herolib.osal { exec }
|
||||
import os
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
@[heap]
|
||||
pub struct DockerRegistry {
|
||||
pub mut:
|
||||
name string = 'default'
|
||||
datapath string
|
||||
ssl bool
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct DockerRegistryArgs {
|
||||
pub mut:
|
||||
name string = 'default' @[required]
|
||||
datapath string
|
||||
ssl bool
|
||||
reset bool // if reset will reset existing running one
|
||||
reset_ssl bool // if reset will reset the sslkey
|
||||
secret string = '1234' @[required]
|
||||
}
|
||||
|
||||
// registry:
|
||||
// restart: always
|
||||
// image: registry:2
|
||||
// ports:
|
||||
// - 5000:5000
|
||||
// environment:
|
||||
// REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt
|
||||
// REGISTRY_HTTP_TLS_KEY: /certs/domain.key
|
||||
// REGISTRY_AUTH: htpasswd
|
||||
// REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
|
||||
// REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
|
||||
// volumes:
|
||||
// - ${registry.datapath}/data:/var/lib/registry
|
||||
// - ${registry.datapath}/certs:/certs
|
||||
// - ${registry.datapath}/auth:/auth
|
||||
|
||||
// check docker has been installed & enabled on node
|
||||
pub fn (mut e DockerEngine) registry_add(args DockerRegistryArgs) ! {
|
||||
mut registry := DockerRegistry{
|
||||
name: args.name
|
||||
datapath: args.datapath
|
||||
ssl: args.ssl
|
||||
}
|
||||
|
||||
if registry.datapath.len < 4 {
|
||||
return error('datapath needs to be len +3')
|
||||
}
|
||||
|
||||
mut composer := e.compose_new(name: 'docker_registry')
|
||||
mut service := composer.service_new(name: 'registry', image: 'registry:2')!
|
||||
service.restart_set()
|
||||
service.port_expose(5000, 5000)!
|
||||
|
||||
if registry.ssl {
|
||||
service.env_add('REGISTRY_HTTP_TLS_CERTIFICATE', '/certs/domain.crt')
|
||||
service.env_add('REGISTRY_HTTP_TLS_KEY', ' /certs/domain.key')
|
||||
service.env_add('REGISTRY_AUTH', 'htpasswd')
|
||||
service.env_add('REGISTRY_AUTH_HTPASSWD_PATH', '/auth/htpasswd')
|
||||
service.env_add('REGISTRY_AUTH_HTPASSWD_REALM', 'Registry Realm')
|
||||
service.env_add('REGISTRY_LOGLEVEL', 'debug')
|
||||
service.env_add('REGISTRY_HTTP_SECRET', args.secret)
|
||||
service.volume_add('${registry.datapath}/data', '/var/lib/registry')!
|
||||
service.volume_add('${registry.datapath}/certs', '/certs')!
|
||||
service.volume_add('${registry.datapath}/auth', '/auth')!
|
||||
|
||||
p1 := '${registry.datapath}/certs/domain.crt'
|
||||
p2 := '${registry.datapath}/certs/domain.key'
|
||||
if !os.exists(p1) || !os.exists(p2) || args.reset_ssl {
|
||||
// means we are missing a key
|
||||
mut ossl := openssl.new()!
|
||||
k := ossl.get(name: 'docker_registry')!
|
||||
os.mkdir_all('${registry.datapath}/certs')!
|
||||
os.cp(k.path_cert.path, p1)!
|
||||
os.cp(k.path_key.path, p2)!
|
||||
}
|
||||
}
|
||||
e.registries << registry
|
||||
|
||||
// delete all previous containers, uses wildcards see https://modules.vlang.io/index.html#string.match_glob
|
||||
e.container_delete(name: 'docker_registry*')!
|
||||
|
||||
composer.start()!
|
||||
|
||||
exec(cmd: 'curl https://localhost:5000/v2/ -k', retry: 4) or {
|
||||
return error('could not start docker registry, did not answer')
|
||||
}
|
||||
|
||||
mut conn := httpconnection.new(
|
||||
name: 'localdockerhub'
|
||||
url: 'https://localhost:5000/v2/'
|
||||
retry: 10
|
||||
)!
|
||||
|
||||
// r := conn.get_json_dict(mut prefix: 'errors')!
|
||||
|
||||
// r := conn.get_json_dict(mut prefix: 'errors')!
|
||||
r := conn.get(method: .get)!
|
||||
console.print_debug('Sdsd')
|
||||
console.print_debug(r)
|
||||
|
||||
if true {
|
||||
panic('sdsd')
|
||||
}
|
||||
|
||||
// now we need to check if we can connect
|
||||
}
|
||||
3
lib/virt/docker/docker_restapi.v
Normal file
3
lib/virt/docker/docker_restapi.v
Normal file
@@ -0,0 +1,3 @@
|
||||
module docker
|
||||
|
||||
// https://docs.docker.com/engine/api/v1.41/#
|
||||
69
lib/virt/docker/docker_test.v
Normal file
69
lib/virt/docker/docker_test.v
Normal file
@@ -0,0 +1,69 @@
|
||||
module docker
|
||||
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
fn test_docker1() {
|
||||
// mut engine := engine_local([]) or { panic(err) }
|
||||
|
||||
// engine.reset_all()!
|
||||
|
||||
// create an ssh enabled alpine container
|
||||
// push to threefold docker hub
|
||||
// have a default sshkey in, which is known to our docker classes here
|
||||
// the following NEW method gets this container with the default SSH key understood in node
|
||||
// mut container := engine.container_get('test_container') or {
|
||||
// panic('Cannot get test container')
|
||||
// }
|
||||
// container.start()!
|
||||
|
||||
// mut res := node.exec('ls /')!
|
||||
// do some assert test
|
||||
|
||||
// check assert that there is 1 container in engine.containers_list()
|
||||
|
||||
// console.print_debug(container)
|
||||
|
||||
// NOW DO SOME MORE TESTS,
|
||||
|
||||
// engine.node = builder.node_new(name: 'test')
|
||||
// console.print_debug(engine.images_list() or { []&DockerImage{} })
|
||||
// panic('A')
|
||||
// mut containers := engine.containers_list()
|
||||
// mut container := containers[0]
|
||||
// console.print_debug(container)
|
||||
// container.start()
|
||||
// mut engine2 := DockerEngine<ExecutorLocal>{}
|
||||
// engine2.executor.name = "aaa"
|
||||
// console.print_debug(engine2.images_list())
|
||||
// mut engine := get(Executor(ExecutorSSH{}))
|
||||
// console.print_debug(engine)
|
||||
// console.print_debug(engine.images_list())
|
||||
// mut engine2 := get(ExecutorLocal{})
|
||||
// console.print_debug(engine2.images_list())
|
||||
}
|
||||
|
||||
// fn test_remote_docker() {
|
||||
// node := builder.Node{
|
||||
// name: "remote digitalocean",
|
||||
// platform: builder.PlatformType.ubuntu,
|
||||
// executor: builder.ExecutorSSH{
|
||||
// sshkey: "~/.ssh/id_rsa_test",
|
||||
// user: "root",
|
||||
// ipaddr: builder.IPAddress{
|
||||
// addr: "104.236.53.191",
|
||||
// port: builder.Port{
|
||||
// number: 22,
|
||||
// cat: builder.PortType.tcp
|
||||
// },
|
||||
// cat: builder.IpAddressType.ipv4
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// engine.node = node
|
||||
// // console.print_debug(engine.images_list())
|
||||
// mut containers := engine.containers_list()
|
||||
// console.print_debug(containers)
|
||||
// mut container := containers[0]
|
||||
// console.print_debug(container)
|
||||
// container.start() or {panic(err)}
|
||||
// }
|
||||
224
lib/virt/docker/readme.md
Normal file
224
lib/virt/docker/readme.md
Normal file
@@ -0,0 +1,224 @@
|
||||
|
||||
|
||||
# Docker Module Documentation
|
||||
|
||||
The Docker module provides a comprehensive set of tools for working with Docker containers, images, and compose files in V. It offers high-level abstractions for common Docker operations while maintaining flexibility for advanced use cases.
|
||||
|
||||
## Core Components
|
||||
|
||||
### DockerEngine
|
||||
|
||||
The main entry point for Docker operations. It manages:
|
||||
- Docker images and containers
|
||||
- Build operations
|
||||
- Registry interactions
|
||||
- Platform-specific settings
|
||||
|
||||
```v
|
||||
// Create a new Docker engine
|
||||
mut engine := docker.new(
|
||||
name: 'myengine'
|
||||
localonly: true
|
||||
)!
|
||||
```
|
||||
|
||||
### DockerContainer
|
||||
|
||||
Represents a Docker container with operations for:
|
||||
- Starting/stopping containers
|
||||
- Shell access
|
||||
- Port forwarding
|
||||
- Volume mounting
|
||||
- Container state management
|
||||
|
||||
```v
|
||||
// Create and start a container
|
||||
mut container := engine.container_create(
|
||||
name: 'mycontainer'
|
||||
image_repo: 'ubuntu'
|
||||
image_tag: 'latest'
|
||||
)!
|
||||
container.start()!
|
||||
```
|
||||
|
||||
### DockerImage
|
||||
|
||||
Handles Docker image operations:
|
||||
- Image loading/saving
|
||||
- Deletion
|
||||
- Export/import
|
||||
- Image metadata
|
||||
|
||||
```v
|
||||
// Get an image by name
|
||||
mut image := engine.image_get(
|
||||
repo: 'ubuntu'
|
||||
tag: 'latest'
|
||||
)!
|
||||
```
|
||||
|
||||
### DockerCompose
|
||||
|
||||
Manages Docker Compose operations:
|
||||
- Service definitions
|
||||
- Multi-container deployments
|
||||
- Network configuration
|
||||
- Volume management
|
||||
|
||||
```v
|
||||
// Create a new compose configuration
|
||||
mut composer := engine.compose_new(
|
||||
name: 'myapp'
|
||||
)!
|
||||
|
||||
// Add a service
|
||||
mut service := composer.service_new(C
|
||||
name: 'web'
|
||||
image: 'nginx:latest'
|
||||
)!
|
||||
|
||||
// Configure service
|
||||
service.port_expose(80, 8080)!
|
||||
service.env_add('NGINX_HOST', 'localhost')
|
||||
service.restart_set()
|
||||
|
||||
// Start the compose stack
|
||||
composer.start()!
|
||||
```
|
||||
|
||||
### DockerRegistry
|
||||
|
||||
Provides functionality for:
|
||||
- Registry setup and management
|
||||
- Image pushing/pulling
|
||||
- Authentication
|
||||
- SSL configuration
|
||||
|
||||
## Recipe System
|
||||
|
||||
The module includes a powerful recipe system for building Docker images:
|
||||
|
||||
### DockerBuilderRecipe
|
||||
|
||||
Allows declarative definition of Docker images with:
|
||||
- Multi-stage builds
|
||||
- Package installation
|
||||
- File operations
|
||||
- Environment configuration
|
||||
- Service initialization
|
||||
|
||||
```v
|
||||
// Create a new recipe
|
||||
mut recipe := engine.recipe_new(RecipeArgs{
|
||||
name: 'myapp'
|
||||
platform: .ubuntu
|
||||
})!
|
||||
|
||||
// Add build steps
|
||||
recipe.add_package(name: 'nginx,curl')!
|
||||
recipe.add_copy(source: 'app', dest: '/app')!
|
||||
recipe.add_env('APP_ENV', 'production')!
|
||||
recipe.add_expose(ports: ['80/tcp'])!
|
||||
|
||||
// Build the image
|
||||
recipe.build(false)!
|
||||
```
|
||||
|
||||
### Recipe Components
|
||||
|
||||
The recipe system supports various Dockerfile instructions through specialized components:
|
||||
|
||||
- **FromItem**: Base image specification
|
||||
- **RunItem**: Command execution
|
||||
- **CmdItem**: Container entry point
|
||||
- **EnvItem**: Environment variables
|
||||
- **WorkDirItem**: Working directory
|
||||
- **CopyItem**: File copying
|
||||
- **ExposeItem**: Port exposure
|
||||
- **VolumeItem**: Volume mounting
|
||||
- **ZinitItem**: Process management
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Container Management
|
||||
```v
|
||||
// Create and manage a container
|
||||
mut engine := docker.new(DockerEngineArgs{name: 'default'})!
|
||||
mut container := engine.container_create(
|
||||
name: 'webserver'
|
||||
image_repo: 'nginx'
|
||||
forwarded_ports: ['80:8080/tcp']
|
||||
)!
|
||||
container.start()!
|
||||
```
|
||||
|
||||
### Custom Image Build
|
||||
```v
|
||||
mut recipe := engine.recipe_new(
|
||||
name: 'custom-nginx'
|
||||
platform: .ubuntu
|
||||
)!
|
||||
|
||||
recipe.add_package(name: 'nginx')!
|
||||
recipe.add_file_embedded(
|
||||
source: 'nginx.conf'
|
||||
dest: '/etc/nginx/nginx.conf'
|
||||
)!
|
||||
recipe.add_expose(ports: ['80/tcp'])!
|
||||
|
||||
recipe.build(false)!
|
||||
```
|
||||
|
||||
### Multi-Container Setup
|
||||
```v
|
||||
mut composer := engine.compose_new(name: 'webapp')!
|
||||
|
||||
// Web service
|
||||
mut web := composer.service_new
|
||||
name: 'web'
|
||||
image: 'nginx'
|
||||
)!
|
||||
web.port_expose(80, 8080)!
|
||||
|
||||
// Database service
|
||||
mut db := composer.service_new(
|
||||
name: 'db'
|
||||
image: 'postgres'
|
||||
)!
|
||||
db.env_add('POSTGRES_PASSWORD', 'secret')
|
||||
|
||||
composer.start()!
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The module provides specific error types for common scenarios:
|
||||
|
||||
- **ImageGetError**: Image retrieval issues
|
||||
- **ContainerGetError**: Container access problems
|
||||
|
||||
Always handle these errors appropriately in your code:
|
||||
|
||||
```v
|
||||
engine.image_get(
|
||||
repo: 'nonexistent'
|
||||
) or {
|
||||
if err is ImageGetError {
|
||||
if err.notfound {
|
||||
// Handle missing image
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
## Builders
|
||||
|
||||
see https://github.com/threefoldtech/vbuilders/tree/development/builders for examples
|
||||
|
||||
How to get started see
|
||||
|
||||
https://github.com/threefoldtech/vbuilders/blob/development/docsrc/src/gettingstarted/ubuntu/ubuntu.md
|
||||
|
||||
|
||||
242
lib/virt/herocontainers/builder.v
Normal file
242
lib/virt/herocontainers/builder.v
Normal file
@@ -0,0 +1,242 @@
|
||||
module herocontainers
|
||||
|
||||
import freeflowuniverse.herolib.osal
|
||||
// import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.installers.lang.herolib
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import os
|
||||
import json
|
||||
|
||||
// is builderah containers
|
||||
|
||||
// pub enum ContainerStatus {
|
||||
// up
|
||||
// down
|
||||
// restarting
|
||||
// paused
|
||||
// dead
|
||||
// created
|
||||
// }
|
||||
|
||||
// need to fill in what is relevant
|
||||
@[heap]
|
||||
pub struct Builder {
|
||||
pub mut:
|
||||
id string
|
||||
builder bool
|
||||
imageid string
|
||||
imagename string
|
||||
containername string
|
||||
engine &CEngine @[skip; str: skip]
|
||||
// hero_in_container bool //once the hero has been installed this is on, does it once per session
|
||||
// created time.Time
|
||||
// ssh_enabled bool // if yes make sure ssh is enabled to the container
|
||||
// ipaddr IPAddress
|
||||
// forwarded_ports []string
|
||||
// mounts []ContainerVolume
|
||||
// ssh_port int // ssh port on node that is used to get ssh
|
||||
// ports []string
|
||||
// networks []string
|
||||
// labels map[string]string @[str: skip]
|
||||
// image &Image @[str: skip]
|
||||
// engine &CEngine @[str: skip]
|
||||
// status ContainerStatus
|
||||
// memsize int // in MB
|
||||
// command string
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct RunArgs {
|
||||
pub mut:
|
||||
cmd string
|
||||
// TODO:/..
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct PackageInstallArgs {
|
||||
pub mut:
|
||||
names string
|
||||
// TODO:/..
|
||||
}
|
||||
|
||||
// TODO: mimic osal.package_install('mc,tmux,git,rsync,curl,screen,redis,wget,git-lfs')!
|
||||
|
||||
// pub fn (mut self Builder) package_install(args PackageInstallArgs) !{
|
||||
// //TODO
|
||||
// names := texttools.to_array(args.names)
|
||||
// //now check which OS, need to make platform function on container level so we know which platform it is
|
||||
// panic("implement")
|
||||
// }
|
||||
|
||||
@[params]
|
||||
pub struct Command {
|
||||
pub mut:
|
||||
name string // to give a name to your command, good to see logs...
|
||||
cmd string
|
||||
description string
|
||||
timeout int = 3600 // timeout in sec
|
||||
stdout bool = true
|
||||
stdout_log bool = true
|
||||
raise_error bool = true // if false, will not raise an error but still error report
|
||||
ignore_error bool // means if error will just exit and not raise, there will be no error reporting
|
||||
work_folder string // location where cmd will be executed
|
||||
environment map[string]string // env variables
|
||||
ignore_error_codes []int
|
||||
scriptpath string // is the path where the script will be put which is executed
|
||||
scriptkeep bool // means we don't remove the script
|
||||
debug bool // if debug will put +ex in the script which is being executed and will make sure script stays
|
||||
shell bool // means we will execute it in a shell interactive
|
||||
retry int
|
||||
interactive bool = true
|
||||
async bool
|
||||
runtime osal.RunTime
|
||||
}
|
||||
|
||||
pub enum RunTime {
|
||||
bash
|
||||
python
|
||||
heroscript
|
||||
herocmd
|
||||
v
|
||||
}
|
||||
|
||||
pub fn (mut self Builder) run(cmd Command) ! {
|
||||
mut rt := RunTime.bash
|
||||
|
||||
scriptpath := osal.cmd_to_script_path(cmd: cmd.cmd, runtime: cmd.runtime)!
|
||||
|
||||
if cmd.runtime == .heroscript || cmd.runtime == .herocmd {
|
||||
self.hero_copy()!
|
||||
}
|
||||
|
||||
script_basename := os.base(scriptpath)
|
||||
script_path_in_container := '/tmp/${script_basename}'
|
||||
|
||||
self.copy(scriptpath, script_path_in_container)!
|
||||
// console.print_debug("copy ${scriptpath} into container '${self.containername}'")
|
||||
cmd_str := 'buildah run ${self.id} ${script_path_in_container}'
|
||||
// console.print_debug(cmd_str)
|
||||
|
||||
if cmd.runtime == .heroscript || cmd.runtime == .herocmd {
|
||||
self.hero_copy()!
|
||||
}
|
||||
osal.exec(
|
||||
name: cmd.name
|
||||
cmd: cmd_str
|
||||
description: cmd.description
|
||||
timeout: cmd.timeout
|
||||
stdout: cmd.stdout
|
||||
stdout_log: cmd.stdout_log
|
||||
raise_error: cmd.raise_error
|
||||
ignore_error: cmd.ignore_error
|
||||
ignore_error_codes: cmd.ignore_error_codes
|
||||
scriptpath: cmd.scriptpath
|
||||
scriptkeep: cmd.scriptkeep
|
||||
debug: cmd.debug
|
||||
shell: cmd.shell
|
||||
retry: cmd.retry
|
||||
interactive: cmd.interactive
|
||||
async: cmd.async
|
||||
) or {
|
||||
mut epath := pathlib.get_file(path: scriptpath, create: false)!
|
||||
c := epath.read()!
|
||||
return error('cannot execute:\n${c}\nerror:\n${err}')
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut self Builder) copy(src string, dest string) ! {
|
||||
cmd := 'buildah copy ${self.id} ${src} ${dest}'
|
||||
osal.exec(cmd: cmd, stdout: false)!
|
||||
}
|
||||
|
||||
// copies the hero from host into guest
|
||||
pub fn (mut self Builder) hero_copy() ! {
|
||||
if !osal.cmd_exists('hero') {
|
||||
herolib.hero_compile()!
|
||||
}
|
||||
heropath := osal.cmd_path('hero')!
|
||||
self.copy(heropath, '/usr/local/bin/hero')!
|
||||
}
|
||||
|
||||
// copies the hero from host into guest and then execute the heroscript or commandline
|
||||
pub fn (mut self Builder) hero_execute_cmd(cmd string) ! {
|
||||
self.hero_copy()!
|
||||
self.run(cmd: cmd, runtime: .herocmd)!
|
||||
}
|
||||
|
||||
pub fn (mut self Builder) hero_execute_script(cmd string) ! {
|
||||
self.hero_copy()!
|
||||
self.run(cmd: cmd, runtime: .heroscript)!
|
||||
}
|
||||
|
||||
pub fn (mut self Builder) shell() ! {
|
||||
cmd := 'buildah run --terminal --env TERM=xterm ${self.id} /bin/bash'
|
||||
osal.execute_interactive(cmd)!
|
||||
}
|
||||
|
||||
pub fn (mut self Builder) clean() ! {
|
||||
cmd := '
|
||||
#set -x
|
||||
set +e
|
||||
rm -rf /root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/share/doc
|
||||
pacman -Rns $(pacman -Qtdq) --noconfirm
|
||||
pacman -Scc --noconfirm
|
||||
rm -rf /var/lib/pacman/sync/*
|
||||
rm -rf /tmp/*
|
||||
rm -rf /var/tmp/*
|
||||
find /var/log -type f -name "*.log" -exec truncate -s 0 {} \\;
|
||||
rm -rf /home/*/.cache/*
|
||||
rm -rf /usr/share/doc/*
|
||||
rm -rf /usr/share/man/*
|
||||
rm -rf /usr/share/info/*
|
||||
rm -rf /usr/share/licenses/*
|
||||
find /usr/share/locale -mindepth 1 -maxdepth 1 ! -name "en*" -exec rm -rf {} \\;
|
||||
rm -rf /usr/share/i18n
|
||||
rm -rf /usr/share/icons/*
|
||||
rm -rf /usr/lib/modules/*
|
||||
rm -rf /var/cache/pacman
|
||||
journalctl --vacuum-time=1s
|
||||
|
||||
'
|
||||
self.run(cmd: cmd, stdout: false)!
|
||||
}
|
||||
|
||||
pub fn (mut self Builder) delete() ! {
|
||||
self.engine.builder_delete(self.containername)!
|
||||
}
|
||||
|
||||
pub fn (mut self Builder) inspect() !BuilderInfo {
|
||||
cmd := 'buildah inspect ${self.containername}'
|
||||
out := osal.execute_silent(cmd)!
|
||||
mut r := json.decode(BuilderInfo, out) or {
|
||||
return error('Failed to decode JSON for inspect: ${err}')
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// mount the build container to a path and return
|
||||
pub fn (mut self Builder) mount_to_path() !string {
|
||||
cmd := 'buildah mount ${self.containername}'
|
||||
out := osal.execute_silent(cmd)!
|
||||
return out.trim_space()
|
||||
}
|
||||
|
||||
pub fn (mut self Builder) commit(image_name string) ! {
|
||||
cmd := 'buildah commit ${self.containername} ${image_name}'
|
||||
osal.exec(cmd: cmd)!
|
||||
}
|
||||
|
||||
pub fn (self Builder) set_entrypoint(entrypoint string) ! {
|
||||
cmd := 'buildah config --entrypoint ${entrypoint} ${self.containername}'
|
||||
osal.exec(cmd: cmd)!
|
||||
}
|
||||
|
||||
pub fn (self Builder) set_workingdir(workdir string) ! {
|
||||
cmd := 'buildah config --workingdir ${workdir} ${self.containername}'
|
||||
osal.exec(cmd: cmd)!
|
||||
}
|
||||
|
||||
pub fn (self Builder) set_cmd(command string) ! {
|
||||
cmd := 'buildah config --cmd ${command} ${self.containername}'
|
||||
osal.exec(cmd: cmd)!
|
||||
}
|
||||
113
lib/virt/herocontainers/builder_info.v
Normal file
113
lib/virt/herocontainers/builder_info.v
Normal file
@@ -0,0 +1,113 @@
|
||||
module herocontainers
|
||||
|
||||
struct BuilderInfo {
|
||||
type_ string @[json: 'Type']
|
||||
from_image string @[json: 'FromImage']
|
||||
from_image_id string @[json: 'FromImageID']
|
||||
from_image_digest string @[json: 'FromImageDigest']
|
||||
group_add []string @[json: 'GroupAdd']
|
||||
config string @[json: 'Config']
|
||||
manifest string @[json: 'Manifest']
|
||||
container string @[json: 'Container']
|
||||
container_id string @[json: 'ContainerID']
|
||||
mount_point string @[json: 'MountPoint']
|
||||
process_label string @[json: 'ProcessLabel']
|
||||
mount_label string @[json: 'MountLabel']
|
||||
image_annotations ImageAnnotations @[json: 'ImageAnnotations']
|
||||
image_created_by string @[json: 'ImageCreatedBy']
|
||||
oci_v1 OCIv1 @[json: 'OCIv1']
|
||||
docker Docker @[json: 'Docker']
|
||||
default_mounts_file_path string @[json: 'DefaultMountsFilePath']
|
||||
isolation string @[json: 'Isolation']
|
||||
namespace_options []NamespaceOption @[json: 'NamespaceOptions']
|
||||
capabilities []string @[json: 'Capabilities']
|
||||
configure_network string @[json: 'ConfigureNetwork']
|
||||
// cni_plugin_path string @[json: 'CNIPluginPath']
|
||||
// cni_config_dir string @[json: 'CNIConfigDir']
|
||||
// id_mapping_options IDMappingOptions @[json: 'IDMappingOptions']
|
||||
history []string @[json: 'History']
|
||||
devices []string @[json: 'Devices']
|
||||
}
|
||||
|
||||
struct ImageAnnotations {
|
||||
org_opencontainers_image_base_digest string @[json: 'org.opencontainers.image.base.digest']
|
||||
org_opencontainers_image_base_name string @[json: 'org.opencontainers.image.base.name']
|
||||
}
|
||||
|
||||
struct OCIv1 {
|
||||
created string @[json: 'created']
|
||||
architecture string @[json: 'architecture']
|
||||
os string @[json: 'os']
|
||||
config map[string]string @[json: 'config']
|
||||
rootfs Rootfs @[json: 'rootfs']
|
||||
}
|
||||
|
||||
struct Rootfs {
|
||||
type_ string @[json: 'type']
|
||||
diff_ids []string @[json: 'diff_ids']
|
||||
}
|
||||
|
||||
struct Docker {
|
||||
created string @[json: 'created']
|
||||
container_config ContainerConfig @[json: 'container_config']
|
||||
config DockerConfig @[json: 'config']
|
||||
architecture string @[json: 'architecture']
|
||||
os string @[json: 'os']
|
||||
}
|
||||
|
||||
struct ContainerConfig {
|
||||
hostname string @[json: 'Hostname']
|
||||
domainname string @[json: 'Domainname']
|
||||
user string @[json: 'User']
|
||||
attach_stdin bool @[json: 'AttachStdin']
|
||||
attach_stdout bool @[json: 'AttachStdout']
|
||||
attach_stderr bool @[json: 'AttachStderr']
|
||||
tty bool @[json: 'Tty']
|
||||
open_stdin bool @[json: 'OpenStdin']
|
||||
stdin_once bool @[json: 'StdinOnce']
|
||||
env []string @[json: 'Env']
|
||||
cmd []string @[json: 'Cmd']
|
||||
image string @[json: 'Image']
|
||||
volumes map[string]string @[json: 'Volumes']
|
||||
working_dir string @[json: 'WorkingDir']
|
||||
entrypoint []string @[json: 'Entrypoint']
|
||||
on_build []string @[json: 'OnBuild']
|
||||
labels map[string]string @[json: 'Labels']
|
||||
}
|
||||
|
||||
struct DockerConfig {
|
||||
// Assuming identical structure to ContainerConfig
|
||||
// Define fields with @json: mapping if different
|
||||
}
|
||||
|
||||
struct NamespaceOption {
|
||||
name string @[json: 'Name']
|
||||
host bool @[json: 'Host']
|
||||
path string @[json: 'Path']
|
||||
}
|
||||
|
||||
// struct IDMappingOptions {
|
||||
// host_uid_mapping bool @[json: 'HostUIDMapping']
|
||||
// host_gid_mapping bool @[json: 'HostGIDMapping']
|
||||
// // uid_map []UIDMap @[json: 'UIDMap']
|
||||
// // gid_map []GIDMap @[json: 'GIDMap']
|
||||
// auto_user_ns bool @[json: 'AutoUserNs']
|
||||
// auto_user_ns_opts AutoUserNsOpts @[json: 'AutoUserNsOpts']
|
||||
// }
|
||||
|
||||
// struct UIDMap {
|
||||
// // Define the structure with @json: mappings
|
||||
// }
|
||||
|
||||
// struct GIDMap {
|
||||
// // Define the structure with @json: mappings
|
||||
// }
|
||||
|
||||
// struct AutoUserNsOpts {
|
||||
// size int @[json: 'Size']
|
||||
// initial_size int @[json: 'InitialSize']
|
||||
// passwd_file string @[json: 'PasswdFile']
|
||||
// group_file string @[json: 'GroupFile']
|
||||
// additional_uid_mappings []UIDMap @[json: 'AdditionalUIDMappings']
|
||||
// additional_gid_mappings []GIDMap @[json: 'AdditionalGIDMappings']
|
||||
// }
|
||||
81
lib/virt/herocontainers/cengine.v
Normal file
81
lib/virt/herocontainers/cengine.v
Normal file
@@ -0,0 +1,81 @@
|
||||
module herocontainers
|
||||
|
||||
import freeflowuniverse.herolib.osal { exec }
|
||||
|
||||
@[heap]
|
||||
pub struct CEngine {
|
||||
pub mut:
|
||||
sshkeys_allowed []string // all keys here have access over ssh into the machine, when ssh enabled
|
||||
images []Image
|
||||
containers []Container
|
||||
builders []Builder
|
||||
buildpath string
|
||||
localonly bool
|
||||
cache bool = true
|
||||
push bool
|
||||
// platform []BuildPlatformType // used to build
|
||||
// registries []BAHRegistry // one or more supported BAHRegistries
|
||||
prefix string
|
||||
}
|
||||
|
||||
pub enum BuildPlatformType {
|
||||
linux_arm64
|
||||
linux_amd64
|
||||
}
|
||||
|
||||
fn (mut e CEngine) init() ! {
|
||||
if e.buildpath == '' {
|
||||
e.buildpath = '/tmp/builder'
|
||||
exec(cmd: 'mkdir -p ${e.buildpath}', stdout: false)!
|
||||
}
|
||||
e.load()!
|
||||
}
|
||||
|
||||
// reload the state from system
|
||||
pub fn (mut e CEngine) load() ! {
|
||||
e.builders_load()!
|
||||
e.images_load()!
|
||||
e.containers_load()!
|
||||
}
|
||||
|
||||
// reset all images & containers, CAREFUL!
|
||||
pub fn (mut e CEngine) reset_all() ! {
|
||||
e.load()!
|
||||
for mut container in e.containers.clone() {
|
||||
container.delete()!
|
||||
}
|
||||
for mut image in e.images.clone() {
|
||||
image.delete(true)!
|
||||
}
|
||||
exec(cmd: 'podman rm -a -f', stdout: false)!
|
||||
exec(cmd: 'podman rmi -a -f', stdout: false)!
|
||||
e.builders_delete_all()!
|
||||
osal.done_reset()!
|
||||
if osal.platform() == .arch {
|
||||
exec(cmd: 'systemctl status podman.socket', stdout: false)!
|
||||
}
|
||||
e.load()!
|
||||
}
|
||||
|
||||
// Get free port
|
||||
pub fn (mut e CEngine) get_free_port() ?int {
|
||||
mut used_ports := []int{}
|
||||
mut range := []int{}
|
||||
|
||||
for c in e.containers {
|
||||
for p in c.forwarded_ports {
|
||||
used_ports << p.split(':')[0].int()
|
||||
}
|
||||
}
|
||||
|
||||
for i in 20000 .. 40000 {
|
||||
if i !in used_ports {
|
||||
range << i
|
||||
}
|
||||
}
|
||||
// arrays.shuffle<int>(mut range, 0)
|
||||
if range.len == 0 {
|
||||
return none
|
||||
}
|
||||
return range[0]
|
||||
}
|
||||
141
lib/virt/herocontainers/cengine_builder_solutions.v
Normal file
141
lib/virt/herocontainers/cengine_builder_solutions.v
Normal file
@@ -0,0 +1,141 @@
|
||||
module herocontainers
|
||||
|
||||
import freeflowuniverse.herolib.osal
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.installers.virt.pacman
|
||||
import os
|
||||
|
||||
@[params]
|
||||
pub struct GetArgs {
|
||||
reset bool
|
||||
}
|
||||
|
||||
// builder machine based on arch and install vlang
|
||||
pub fn (mut e CEngine) builder_base(args GetArgs) !Builder {
|
||||
name := 'base'
|
||||
if !args.reset && e.builder_exists(name)! {
|
||||
return e.builder_get(name)!
|
||||
}
|
||||
console.print_header('buildah base build')
|
||||
|
||||
// mut installer:= pacman.get()!
|
||||
// installer.install()!
|
||||
|
||||
mut builder := e.builder_new(name: name, from: 'scratch', delete: true)!
|
||||
mount_path := builder.mount_to_path()!
|
||||
osal.exec(
|
||||
cmd: 'pacstrap -D -c ${mount_path} base screen bash coreutils curl mc unzip sudo which openssh'
|
||||
)!
|
||||
// builder.set_entrypoint('redis-server')!
|
||||
builder.commit('localhost/${name}')!
|
||||
return builder
|
||||
}
|
||||
|
||||
// builder machine based on arch and install vlang
|
||||
pub fn (mut e CEngine) builder_go_rust(args GetArgs) !Builder {
|
||||
console.print_header('buildah builder go rust')
|
||||
name := 'builder_go_rust'
|
||||
e.builder_base(reset: false)!
|
||||
if !args.reset && e.builder_exists(name)! {
|
||||
return e.builder_get(name)!
|
||||
}
|
||||
mut builder := e.builder_new(name: name, from: 'localhost/base', delete: true)!
|
||||
builder.hero_execute_cmd('installers -n golang,rust')!
|
||||
// builder.clean()!
|
||||
builder.commit('localhost/${name}')!
|
||||
e.load()!
|
||||
return builder
|
||||
}
|
||||
|
||||
pub fn (mut e CEngine) builder_js(args GetArgs) !Builder {
|
||||
console.print_header('buildah builder js')
|
||||
name := 'builder_js'
|
||||
e.builder_base(reset: false)!
|
||||
if !args.reset && e.builder_exists(name)! {
|
||||
return e.builder_get(name)!
|
||||
}
|
||||
mut builder := e.builder_new(name: name, from: 'localhost/base', delete: true)!
|
||||
builder.hero_execute_cmd('installers -n nodejs')!
|
||||
// builder.clean()!
|
||||
builder.commit('localhost/${name}')!
|
||||
e.load()!
|
||||
return builder
|
||||
}
|
||||
|
||||
pub fn (mut e CEngine) builder_js_python(args GetArgs) !Builder {
|
||||
console.print_header('buildah builder js python')
|
||||
name := 'builder_js_python'
|
||||
e.builder_js(reset: false)!
|
||||
if !args.reset && e.builder_exists(name)! {
|
||||
return e.builder_get(name)!
|
||||
}
|
||||
mut builder := e.builder_new(name: name, from: 'localhost/builder_js', delete: true)!
|
||||
builder.hero_execute_cmd('installers -n python')!
|
||||
// builder.clean()!
|
||||
builder.commit('localhost/${name}')!
|
||||
e.load()!
|
||||
return builder
|
||||
}
|
||||
|
||||
pub fn (mut e CEngine) builder_hero(args GetArgs) !Builder {
|
||||
console.print_header('buildah builder hero dev')
|
||||
name := 'builder_hero'
|
||||
e.builder_js_python(reset: false)!
|
||||
if !args.reset && e.builder_exists(name)! {
|
||||
return e.builder_get(name)!
|
||||
}
|
||||
mut builder := e.builder_new(name: name, from: 'localhost/builder_js_python', delete: true)!
|
||||
builder.hero_execute_cmd('installers -n hero')!
|
||||
// builder.clean()!
|
||||
builder.commit('localhost/${name}')!
|
||||
e.load()!
|
||||
return builder
|
||||
}
|
||||
|
||||
pub fn (mut e CEngine) builder_herodev(args GetArgs) !Builder {
|
||||
console.print_header('buildah builder hero dev')
|
||||
name := 'builder_herodev'
|
||||
e.builder_js_python(reset: false)!
|
||||
if !args.reset && e.builder_exists(name)! {
|
||||
return e.builder_get(name)!
|
||||
}
|
||||
mut builder := e.builder_new(name: name, from: 'localhost/builder_hero', delete: true)!
|
||||
builder.hero_execute_cmd('installers -n herodev')!
|
||||
// builder.clean()!
|
||||
builder.commit('localhost/${name}')!
|
||||
e.load()!
|
||||
return builder
|
||||
}
|
||||
|
||||
pub fn (mut e CEngine) builder_heroweb(args GetArgs) !Builder {
|
||||
console.print_header('buildah builder hero web')
|
||||
name := 'builder_heroweb'
|
||||
e.builder_go_rust(reset: false)!
|
||||
e.builder_hero(reset: false)!
|
||||
if !args.reset && e.builder_exists(name)! {
|
||||
return e.builder_get(name)!
|
||||
}
|
||||
mut builder0 := e.builder_new(
|
||||
name: 'builder_heroweb_temp'
|
||||
from: 'localhost/builder_go_rust'
|
||||
delete: true
|
||||
)!
|
||||
builder0.hero_execute_cmd('installers -n heroweb')!
|
||||
// builder0.hero_execute_cmd("installers -n heroweb")!
|
||||
mpath := builder0.mount_to_path()!
|
||||
|
||||
// copy the built binary to host
|
||||
osal.exec(
|
||||
cmd: '
|
||||
mkdir -p ${os.home_dir()}/hero/var/bin
|
||||
cp ${mpath}/usr/local/bin/* ${os.home_dir()}/hero/var/bin/
|
||||
'
|
||||
)!
|
||||
|
||||
builder0.delete()!
|
||||
mut builder2 := e.builder_new(name: name, from: 'localhost/builder_hero', delete: true)!
|
||||
builder2.copy('${os.home_dir()}/hero/var/bin/', '/usr/local/bin/')!
|
||||
builder2.commit('localhost/${name}')!
|
||||
e.load()!
|
||||
return builder2
|
||||
}
|
||||
83
lib/virt/herocontainers/cengine_builders.v
Normal file
83
lib/virt/herocontainers/cengine_builders.v
Normal file
@@ -0,0 +1,83 @@
|
||||
module herocontainers
|
||||
|
||||
import freeflowuniverse.herolib.osal
|
||||
// import freeflowuniverse.herolib.ui.console
|
||||
import json
|
||||
|
||||
fn (mut e CEngine) builders_load() ! {
|
||||
cmd := 'buildah containers --json'
|
||||
out := osal.execute_silent(cmd)!
|
||||
mut r := json.decode([]Builder, out) or { return error('Failed to decode JSON: ${err}') }
|
||||
for mut item in r {
|
||||
item.engine = &e
|
||||
}
|
||||
e.builders = r
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct BuilderNewArgs {
|
||||
pub mut:
|
||||
name string = 'default'
|
||||
from string = 'docker.io/archlinux:latest'
|
||||
// arch_scratch bool // means start from scratch with arch linux
|
||||
delete bool = true
|
||||
}
|
||||
|
||||
pub fn (mut e CEngine) builder_new(args_ BuilderNewArgs) !Builder {
|
||||
mut args := args_
|
||||
if args.delete {
|
||||
e.builder_delete(args.name)!
|
||||
}
|
||||
osal.exec(cmd: 'buildah --name ${args.name} from ${args.from}')!
|
||||
e.builders_load()!
|
||||
return e.builder_get(args.name)
|
||||
}
|
||||
|
||||
// get buildah containers
|
||||
pub fn (mut e CEngine) builders_get() ![]Builder {
|
||||
if e.builders.len == 0 {
|
||||
e.builders_load()!
|
||||
}
|
||||
return e.builders
|
||||
}
|
||||
|
||||
pub fn (mut e CEngine) builder_exists(name string) !bool {
|
||||
r := e.builders_get()!
|
||||
res := r.filter(it.containername == name)
|
||||
if res.len == 1 {
|
||||
return true
|
||||
}
|
||||
if res.len > 1 {
|
||||
panic('bug')
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
pub fn (mut e CEngine) builder_get(name string) !Builder {
|
||||
r := e.builders_get()!
|
||||
res := r.filter(it.containername == name)
|
||||
if res.len == 1 {
|
||||
return res[0]
|
||||
}
|
||||
if res.len > 1 {
|
||||
panic('bug')
|
||||
}
|
||||
return error('couldnt find builder with name ${name}')
|
||||
}
|
||||
|
||||
pub fn (mut e CEngine) builders_delete_all() ! {
|
||||
osal.execute_stdout('buildah rm -a')!
|
||||
e.builders_load()!
|
||||
}
|
||||
|
||||
pub fn (mut e CEngine) builder_delete(name string) ! {
|
||||
if e.builder_exists(name)! {
|
||||
osal.execute_stdout('buildah rm ${name}')!
|
||||
e.builders_load()!
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut e CEngine) builder_names() ![]string {
|
||||
r := e.builders_get()!
|
||||
return r.map(it.containername)
|
||||
}
|
||||
163
lib/virt/herocontainers/cengine_containers.v
Normal file
163
lib/virt/herocontainers/cengine_containers.v
Normal file
@@ -0,0 +1,163 @@
|
||||
module herocontainers
|
||||
|
||||
import freeflowuniverse.herolib.osal { exec }
|
||||
// import freeflowuniverse.herolib.data.ipaddress { IPAddress }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.virt.utils
|
||||
// import freeflowuniverse.herolib.ui.console
|
||||
|
||||
// load all containers, they can be consulted in e.containers
|
||||
// see obj: Container as result in e.containers
|
||||
fn (mut e CEngine) containers_load() ! {
|
||||
e.containers = []Container{}
|
||||
mut ljob := exec(
|
||||
// we used || because sometimes the command has | in it and this will ruin all subsequent columns
|
||||
cmd: "podman container list -a --no-trunc --size --format '{{.ID}}||{{.Names}}||{{.ImageID}}||{{.Command}}||{{.CreatedAt}}||{{.Ports}}||{{.State}}||{{.Size}}||{{.Mounts}}||{{.Networks}}||{{.Labels}}'"
|
||||
ignore_error_codes: [6]
|
||||
stdout: false
|
||||
)!
|
||||
lines := ljob.output.split_into_lines()
|
||||
for line in lines {
|
||||
if line.trim_space() == '' {
|
||||
continue
|
||||
}
|
||||
fields := line.split('||').map(utils.clear_str)
|
||||
if fields.len < 11 {
|
||||
panic('podman ps needs to output 11 parts.\n${fields}')
|
||||
}
|
||||
id := fields[0]
|
||||
// if image doesn't have id skip this container, maybe ran from filesystme
|
||||
if fields[2] == '' {
|
||||
continue
|
||||
}
|
||||
mut image := e.image_get(id_full: fields[2])!
|
||||
mut container := Container{
|
||||
engine: &e
|
||||
image: &image
|
||||
}
|
||||
container.id = id
|
||||
container.name = texttools.name_fix(fields[1])
|
||||
container.command = fields[3]
|
||||
container.created = utils.parse_time(fields[4])!
|
||||
container.ports = utils.parse_ports(fields[5])!
|
||||
container.status = utils.parse_container_state(fields[6])!
|
||||
container.memsize = utils.parse_size_mb(fields[7])!
|
||||
container.mounts = utils.parse_mounts(fields[8])!
|
||||
container.mounts = []
|
||||
container.networks = utils.parse_networks(fields[9])!
|
||||
container.labels = utils.parse_labels(fields[10])!
|
||||
container.ssh_enabled = utils.contains_ssh_port(container.ports)
|
||||
e.containers << container
|
||||
}
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct ContainerGetArgs {
|
||||
pub mut:
|
||||
name string
|
||||
id string
|
||||
image_id string
|
||||
// tag string
|
||||
// digest string
|
||||
}
|
||||
|
||||
// get containers from memory
|
||||
// params:
|
||||
// name string (can also be a glob e.g. use *,? and [])
|
||||
// id string
|
||||
// image_id string
|
||||
pub fn (mut e CEngine) containers_get(args_ ContainerGetArgs) ![]&Container {
|
||||
mut args := args_
|
||||
args.name = texttools.name_fix(args.name)
|
||||
mut res := []&Container{}
|
||||
for _, c in e.containers {
|
||||
if args.name.contains('*') || args.name.contains('?') || args.name.contains('[') {
|
||||
if c.name.match_glob(args.name) {
|
||||
res << &c
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if c.name == args.name || c.id == args.id {
|
||||
res << &c
|
||||
continue
|
||||
}
|
||||
}
|
||||
if args.image_id.len > 0 && c.image.id == args.image_id {
|
||||
res << &c
|
||||
}
|
||||
}
|
||||
if res.len == 0 {
|
||||
return ContainerGetError{
|
||||
args: args
|
||||
notfound: true
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// get container from memory, can use match_glob see https://modules.vlang.io/index.html#string.match_glob
|
||||
pub fn (mut e CEngine) container_get(args_ ContainerGetArgs) !&Container {
|
||||
mut args := args_
|
||||
args.name = texttools.name_fix(args.name)
|
||||
mut res := e.containers_get(args)!
|
||||
if res.len > 1 {
|
||||
return ContainerGetError{
|
||||
args: args
|
||||
notfound: true
|
||||
}
|
||||
}
|
||||
return res[0]
|
||||
}
|
||||
|
||||
pub fn (mut e CEngine) container_exists(args ContainerGetArgs) !bool {
|
||||
e.container_get(args) or {
|
||||
if err.code() == 1 {
|
||||
return false
|
||||
}
|
||||
return err
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
pub fn (mut e CEngine) container_delete(args ContainerGetArgs) ! {
|
||||
mut c := e.container_get(args)!
|
||||
c.delete()!
|
||||
e.load()!
|
||||
}
|
||||
|
||||
// remove one or more container
|
||||
pub fn (mut e CEngine) containers_delete(args ContainerGetArgs) ! {
|
||||
mut cs := e.containers_get(args)!
|
||||
for mut c in cs {
|
||||
c.delete()!
|
||||
}
|
||||
e.load()!
|
||||
}
|
||||
|
||||
pub struct ContainerGetError {
|
||||
Error
|
||||
pub:
|
||||
args ContainerGetArgs
|
||||
notfound bool
|
||||
toomany bool
|
||||
}
|
||||
|
||||
pub fn (err ContainerGetError) msg() string {
|
||||
if err.notfound {
|
||||
return 'Could not find image with args:\n${err.args}'
|
||||
}
|
||||
if err.toomany {
|
||||
return 'can not get container, Found more than 1 container with args:\n${err.args}'
|
||||
}
|
||||
panic('unknown error for ContainerGetError')
|
||||
}
|
||||
|
||||
pub fn (err ContainerGetError) code() int {
|
||||
if err.notfound {
|
||||
return 1
|
||||
}
|
||||
if err.toomany {
|
||||
return 2
|
||||
}
|
||||
panic('unknown error for ContainerGetError')
|
||||
}
|
||||
138
lib/virt/herocontainers/cengine_images.v
Normal file
138
lib/virt/herocontainers/cengine_images.v
Normal file
@@ -0,0 +1,138 @@
|
||||
module herocontainers
|
||||
|
||||
import freeflowuniverse.herolib.virt.utils
|
||||
import freeflowuniverse.herolib.osal { exec }
|
||||
import time
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
fn (mut e CEngine) images_load() ! {
|
||||
e.images = []Image{}
|
||||
mut lines := osal.execute_silent("podman images --format '{{.ID}}||{{.Id}}||{{.Repository}}||{{.Tag}}||{{.Digest}}||{{.Size}}||{{.CreatedAt}}'")!
|
||||
for line in lines.split_into_lines() {
|
||||
fields := line.split('||').map(utils.clear_str)
|
||||
if fields.len != 7 {
|
||||
panic('podman image needs to output 7 parts.\n${fields}')
|
||||
}
|
||||
mut image := Image{
|
||||
engine: &e
|
||||
}
|
||||
image.id = fields[0]
|
||||
image.id_full = fields[1]
|
||||
image.repo = fields[2]
|
||||
image.tag = fields[3]
|
||||
image.digest = utils.parse_digest(fields[4]) or { '' }
|
||||
image.size = utils.parse_size_mb(fields[5]) or { 0 }
|
||||
image.created = utils.parse_time(fields[6]) or { time.now() }
|
||||
e.images << image
|
||||
}
|
||||
}
|
||||
|
||||
// import herocontainers image back into the local env
|
||||
pub fn (mut engine CEngine) image_load(path string) ! {
|
||||
exec(cmd: 'podman load < ${path}', stdout: false)!
|
||||
engine.images_load()!
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct ImageGetArgs {
|
||||
pub:
|
||||
repo string
|
||||
tag string
|
||||
digest string
|
||||
id string
|
||||
id_full string
|
||||
}
|
||||
|
||||
// find image based on repo and optional tag
|
||||
// args:
|
||||
// repo string
|
||||
// tag string
|
||||
// digest string
|
||||
// id string
|
||||
// id_full
|
||||
pub fn (mut e CEngine) image_get(args ImageGetArgs) !Image {
|
||||
for i in e.images {
|
||||
if args.digest != '' && i.digest == args.digest {
|
||||
return i
|
||||
}
|
||||
if args.id != '' && i.id == args.id {
|
||||
return i
|
||||
}
|
||||
if args.id_full != '' && i.id_full == args.id_full {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
if args.repo != '' || args.tag != '' {
|
||||
mut counter := 0
|
||||
mut result_digest := ''
|
||||
for i in e.images {
|
||||
if args.repo != '' && i.repo != args.repo {
|
||||
continue
|
||||
}
|
||||
if args.tag != '' && i.tag != args.tag {
|
||||
continue
|
||||
}
|
||||
console.print_debug('found image for get: ${i} -- ${args}')
|
||||
result_digest = i.digest
|
||||
counter += 1
|
||||
}
|
||||
if counter > 1 {
|
||||
return ImageGetError{
|
||||
args: args
|
||||
toomany: true
|
||||
}
|
||||
}
|
||||
return e.image_get(digest: result_digest)!
|
||||
}
|
||||
return ImageGetError{
|
||||
args: args
|
||||
notfound: true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut e CEngine) image_exists(args ImageGetArgs) !bool {
|
||||
e.image_get(args) or {
|
||||
if err.code() == 1 {
|
||||
return false
|
||||
}
|
||||
return err
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// get buildah containers
|
||||
pub fn (mut e CEngine) images_get() ![]Image {
|
||||
if e.builders.len == 0 {
|
||||
e.images_load()!
|
||||
}
|
||||
return e.images
|
||||
}
|
||||
|
||||
pub fn (err ImageGetError) msg() string {
|
||||
if err.notfound {
|
||||
return 'Could not find image with args:\n${err.args}'
|
||||
}
|
||||
if err.toomany {
|
||||
return 'Found more than 1 image with args:\n${err.args}'
|
||||
}
|
||||
panic('unknown error for ImageGetError')
|
||||
}
|
||||
|
||||
pub fn (err ImageGetError) code() int {
|
||||
if err.notfound {
|
||||
return 1
|
||||
}
|
||||
if err.toomany {
|
||||
return 2
|
||||
}
|
||||
panic('unknown error for ImageGetError')
|
||||
}
|
||||
|
||||
pub struct ImageGetError {
|
||||
Error
|
||||
pub:
|
||||
args ImageGetArgs
|
||||
notfound bool
|
||||
toomany bool
|
||||
}
|
||||
135
lib/virt/herocontainers/container.v
Normal file
135
lib/virt/herocontainers/container.v
Normal file
@@ -0,0 +1,135 @@
|
||||
module herocontainers
|
||||
|
||||
import time
|
||||
import freeflowuniverse.herolib.osal { exec }
|
||||
import freeflowuniverse.herolib.data.ipaddress { IPAddress }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.virt.utils
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
@[heap]
|
||||
pub struct Container {
|
||||
pub mut:
|
||||
id string
|
||||
name string
|
||||
created time.Time
|
||||
ssh_enabled bool // if yes make sure ssh is enabled to the container
|
||||
ipaddr IPAddress
|
||||
forwarded_ports []string
|
||||
mounts []utils.ContainerVolume
|
||||
ssh_port int // ssh port on node that is used to get ssh
|
||||
ports []string
|
||||
networks []string
|
||||
labels map[string]string @[str: skip]
|
||||
image &Image @[str: skip]
|
||||
engine &CEngine @[skip; str: skip]
|
||||
status utils.ContainerStatus
|
||||
memsize int // in MB
|
||||
command string
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct ContainerCreateArgs {
|
||||
name string
|
||||
hostname string
|
||||
forwarded_ports []string // ["80:9000/tcp", "1000, 10000/udp"]
|
||||
mounted_volumes []string // ["/root:/root", ]
|
||||
env map[string]string // map of environment variables that will be passed to the container
|
||||
privileged bool
|
||||
remove_when_done bool = true // remove the container when it shuts down
|
||||
pub mut:
|
||||
image_repo string
|
||||
image_tag string
|
||||
command string = '/bin/bash'
|
||||
}
|
||||
|
||||
// TODO: implement
|
||||
|
||||
// import a container into an image, run podman container with it
|
||||
// image_repo examples ['myimage', 'myimage:latest']
|
||||
// if ContainerCreateArgs contains a name, container will be created and restarted
|
||||
// pub fn (mut e CEngine) container_import(path string, mut args ContainerCreateArgs) !&Container {
|
||||
// mut image := args.image_repo
|
||||
// if args.image_tag != '' {
|
||||
// image = image + ':${args.image_tag}'
|
||||
// }
|
||||
|
||||
// exec(cmd: 'herocontainers import ${path} ${image}', stdout: false)!
|
||||
// // make sure we start from loaded image
|
||||
// return e.container_create(args)
|
||||
// }
|
||||
|
||||
// create/start container (first need to get a herocontainerscontainer before we can start)
|
||||
pub fn (mut container Container) start() ! {
|
||||
exec(cmd: 'podman start ${container.id}')!
|
||||
container.status = utils.ContainerStatus.up
|
||||
}
|
||||
|
||||
// delete container
|
||||
pub fn (mut container Container) halt() ! {
|
||||
osal.execute_stdout('podman stop ${container.id}') or { '' }
|
||||
container.status = utils.ContainerStatus.down
|
||||
}
|
||||
|
||||
// delete container
|
||||
pub fn (mut container Container) delete() ! {
|
||||
console.print_debug('container delete: ${container.name}')
|
||||
cmd := 'podman rm ${container.id} -f'
|
||||
// console.print_debug(cmd)
|
||||
exec(cmd: cmd, stdout: false)!
|
||||
}
|
||||
|
||||
// save the container to image
|
||||
pub fn (mut container Container) save2image(image_repo string, image_tag string) !string {
|
||||
id := osal.execute_stdout('podman commit ${container.id} ${image_repo}:${image_tag}')!
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
// export herocontainers to tgz
|
||||
pub fn (mut container Container) export(path string) ! {
|
||||
exec(cmd: 'podman export ${container.id} > ${path}')!
|
||||
}
|
||||
|
||||
// // open ssh shell to the cobtainer
|
||||
// pub fn (mut container Container) ssh_shell(cmd string) ! {
|
||||
// container.engine.node.shell(cmd)!
|
||||
// }
|
||||
|
||||
@[params]
|
||||
pub struct BAHShellArgs {
|
||||
pub mut:
|
||||
cmd string
|
||||
}
|
||||
|
||||
// open shell to the container using podman, is interactive, cannot use in script
|
||||
pub fn (mut container Container) shell(args BAHShellArgs) ! {
|
||||
mut cmd := ''
|
||||
if args.cmd.len == 0 {
|
||||
cmd = 'podman exec -ti ${container.id} /bin/bash'
|
||||
} else {
|
||||
cmd = "podman exec -ti ${container.id} /bin/bash -c '${args.cmd}'"
|
||||
}
|
||||
exec(cmd: cmd, shell: true, debug: true)!
|
||||
}
|
||||
|
||||
pub fn (mut container Container) execute(cmd_ string, silent bool) ! {
|
||||
cmd := 'podman exec ${container.id} ${cmd_}'
|
||||
exec(cmd: cmd, stdout: !silent)!
|
||||
}
|
||||
|
||||
// pub fn (mut container Container) ssh_enable() ! {
|
||||
// // mut herocontainers_pubkey := pubkey
|
||||
// // cmd = "podman exec $container.id sh -c 'echo \"$herocontainers_pubkey\" >> ~/.ssh/authorized_keys'"
|
||||
|
||||
// // if container.engine.node.executor is builder.ExecutorSSH {
|
||||
// // mut sshkey := container.engine.node.executor.info()['sshkey'] + '.pub'
|
||||
// // sshkey = os.read_file(sshkey) or { panic(err) }
|
||||
// // // add pub sshkey on authorized keys of node and container
|
||||
// // cmd = "echo \"$sshkey\" >> ~/.ssh/authorized_keys && podman exec $container.id sh -c 'echo \"$herocontainers_pubkey\" >> ~/.ssh/authorized_keys && echo \"$sshkey\" >> ~/.ssh/authorized_keys'"
|
||||
// // }
|
||||
|
||||
// // wait making sure container started correctly
|
||||
// // time.sleep_ms(100 * time.millisecond)
|
||||
// // container.engine.node.executor.exec(cmd) !
|
||||
// }
|
||||
42
lib/virt/herocontainers/factory.v
Normal file
42
lib/virt/herocontainers/factory.v
Normal file
@@ -0,0 +1,42 @@
|
||||
module herocontainers
|
||||
|
||||
import freeflowuniverse.herolib.installers.virt.podman as podman_installer
|
||||
import freeflowuniverse.herolib.installers.virt.buildah as buildah_installer
|
||||
import freeflowuniverse.herolib.installers.lang.herolib
|
||||
import freeflowuniverse.herolib.osal
|
||||
|
||||
@[params]
|
||||
pub struct NewArgs {
|
||||
pub mut:
|
||||
install bool = true
|
||||
reset bool
|
||||
herocompile bool
|
||||
}
|
||||
|
||||
pub fn new(args_ NewArgs) !CEngine {
|
||||
mut args := args_
|
||||
|
||||
if !osal.is_linux() {
|
||||
return error('only linux supported as host for now')
|
||||
}
|
||||
|
||||
if args.install {
|
||||
mut podman_installer0 := podman_installer.get()!
|
||||
mut buildah_installer0 := buildah_installer.get()!
|
||||
podman_installer0.install()!
|
||||
buildah_installer0.install()!
|
||||
}
|
||||
|
||||
if args.herocompile {
|
||||
herolib.check()! // will check if install, if not will do
|
||||
herolib.hero_compile(reset: true)!
|
||||
}
|
||||
|
||||
mut engine := CEngine{}
|
||||
engine.init()!
|
||||
if args.reset {
|
||||
engine.reset_all()!
|
||||
}
|
||||
|
||||
return engine
|
||||
}
|
||||
35
lib/virt/herocontainers/image.v
Normal file
35
lib/virt/herocontainers/image.v
Normal file
@@ -0,0 +1,35 @@
|
||||
module herocontainers
|
||||
|
||||
import freeflowuniverse.herolib.osal { exec }
|
||||
import time
|
||||
import freeflowuniverse.herolib.virt.utils
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
// TODO: needs to be implemented for buildah, is still code from docker
|
||||
|
||||
@[heap]
|
||||
pub struct Image {
|
||||
pub mut:
|
||||
repo string
|
||||
id string
|
||||
id_full string
|
||||
tag string
|
||||
digest string
|
||||
size int // size in MB
|
||||
created time.Time
|
||||
engine &CEngine @[skip; str: skip]
|
||||
}
|
||||
|
||||
// delete podman image
|
||||
pub fn (mut image Image) delete(force bool) ! {
|
||||
mut forcestr := ''
|
||||
if force {
|
||||
forcestr = '-f'
|
||||
}
|
||||
exec(cmd: 'podman rmi ${image.id} ${forcestr}', stdout: false)!
|
||||
}
|
||||
|
||||
// export podman image to tar.gz
|
||||
pub fn (mut image Image) export(path string) !string {
|
||||
exec(cmd: 'podman save ${image.id} > ${path}', stdout: false)!
|
||||
return ''
|
||||
}
|
||||
72
lib/virt/herocontainers/readme.md
Normal file
72
lib/virt/herocontainers/readme.md
Normal file
@@ -0,0 +1,72 @@
|
||||
|
||||
# Herocontainers
|
||||
|
||||
Tools to work with containers
|
||||
|
||||
```go
|
||||
#!/usr/bin/env -S v -n -cg -w -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.virt.herocontainers
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.builder
|
||||
|
||||
//interative means will ask for login/passwd
|
||||
|
||||
console.print_header("BUILDAH Demo.")
|
||||
|
||||
//if herocompile on, then will forced compile hero, which might be needed in debug mode for hero
|
||||
// to execute hero scripts inside build container
|
||||
mut pm:=herocontainers.new(herocompile=true)!
|
||||
//mut b:=pm.builder_new(name:"test")!
|
||||
|
||||
//create
|
||||
pm.builderv_create()!
|
||||
|
||||
//get the container
|
||||
//mut b2:=pm.builder_get("builderv")!
|
||||
//b2.shell()!
|
||||
|
||||
|
||||
```
|
||||
|
||||
## buildah tricks
|
||||
|
||||
```bash
|
||||
#find the containers as have been build, these are the active ones you can work with
|
||||
buildah ls
|
||||
#see the images
|
||||
buildah images
|
||||
```
|
||||
|
||||
result is something like
|
||||
|
||||
|
||||
```bash
|
||||
CONTAINER ID BUILDER IMAGE ID IMAGE NAME CONTAINER NAME
|
||||
a9946633d4e7 * scratch base
|
||||
86ff0deb00bf * 4feda76296d6 localhost/builder:latest base_go_rust
|
||||
```
|
||||
|
||||
some tricks
|
||||
|
||||
```bash
|
||||
#run interactive in one (here we chose the builderv one)
|
||||
buildah run --terminal --env TERM=xterm base /bin/bash
|
||||
#or
|
||||
buildah run --terminal --env TERM=xterm default /bin/bash
|
||||
#or
|
||||
buildah run --terminal --env TERM=xterm base_go_rust /bin/bash
|
||||
|
||||
```
|
||||
|
||||
to check inside the container about diskusage
|
||||
|
||||
```bash
|
||||
pacman -Su ncdu
|
||||
ncdu
|
||||
```
|
||||
|
||||
|
||||
## future
|
||||
|
||||
should make this module compatible with https://github.com/containerd/nerdctl
|
||||
8
lib/virt/hetzner/.heroscript
Normal file
8
lib/virt/hetzner/.heroscript
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
!!hero_code.generate_client
|
||||
name:'hetzner'
|
||||
classname:'HetznerManager'
|
||||
singleton:0
|
||||
default:1
|
||||
hasconfig:1
|
||||
reset:0
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user