diff --git a/aiprompts/ai_instruct/instruct.md b/aiprompts/ai_instruct/prompt_processing_instructions.md similarity index 100% rename from aiprompts/ai_instruct/instruct.md rename to aiprompts/ai_instruct/prompt_processing_instructions.md diff --git a/aiprompts/ai_instruct/instruct2.md b/aiprompts/ai_instruct/prompt_processing_openrpc_like.md similarity index 100% rename from aiprompts/ai_instruct/instruct2.md rename to aiprompts/ai_instruct/prompt_processing_openrpc_like.md diff --git a/aiprompts/ai_instruct/twin.md b/aiprompts/ai_instruct/what_is_a_hero_twin.md similarity index 100% rename from aiprompts/ai_instruct/twin.md rename to aiprompts/ai_instruct/what_is_a_hero_twin.md diff --git a/aiprompts/code/opeapi.md b/aiprompts/code/opeapi.md deleted file mode 100644 index 17d4b67a..00000000 --- a/aiprompts/code/opeapi.md +++ /dev/null @@ -1,15 +0,0 @@ -for @lib/circles/mcc - -generate openapi 3.1 spec -do it as one file called openapi.yaml and put in the dir as mentioned above - -based on the models and db implementation - -implement well chosen examples in the openapi spec - -note: in OpenAPI 3.1.0, the example property is deprecated in favor of examples - -do this for the models & methods as defined below - -do it also for the custom and generic methods, don't forget any - diff --git a/aiprompts/code/opeapi_full.md b/aiprompts/code/opeapi_full.md deleted file mode 100644 index b6f3b441..00000000 --- a/aiprompts/code/opeapi_full.md +++ /dev/null @@ -1,197 +0,0 @@ -in @lib/circles/mcc -generate openapi 3.1 spec -based on the models and db implementation - -implement well chosen examples in the openapi spec - -note: in OpenAPI 3.1.0, the example property is deprecated in favor of examples. - -do this for the models & methods as defined below - -do it for custom and generic methods, don't forget any - -```v - -// CalendarEvent represents a calendar event with all its properties -pub struct CalendarEvent { -pub mut: - id u32 // Unique identifier - title string // Event title - description string // Event details - location string // Event location - start_time ourtime.OurTime - end_time ourtime.OurTime // End time - all_day bool // True if it's an all-day event - recurrence string // RFC 5545 Recurrence Rule (e.g., "FREQ=DAILY;COUNT=10") - attendees []string // List of emails or user IDs - organizer string // Organizer email - status string // "CONFIRMED", "CANCELLED", "TENTATIVE" - caldav_uid string // CalDAV UID for syncing - sync_token string // Sync token for tracking changes - etag string // ETag for caching - color string // User-friendly color categorization -} - - -// Email represents an email message with all its metadata and content -pub struct Email { -pub mut: - // Database ID - id u32 // Database ID (assigned by DBHandler) - // Content fields - uid u32 // Unique identifier of the message (in the circle) - seq_num u32 // IMAP sequence number (in the mailbox) - mailbox string // The mailbox this email belongs to - message string // The email body content - attachments []Attachment // Any file attachments - - // IMAP specific fields - flags []string // IMAP flags like \Seen, \Deleted, etc. - internal_date i64 // Unix timestamp when the email was received - size u32 // Size of the message in bytes - envelope ?Envelope // IMAP envelope information (contains From, To, Subject, etc.) -} - -// Attachment represents an email attachment -pub struct Attachment { -pub mut: - filename string - content_type string - data string // Base64 encoded binary data -} - -// Envelope represents an IMAP envelope structure -pub struct Envelope { -pub mut: - date i64 - subject string - from []string - sender []string - reply_to []string - to []string - cc []string - bcc []string - in_reply_to string - message_id string -} -``` - -methods - -```v -pub fn (mut m MailDB) new() Email { -} - -// set adds or updates an email -pub fn (mut m MailDB) set(email Email) !Email { -} - -// get retrieves an email by its ID -pub fn (mut m MailDB) get(id u32) !Email { -} - -// list returns all email IDs -pub fn (mut m MailDB) list() ![]u32 { -} - -pub fn (mut m MailDB) getall() ![]Email { -} - -// delete removes an email by its ID -pub fn (mut m MailDB) delete(id u32) ! { -} - -//////////////////CUSTOM METHODS////////////////////////////////// - -// get_by_uid retrieves an email by its UID -pub fn (mut m MailDB) get_by_uid(uid u32) !Email { -} - -// get_by_mailbox retrieves all emails in a specific mailbox -pub fn (mut m MailDB) get_by_mailbox(mailbox string) ![]Email { -} - -// delete_by_uid removes an email by its UID -pub fn (mut m MailDB) delete_by_uid(uid u32) ! { -} - -// delete_by_mailbox removes all emails in a specific mailbox -pub fn (mut m MailDB) delete_by_mailbox(mailbox string) ! { -} - -// update_flags updates the flags of an email -pub fn (mut m MailDB) update_flags(uid u32, flags []string) !Email { -} - -// search_by_subject searches for emails with a specific subject substring -pub fn (mut m MailDB) search_by_subject(subject string) ![]Email { -} - -// search_by_address searches for emails with a specific email address in from, to, cc, or bcc fields -pub fn (mut m MailDB) search_by_address(address string) ![]Email { -} - -pub fn (mut c CalendarDB) new() CalendarEvent { - CalendarEvent {} -} - -// set adds or updates a calendar event -pub fn (mut c CalendarDB) set(event CalendarEvent) CalendarEvent { - CalendarEvent {} -} - -// get retrieves a calendar event by its ID -pub fn (mut c CalendarDB) get(id u32) CalendarEvent { - CalendarEvent {} -} - -// list returns all calendar event IDs -pub fn (mut c CalendarDB) list() []u32 { - [] -} - -pub fn (mut c CalendarDB) getall() []CalendarEvent { - [] -} - -// delete removes a calendar event by its ID -pub fn (mut c CalendarDB) delete(id u32) { -} - -//////////////////CUSTOM METHODS////////////////////////////////// - -// get_by_caldav_uid retrieves a calendar event by its CalDAV UID -pub fn (mut c CalendarDB) get_by_caldav_uid(caldav_uid String) CalendarEvent { - CalendarEvent {} -} - -// get_events_by_date retrieves all events that occur on a specific date -pub fn (mut c CalendarDB) get_events_by_date(date String) []CalendarEvent { - [] -} - -// get_events_by_organizer retrieves all events organized by a specific person -pub fn (mut c CalendarDB) get_events_by_organizer(organizer String) []CalendarEvent { - [] -} - -// get_events_by_attendee retrieves all events that a specific person is attending -pub fn (mut c CalendarDB) get_events_by_attendee(attendee String) []CalendarEvent { - [] -} - -// search_events_by_title searches for events with a specific title substring -pub fn (mut c CalendarDB) search_events_by_title(title String) []CalendarEvent { - [] -} - -// update_status updates the status of an event -pub fn (mut c CalendarDB) update_status(id u32, status String) CalendarEvent { - CalendarEvent {} -} - -// delete_by_caldav_uid removes an event by its CalDAV UID -pub fn (mut c CalendarDB) delete_by_caldav_uid(caldav_uid String) { -} - -``` \ No newline at end of file diff --git a/aiprompts/code/vfs.md b/aiprompts/code/vfs.md deleted file mode 100644 index fe9bb4d1..00000000 --- a/aiprompts/code/vfs.md +++ /dev/null @@ -1,26 +0,0 @@ - -create a module vfs_mail in @lib/vfs -check the interface as defined in @lib/vfs/interface.v and @metadata.v - -see example how a vfs is made in @lib/vfs/vfs_local - -create the vfs to represent mail objects in @lib/circles/dbs/core/mail_db.v - -mailbox propery on the Email object defines the path in the vfs -this mailbox property can be e.g. Draft/something/somethingelse - -in that dir show a subdir /id: -- which show the Email as a json underneith the ${email.id}.json - -in that dir show subdir /subject: -- which show the Email as a json underneith the name_fix(${email.envelope.subject}.json - -so basically we have 2 representations of the same mail in the vfs, both have the. json as content of the file - - - - - - - - diff --git a/aiprompts/readme.md b/aiprompts/documentor/generate_v_doc_readable_md_files.md similarity index 100% rename from aiprompts/readme.md rename to aiprompts/documentor/generate_v_doc_readable_md_files.md diff --git a/aiprompts/ui console chalk.md b/aiprompts/herolib_advanced/ui console chalk.md similarity index 100% rename from aiprompts/ui console chalk.md rename to aiprompts/herolib_advanced/ui console chalk.md diff --git a/aiprompts/herolib_core/cmdline_argument_parsing_example.vsh b/aiprompts/herolib_core/cmdline_argument_parsing_example.vsh new file mode 100644 index 00000000..be6cb3e1 --- /dev/null +++ b/aiprompts/herolib_core/cmdline_argument_parsing_example.vsh @@ -0,0 +1,24 @@ +#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run + +import os +import flag + +mut fp := flag.new_flag_parser(os.args) +fp.application('compile.vsh') +fp.version('v0.1.0') +fp.description('Compile hero binary in debug or production mode') +fp.skip_executable() + +prod_mode := fp.bool('prod', `p`, false, 'Build production version (optimized)') +help_requested := fp.bool('help', `h`, false, 'Show help message') + +if help_requested { + println(fp.usage()) + exit(0) +} + +additional_args := fp.finalize() or { + eprintln(err) + println(fp.usage()) + exit(1) +} \ No newline at end of file diff --git a/aiprompts/vshell example script instructions.md b/aiprompts/herolib_core/core_vshell.md similarity index 59% rename from aiprompts/vshell example script instructions.md rename to aiprompts/herolib_core/core_vshell.md index a45e7c41..99476e8e 100644 --- a/aiprompts/vshell example script instructions.md +++ b/aiprompts/herolib_core/core_vshell.md @@ -3,13 +3,10 @@ this is how we want example scripts to be, see the first line ```vlang -#!/usr/bin/env -S v -gc none -cc tcc -d use_openssl -enable-globals run +#!/usr/bin/env -S v -cg -gc none -cc tcc -d use_openssl -enable-globals run -import freeflowuniverse.herolib.installers.sysadmintools.daguserver +import freeflowuniverse.herolib... -mut ds := daguserver.get()! - -println(ds) ``` the files are in ~/code/github/freeflowuniverse/herolib/examples for herolib @@ -17,3 +14,4 @@ the files are in ~/code/github/freeflowuniverse/herolib/examples for herolib ## important instructions - never use fn main() in a .vsh script +- always use the top line as in example above diff --git a/aiprompts/herolib_core/osal_core.md b/aiprompts/herolib_core/osal_systems_library_core.md similarity index 100% rename from aiprompts/herolib_core/osal_core.md rename to aiprompts/herolib_core/osal_systems_library_core.md diff --git a/aiprompts/ui console.md b/aiprompts/herolib_core/ui console.md similarity index 100% rename from aiprompts/ui console.md rename to aiprompts/herolib_core/ui console.md diff --git a/aiprompts/starter/0_start_here.md b/aiprompts/herolib_start_here.md similarity index 59% rename from aiprompts/starter/0_start_here.md rename to aiprompts/herolib_start_here.md index f3c811b1..6eef01a0 100644 --- a/aiprompts/starter/0_start_here.md +++ b/aiprompts/herolib_start_here.md @@ -11,43 +11,13 @@ when I generate vlang scripts I will always use .vsh extension and use following as first line: ``` -#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run +#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run ``` - a .vsh is a v shell script and can be executed as is, no need to use v ... - in .vsh file there is no need for a main() function - these scripts can be used for examples or instruction scripts e.g. an installs script -## to do argument parsing use following examples - -```v -#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run - -import os -import flag - -mut fp := flag.new_flag_parser(os.args) -fp.application('compile.vsh') -fp.version('v0.1.0') -fp.description('Compile hero binary in debug or production mode') -fp.skip_executable() - -prod_mode := fp.bool('prod', `p`, false, 'Build production version (optimized)') -help_requested := fp.bool('help', `h`, false, 'Show help message') - -if help_requested { - println(fp.usage()) - exit(0) -} - -additional_args := fp.finalize() or { - eprintln(err) - println(fp.usage()) - exit(1) -} - -``` - ## when creating a test script @@ -58,3 +28,4 @@ vtest ~/code/github/freeflowuniverse/herolib/lib/osal/package_test.v ``` - use ~ so it works over all machines +- don't use 'v test', we have vtest as alternative diff --git a/aiprompts/httpconnection.md b/aiprompts/httpconnection.md deleted file mode 120000 index 216d8663..00000000 --- a/aiprompts/httpconnection.md +++ /dev/null @@ -1 +0,0 @@ -../lib/core/httpconnection/readme.md \ No newline at end of file diff --git a/aiprompts/osal.md b/aiprompts/osal.md deleted file mode 120000 index fa312835..00000000 --- a/aiprompts/osal.md +++ /dev/null @@ -1 +0,0 @@ -../lib/osal/readme.md \ No newline at end of file diff --git a/aiprompts/ourdb.md b/aiprompts/ourdb.md deleted file mode 120000 index 96b5e46e..00000000 --- a/aiprompts/ourdb.md +++ /dev/null @@ -1 +0,0 @@ -../lib/data/ourdb/README.md \ No newline at end of file diff --git a/aiprompts/ourtime.md b/aiprompts/ourtime.md deleted file mode 120000 index 259b1937..00000000 --- a/aiprompts/ourtime.md +++ /dev/null @@ -1 +0,0 @@ -../lib/data/ourtime/readme.md \ No newline at end of file diff --git a/aiprompts/starter/1_heroscript.md b/aiprompts/starter/1_heroscript.md deleted file mode 100644 index fa6fd90c..00000000 --- a/aiprompts/starter/1_heroscript.md +++ /dev/null @@ -1,78 +0,0 @@ -# HeroScript - -## Overview - -HeroScript is a simple, declarative scripting language designed to define workflows and execute commands in a structured manner. It follows a straightforward syntax where each action is prefixed with `!!`, indicating the actor and action name. - -## Example - -A basic HeroScript script for virtual machine management looks like this: - -```heroscript -!!vm.define name:'test_vm' cpu:4 - memory: '8GB' - storage: '100GB' - description: ' - A virtual machine configuration - with specific resources. - ' - -!!vm.start name:'test_vm' - -!!vm.disk_add - name: 'test_vm' - size: '50GB' - type: 'SSD' - -!!vm.delete - name: 'test_vm' - force: true -``` - -### Key Features - -- Every action starts with `!!`. - - The first part after `!!` is the actor (e.g., `vm`). - - The second part is the action name (e.g., `define`, `start`, `delete`). -- Multi-line values are supported (e.g., the `description` field). -- Lists are comma-separated where applicable and inside ''. -- If items one 1 line, then no space between name & argument e.g. name:'test_vm' - -## Parsing HeroScript - -Internally, HeroScript gets parsed into an action object with parameters. Each parameter follows a `key: value` format. - -### Parsing Example - -```heroscript -!!actor.action - 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' -``` - -### Parsing Details -- Each parameter follows a `key: value` format. -- Multi-line values (such as descriptions) support Markdown formatting. -- Comments can be added using `//`. -- Keys and values can have spaces, and values can be enclosed in single quotes. - diff --git a/aiprompts/starter/3_heroscript_vlang.md b/aiprompts/starter/3_heroscript_vlang.md deleted file mode 100644 index 2e7935e8..00000000 --- a/aiprompts/starter/3_heroscript_vlang.md +++ /dev/null @@ -1,267 +0,0 @@ - -## 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' - }] - } -``` \ No newline at end of file diff --git a/aiprompts/v_manual_advanced.md b/aiprompts/v_advanced/advanced_topics.md similarity index 100% rename from aiprompts/v_manual_advanced.md rename to aiprompts/v_advanced/advanced_topics.md diff --git a/aiprompts/html_parser.md b/aiprompts/v_advanced/html_parser.md similarity index 100% rename from aiprompts/html_parser.md rename to aiprompts/v_advanced/html_parser.md diff --git a/aiprompts/io.md b/aiprompts/v_advanced/io.md similarity index 100% rename from aiprompts/io.md rename to aiprompts/v_advanced/io.md diff --git a/aiprompts/net.md b/aiprompts/v_advanced/net.md similarity index 100% rename from aiprompts/net.md rename to aiprompts/v_advanced/net.md diff --git a/aiprompts/reflection.md b/aiprompts/v_advanced/reflection.md similarity index 100% rename from aiprompts/reflection.md rename to aiprompts/v_advanced/reflection.md diff --git a/aiprompts/regex.md b/aiprompts/v_advanced/regex.md similarity index 100% rename from aiprompts/regex.md rename to aiprompts/v_advanced/regex.md diff --git a/aiprompts/smtp.md b/aiprompts/v_advanced/smtp.md similarity index 100% rename from aiprompts/smtp.md rename to aiprompts/v_advanced/smtp.md diff --git a/aiprompts/time instructions.md b/aiprompts/v_advanced/time instructions.md similarity index 100% rename from aiprompts/time instructions.md rename to aiprompts/v_advanced/time instructions.md diff --git a/aiprompts/v_advanced/v_manual2.md b/aiprompts/v_advanced/v_manual2.md deleted file mode 100644 index e7581a35..00000000 --- a/aiprompts/v_advanced/v_manual2.md +++ /dev/null @@ -1,2009 +0,0 @@ - -## Modules - -Every file in the root of a folder is part of the same module. -Simple programs don't need to specify module name, in which case it defaults to 'main'. - -See [symbol visibility](#symbol-visibility), [Access modifiers](#access-modifiers). - -### Create modules - -V is a very modular language. Creating reusable modules is encouraged and is -quite easy to do. -To create a new module, create a directory with your module's name containing -.v files with code: - -```shell -cd ~/code/modules -mkdir mymodule -vim mymodule/myfile.v -``` - -```v failcompile -// myfile.v -module mymodule - -// To export a function we have to use `pub` -pub fn say_hi() { - println('hello from mymodule!') -} -``` -All items inside a module can be used between the files of a module regardless of whether or -not they are prefaced with the `pub` keyword. -```v failcompile -// myfile2.v -module mymodule - -pub fn say_hi_and_bye() { - say_hi() // from myfile.v - println('goodbye from mymodule') -} -``` - -You can now use `mymodule` in your code: - -```v failcompile -import mymodule - -fn main() { - mymodule.say_hi() - mymodule.say_hi_and_bye() -} -``` - -* Module names should be short, under 10 characters. -* Module names must use `snake_case`. -* Circular imports are not allowed. -* You can have as many .v files in a module as you want. -* You can create modules anywhere. -* All modules are compiled statically into a single executable. - -### Special considerations for project folders - -For the top level project folder (the one, compiled with `v .`), and *only* -that folder, you can have several .v files, that may be mentioning different modules -with `module main`, `module abc` etc - -This is to ease the prototyping workflow in that folder: -- you can start developing some new project with a single .v file -- split functionality as necessary to different .v files in the same folder -- when that makes logical sense to be further organised, put them into their own directory module. - -Note that in ordinary modules, all .v files must start with `module name_of_folder`. - -### `init` functions - -If you want a module to automatically call some setup/initialization code when it is imported, -you can define a module `init` function: - -```v -fn init() { - // your setup code here ... -} -``` - -The `init` function cannot be public - it will be called automatically by V, *just once*, no matter -how many times the module was imported in your program. This feature is particularly useful for -initializing a C library. - -### `cleanup` functions - -If you want a module to automatically call some cleanup/deinitialization code, when your program -ends, you can define a module `cleanup` function: - -```v -fn cleanup() { - // your deinitialisation code here ... -} -``` - -Just like the `init` function, the `cleanup` function for a module cannot be public - it will be -called automatically, when your program ends, once per module, even if the module was imported -transitively by other modules several times, in the reverse order of the init calls. - -## Type Declarations - -### Type aliases - -To define a new type `NewType` as an alias for `ExistingType`, -do `type NewType = ExistingType`.
-This is a special case of a [sum type](#sum-types) declaration. - -### Enums - -```v -enum Color as u8 { - red - green - blue -} - -mut color := Color.red -// V knows that `color` is a `Color`. No need to use `color = Color.green` here. -color = .green -println(color) // "green" -match color { - .red { println('the color was red') } - .green { println('the color was green') } - .blue { println('the color was blue') } -} -``` - -The enum type can be any integer type, but can be omitted, if it is `int`: `enum Color {`. - -Enum match must be exhaustive or have an `else` branch. -This ensures that if a new enum field is added, it's handled everywhere in the code. - -Enum fields cannot re-use reserved keywords. However, reserved keywords may be escaped -with an @. - -```v -enum Color { - @none - red - green - blue -} - -color := Color.@none -println(color) -``` - -Integers may be assigned to enum fields. - -```v -enum Grocery { - apple - orange = 5 - pear -} - -g1 := int(Grocery.apple) -g2 := int(Grocery.orange) -g3 := int(Grocery.pear) -println('Grocery IDs: ${g1}, ${g2}, ${g3}') -``` - -Output: `Grocery IDs: 0, 5, 6`. - -Operations are not allowed on enum variables; they must be explicitly cast to `int`. - -Enums can have methods, just like structs. - -```v -enum Cycle { - one - two - three -} - -fn (c Cycle) next() Cycle { - match c { - .one { - return .two - } - .two { - return .three - } - .three { - return .one - } - } -} - -mut c := Cycle.one -for _ in 0 .. 10 { - println(c) - c = c.next() -} -``` - -Output: - -``` -one -two -three -one -two -three -one -two -three -one -``` - -Enums can be created from string or integer value and converted into string - -```v -enum Cycle { - one - two = 2 - three -} - -// Create enum from value -println(Cycle.from(10) or { Cycle.three }) -println(Cycle.from('two')!) - -// Convert an enum value to a string -println(Cycle.one.str()) -``` - -Output: - -``` -three -two -one -``` - -### Function Types - -You can use type aliases for naming specific function signatures - for -example: - -```v -type Filter = fn (string) string -``` - -This works like any other type - for example, a function can accept an -argument of a function type: - -```v -type Filter = fn (string) string - -fn filter(s string, f Filter) string { - return f(s) -} -``` - -V has duck-typing, so functions don't need to declare compatibility with -a function type - they just have to be compatible: - -```v -fn uppercase(s string) string { - return s.to_upper() -} - -// now `uppercase` can be used everywhere where Filter is expected -``` - -Compatible functions can also be explicitly cast to a function type: - -```v oksyntax -my_filter := Filter(uppercase) -``` - -The cast here is purely informational - again, duck-typing means that the -resulting type is the same without an explicit cast: - -```v oksyntax -my_filter := uppercase -``` - -You can pass the assigned function as an argument: - -```v oksyntax -println(filter('Hello world', my_filter)) // prints `HELLO WORLD` -``` - -And you could of course have passed it directly as well, without using a -local variable: - -```v oksyntax -println(filter('Hello world', uppercase)) -``` - -And this works with anonymous functions as well: - -```v oksyntax -println(filter('Hello world', fn (s string) string { - return s.to_upper() -})) -``` - -You can see the complete -[example here](https://github.com/vlang/v/tree/master/examples/function_types.v). - -### Interfaces - -```v -// interface-example.1 -struct Dog { - breed string -} - -fn (d Dog) speak() string { - return 'woof' -} - -struct Cat { - breed string -} - -fn (c Cat) speak() string { - return 'meow' -} - -// unlike Go, but like TypeScript, V's interfaces can define both fields and methods. -interface Speaker { - breed string - speak() string -} - -fn main() { - dog := Dog{'Leonberger'} - cat := Cat{'Siamese'} - - mut arr := []Speaker{} - arr << dog - arr << cat - for item in arr { - println('a ${item.breed} says: ${item.speak()}') - } -} -``` - -#### Implement an interface - -A type implements an interface by implementing its methods and fields. -There is no explicit declaration of intent, no "implements" keyword. - -An interface can have a `mut:` section. Implementing types will need -to have a `mut` receiver, for methods declared in the `mut:` section -of an interface. - -```v -// interface-example.2 -module main - -interface Foo { - write(string) string -} - -// => the method signature of a type, implementing interface Foo should be: -// `fn (s Type) write(a string) string` - -interface Bar { -mut: - write(string) string -} - -// => the method signature of a type, implementing interface Bar should be: -// `fn (mut s Type) write(a string) string` - -struct MyStruct {} - -// MyStruct implements the interface Foo, but *not* interface Bar -fn (s MyStruct) write(a string) string { - return a -} - -fn main() { - s1 := MyStruct{} - fn1(s1) - // fn2(s1) -> compile error, since MyStruct does not implement Bar -} - -fn fn1(s Foo) { - println(s.write('Foo')) -} - -// fn fn2(s Bar) { // does not match -// println(s.write('Foo')) -// } -``` - -#### Casting an interface - -We can test the underlying type of an interface using dynamic cast operators. -> [!NOTE] -> Dynamic cast converts variable `s` into a pointer inside the `if` statements in this example: - -```v oksyntax -// interface-example.3 (continued from interface-example.1) -interface Something {} - -fn announce(s Something) { - if s is Dog { - println('a ${s.breed} dog') // `s` is automatically cast to `Dog` (smart cast) - } else if s is Cat { - println('a cat speaks ${s.speak()}') - } else { - println('something else') - } -} - -fn main() { - dog := Dog{'Leonberger'} - cat := Cat{'Siamese'} - announce(dog) - announce(cat) -} -``` - -```v -// interface-example.4 -interface IFoo { - foo() -} - -interface IBar { - bar() -} - -// implements only IFoo -struct SFoo {} - -fn (sf SFoo) foo() {} - -// implements both IFoo and IBar -struct SFooBar {} - -fn (sfb SFooBar) foo() {} - -fn (sfb SFooBar) bar() { - dump('This implements IBar') -} - -fn main() { - mut arr := []IFoo{} - arr << SFoo{} - arr << SFooBar{} - - for a in arr { - dump(a) - // In order to execute instances that implements IBar. - if a is IBar { - a.bar() - } - } -} -``` - -For more information, see [Dynamic casts](#dynamic-casts). - -#### Interface method definitions - -Also unlike Go, an interface can have its own methods, similar to how -structs can have their methods. These 'interface methods' do not have -to be implemented, by structs which implement that interface. -They are just a convenient way to write `i.some_function()` instead of -`some_function(i)`, similar to how struct methods can be looked at, as -a convenience for writing `s.xyz()` instead of `xyz(s)`. - -> [!NOTE] -> This feature is NOT a "default implementation" like in C#. - -For example, if a struct `cat` is wrapped in an interface `a`, that has -implemented a method with the same name `speak`, as a method implemented by -the struct, and you do `a.speak()`, *only* the interface method is called: - -```v -interface Adoptable {} - -fn (a Adoptable) speak() string { - return 'adopt me!' -} - -struct Cat {} - -fn (c Cat) speak() string { - return 'meow!' -} - -struct Dog {} - -fn main() { - cat := Cat{} - assert dump(cat.speak()) == 'meow!' - - a := Adoptable(cat) - assert dump(a.speak()) == 'adopt me!' // call Adoptable's `speak` - if a is Cat { - // Inside this `if` however, V knows that `a` is not just any - // kind of Adoptable, but actually a Cat, so it will use the - // Cat `speak`, NOT the Adoptable `speak`: - dump(a.speak()) // meow! - } - - b := Adoptable(Dog{}) - assert dump(b.speak()) == 'adopt me!' // call Adoptable's `speak` - // if b is Dog { - // dump(b.speak()) // error: unknown method or field: Dog.speak - // } -} -``` - -#### Embedded interface - -Interfaces support embedding, just like structs: - -```v -pub interface Reader { -mut: - read(mut buf []u8) ?int -} - -pub interface Writer { -mut: - write(buf []u8) ?int -} - -// ReaderWriter embeds both Reader and Writer. -// The effect is the same as copy/pasting all of the -// Reader and all of the Writer methods/fields into -// ReaderWriter. -pub interface ReaderWriter { - Reader - Writer -} -``` - -### Sum types - -A sum type instance can hold a value of several different types. Use the `type` -keyword to declare a sum type: - -```v -struct Moon {} - -struct Mars {} - -struct Venus {} - -type World = Mars | Moon | Venus - -sum := World(Moon{}) -assert sum.type_name() == 'Moon' -println(sum) -``` - -The built-in method `type_name` returns the name of the currently held -type. - -With sum types you could build recursive structures and write concise but powerful code on them. - -```v -// V's binary tree -struct Empty {} - -struct Node { - value f64 - left Tree - right Tree -} - -type Tree = Empty | Node - -// sum up all node values - -fn sum(tree Tree) f64 { - return match tree { - Empty { 0 } - Node { tree.value + sum(tree.left) + sum(tree.right) } - } -} - -fn main() { - left := Node{0.2, Empty{}, Empty{}} - right := Node{0.3, Empty{}, Node{0.4, Empty{}, Empty{}}} - tree := Node{0.5, left, right} - println(sum(tree)) // 0.2 + 0.3 + 0.4 + 0.5 = 1.4 -} -``` - -#### Dynamic casts - -To check whether a sum type instance holds a certain type, use `sum is Type`. -To cast a sum type to one of its variants you can use `sum as Type`: - -```v -struct Moon {} - -struct Mars {} - -struct Venus {} - -type World = Mars | Moon | Venus - -fn (m Mars) dust_storm() bool { - return true -} - -fn main() { - mut w := World(Moon{}) - assert w is Moon - w = Mars{} - // use `as` to access the Mars instance - mars := w as Mars - if mars.dust_storm() { - println('bad weather!') - } -} -``` - -`as` will panic if `w` doesn't hold a `Mars` instance. -A safer way is to use a smart cast. - -#### Smart casting - -```v oksyntax -if w is Mars { - assert typeof(w).name == 'Mars' - if w.dust_storm() { - println('bad weather!') - } -} -``` - -`w` has type `Mars` inside the body of the `if` statement. This is -known as *flow-sensitive typing*. -If `w` is a mutable identifier, it would be unsafe if the compiler smart casts it without a warning. -That's why you have to declare a `mut` before the `is` expression: - -```v ignore -if mut w is Mars { - assert typeof(w).name == 'Mars' - if w.dust_storm() { - println('bad weather!') - } -} -``` - -Otherwise `w` would keep its original type. -> This works for both simple variables and complex expressions like `user.name` - -#### Matching sum types - -You can also use `match` to determine the variant: - -```v -struct Moon {} - -struct Mars {} - -struct Venus {} - -type World = Mars | Moon | Venus - -fn open_parachutes(n int) { - println(n) -} - -fn land(w World) { - match w { - Moon {} // no atmosphere - Mars { - // light atmosphere - open_parachutes(3) - } - Venus { - // heavy atmosphere - open_parachutes(1) - } - } -} -``` - -`match` must have a pattern for each variant or have an `else` branch. - -```v ignore -struct Moon {} -struct Mars {} -struct Venus {} - -type World = Moon | Mars | Venus - -fn (m Moon) moon_walk() {} -fn (m Mars) shiver() {} -fn (v Venus) sweat() {} - -fn pass_time(w World) { - match w { - // using the shadowed match variable, in this case `w` (smart cast) - Moon { w.moon_walk() } - Mars { w.shiver() } - else {} - } -} -``` - -### Option/Result types and error handling - -Option types are for types which may represent `none`. Result types may -represent an error returned from a function. - -`Option` types are declared by prepending `?` to the type name: `?Type`. -`Result` types use `!`: `!Type`. - -```v -struct User { - id int - name string -} - -struct Repo { - users []User -} - -fn (r Repo) find_user_by_id(id int) !User { - for user in r.users { - if user.id == id { - // V automatically wraps this into a result or option type - return user - } - } - return error('User ${id} not found') -} - -// A version of the function using an option -fn (r Repo) find_user_by_id2(id int) ?User { - for user in r.users { - if user.id == id { - return user - } - } - return none -} - -fn main() { - repo := Repo{ - users: [User{1, 'Andrew'}, User{2, 'Bob'}, User{10, 'Charles'}] - } - user := repo.find_user_by_id(10) or { // Option/Result types must be handled by `or` blocks - println(err) - return - } - println(user.id) // "10" - println(user.name) // "Charles" - - user2 := repo.find_user_by_id2(10) or { return } - - // To create an Option var directly: - my_optional_int := ?int(none) - my_optional_string := ?string(none) - my_optional_user := ?User(none) -} -``` - -V used to combine `Option` and `Result` into one type, now they are separate. - -The amount of work required to "upgrade" a function to an option/result function is minimal; -you have to add a `?` or `!` to the return type and return `none` or an error (respectively) -when something goes wrong. - -This is the primary mechanism for error handling in V. They are still values, like in Go, -but the advantage is that errors can't be unhandled, and handling them is a lot less verbose. -Unlike other languages, V does not handle exceptions with `throw/try/catch` blocks. - -`err` is defined inside an `or` block and is set to the string message passed -to the `error()` function. - -```v oksyntax -user := repo.find_user_by_id(7) or { - println(err) // "User 7 not found" - return -} -``` - -#### Options/results when returning multiple values - -Only one `Option` or `Result` is allowed to be returned from a function. It is -possible to return multiple values and still signal an error. - -```v -fn multireturn(v int) !(int, int) { - if v < 0 { - return error('must be positive') - } - return v, v * v -} -``` - -#### Handling options/results - -There are four ways of handling an option/result. The first method is to -propagate the error: - -```v -import net.http - -fn f(url string) !string { - resp := http.get(url)! - return resp.body -} -``` - -`http.get` returns `!http.Response`. Because `!` follows the call, the -error will be propagated to the caller of `f`. When using `?` after a -function call producing an option, the enclosing function must return -an option as well. If error propagation is used in the `main()` -function it will `panic` instead, since the error cannot be propagated -any further. - -The body of `f` is essentially a condensed version of: - -```v ignore - resp := http.get(url) or { return err } - return resp.body -``` - ---- -The second method is to break from execution early: - -```v oksyntax -user := repo.find_user_by_id(7) or { return } -``` - -Here, you can either call `panic()` or `exit()`, which will stop the execution of the -entire program, or use a control flow statement (`return`, `break`, `continue`, etc) -to break from the current block. - -> [!NOTE] -> `break` and `continue` can only be used inside a `for` loop. - -V does not have a way to forcibly "unwrap" an option (as other languages do, -for instance Rust's `unwrap()` or Swift's `!`). To do this, use `or { panic(err) }` instead. - ---- -The third method is to provide a default value at the end of the `or` block. -In case of an error, that value would be assigned instead, -so it must have the same type as the content of the `Option` being handled. - -```v -fn do_something(s string) !string { - if s == 'foo' { - return 'foo' - } - return error('invalid string') -} - -a := do_something('foo') or { 'default' } // a will be 'foo' -b := do_something('bar') or { 'default' } // b will be 'default' -println(a) -println(b) -``` - ---- -The fourth method is to use `if` unwrapping: - -```v -import net.http - -if resp := http.get('https://google.com') { - println(resp.body) // resp is a http.Response, not an option -} else { - println(err) -} -``` - -Above, `http.get` returns a `!http.Response`. `resp` is only in scope for the first -`if` branch. `err` is only in scope for the `else` branch. - -### Custom error types - -V gives you the ability to define custom error types through the `IError` interface. -The interface requires two methods: `msg() string` and `code() int`. Every type that -implements these methods can be used as an error. - -When defining a custom error type it is recommended to embed the builtin `Error` default -implementation. This provides an empty default implementation for both required methods, -so you only have to implement what you really need, and may provide additional utility -functions in the future. - -```v -struct PathError { - Error - path string -} - -fn (err PathError) msg() string { - return 'Failed to open path: ${err.path}' -} - -fn try_open(path string) ! { - // V automatically casts this to IError - return PathError{ - path: path - } -} - -fn main() { - try_open('/tmp') or { panic(err) } -} -``` - -### Generics - -```v wip - -struct Repo[T] { - db DB -} - -struct User { - id int - name string -} - -struct Post { - id int - user_id int - title string - body string -} - -fn new_repo[T](db DB) Repo[T] { - return Repo[T]{db: db} -} - -// This is a generic function. V will generate it for every type it's used with. -fn (r Repo[T]) find_by_id(id int) ?T { - table_name := T.name // in this example getting the name of the type gives us the table name - return r.db.query_one[T]('select * from ${table_name} where id = ?', id) -} - -db := new_db() -users_repo := new_repo[User](db) // returns Repo[User] -posts_repo := new_repo[Post](db) // returns Repo[Post] -user := users_repo.find_by_id(1)? // find_by_id[User] -post := posts_repo.find_by_id(1)? // find_by_id[Post] -``` - -Currently generic function definitions must declare their type parameters, but in -future V will infer generic type parameters from single-letter type names in -runtime parameter types. This is why `find_by_id` can omit `[T]`, because the -receiver argument `r` uses a generic type `T`. - -Another example: - -```v -fn compare[T](a T, b T) int { - if a < b { - return -1 - } - if a > b { - return 1 - } - return 0 -} - -// compare[int] -println(compare(1, 0)) // Outputs: 1 -println(compare(1, 1)) // 0 -println(compare(1, 2)) // -1 -// compare[string] -println(compare('1', '0')) // Outputs: 1 -println(compare('1', '1')) // 0 -println(compare('1', '2')) // -1 -// compare[f64] -println(compare(1.1, 1.0)) // Outputs: 1 -println(compare(1.1, 1.1)) // 0 -println(compare(1.1, 1.2)) // -1 -``` - -## Concurrency - -### Spawning Concurrent Tasks - -V's model of concurrency is going to be very similar to Go's. -For now, `spawn foo()` runs `foo()` concurrently in a different thread: - -```v -import math - -fn p(a f64, b f64) { // ordinary function without return value - c := math.sqrt(a * a + b * b) - println(c) -} - -fn main() { - spawn p(3, 4) - // p will be run in parallel thread - // It can also be written as follows - // spawn fn (a f64, b f64) { - // c := math.sqrt(a * a + b * b) - // println(c) - // }(3, 4) -} -``` - -> [!NOTE] -> Threads rely on the machine's CPU (number of cores/threads). -> Be aware that OS threads spawned with `spawn` -> have limitations in regard to concurrency, -> including resource overhead and scalability issues, -> and might affect performance in cases of high thread count. - -There's also a `go` keyword. Right now `go foo()` will be automatically renamed via vfmt -to `spawn foo()`, and there will be a way to launch a coroutine with `go` (a lightweight -thread managed by the runtime). - -Sometimes it is necessary to wait until a parallel thread has finished. This can -be done by assigning a *handle* to the started thread and calling the `wait()` method -to this handle later: - -```v -import math - -fn p(a f64, b f64) { // ordinary function without return value - c := math.sqrt(a * a + b * b) - println(c) // prints `5` -} - -fn main() { - h := spawn p(3, 4) - // p() runs in parallel thread - h.wait() - // p() has definitely finished -} -``` - -This approach can also be used to get a return value from a function that is run in a -parallel thread. There is no need to modify the function itself to be able to call it -concurrently. - -```v -import math { sqrt } - -fn get_hypot(a f64, b f64) f64 { // ordinary function returning a value - c := sqrt(a * a + b * b) - return c -} - -fn main() { - g := spawn get_hypot(54.06, 2.08) // spawn thread and get handle to it - h1 := get_hypot(2.32, 16.74) // do some other calculation here - h2 := g.wait() // get result from spawned thread - println('Results: ${h1}, ${h2}') // prints `Results: 16.9, 54.1` -} -``` - -If there is a large number of tasks, it might be easier to manage them -using an array of threads. - -```v -import time - -fn task(id int, duration int) { - println('task ${id} begin') - time.sleep(duration * time.millisecond) - println('task ${id} end') -} - -fn main() { - mut threads := []thread{} - threads << spawn task(1, 500) - threads << spawn task(2, 900) - threads << spawn task(3, 100) - threads.wait() - println('done') -} - -// Output: -// task 1 begin -// task 2 begin -// task 3 begin -// task 3 end -// task 1 end -// task 2 end -// done -``` - -Additionally for threads that return the same type, calling `wait()` -on the thread array will return all computed values. - -```v -fn expensive_computing(i int) int { - return i * i -} - -fn main() { - mut threads := []thread int{} - for i in 1 .. 10 { - threads << spawn expensive_computing(i) - } - // Join all tasks - r := threads.wait() - println('All jobs finished: ${r}') -} - -// Output: All jobs finished: [1, 4, 9, 16, 25, 36, 49, 64, 81] -``` - -### Channels - -Channels are the preferred way to communicate between threads. V's channels work basically like -those in Go. You can push objects into a channel on one end and pop objects from the other end. -Channels can be buffered or unbuffered and it is possible to `select` from multiple channels. - -#### Syntax and Usage - -Channels have the type `chan objtype`. An optional buffer length can be specified as the `cap` field -in the declaration: - -```v -ch := chan int{} // unbuffered - "synchronous" -ch2 := chan f64{cap: 100} // buffer length 100 -``` - -Channels do not have to be declared as `mut`. The buffer length is not part of the type but -a field of the individual channel object. Channels can be passed to threads like normal -variables: - -```v -fn f(ch chan int) { - // ... -} - -fn main() { - ch := chan int{} - spawn f(ch) - // ... -} -``` - -Objects can be pushed to channels using the arrow operator. The same operator can be used to -pop objects from the other end: - -```v -// make buffered channels so pushing does not block (if there is room in the buffer) -ch := chan int{cap: 1} -ch2 := chan f64{cap: 1} -n := 5 -// push -ch <- n -ch2 <- 7.3 -mut y := f64(0.0) -m := <-ch // pop creating new variable -y = <-ch2 // pop into existing variable -``` - -A channel can be closed to indicate that no further objects can be pushed. Any attempt -to do so will then result in a runtime panic (with the exception of `select` and -`try_push()` - see below). Attempts to pop will return immediately if the -associated channel has been closed and the buffer is empty. This situation can be -handled using an `or {}` block (see [Handling options/results](#handling-optionsresults)). - -```v wip -ch := chan int{} -ch2 := chan f64{} -// ... -ch.close() -// ... -m := <-ch or { - println('channel has been closed') -} - -// propagate error -y := <-ch2 ? -``` - -#### Channel Select - -The `select` command allows monitoring several channels at the same time -without noticeable CPU load. It consists of a list of possible transfers and associated branches -of statements - similar to the [match](#match) command: - -```v -import time - -fn main() { - ch := chan f64{} - ch2 := chan f64{} - ch3 := chan f64{} - mut b := 0.0 - c := 1.0 - // ... setup spawn threads that will send on ch/ch2 - spawn fn (the_channel chan f64) { - time.sleep(5 * time.millisecond) - the_channel <- 1.0 - }(ch) - spawn fn (the_channel chan f64) { - time.sleep(1 * time.millisecond) - the_channel <- 1.0 - }(ch2) - spawn fn (the_channel chan f64) { - _ := <-the_channel - }(ch3) - - select { - a := <-ch { - // do something with `a` - eprintln('> a: ${a}') - } - b = <-ch2 { - // do something with predeclared variable `b` - eprintln('> b: ${b}') - } - ch3 <- c { - // do something if `c` was sent - time.sleep(5 * time.millisecond) - eprintln('> c: ${c} was send on channel ch3') - } - 500 * time.millisecond { - // do something if no channel has become ready within 0.5s - eprintln('> more than 0.5s passed without a channel being ready') - } - } - eprintln('> done') -} -``` - -The timeout branch is optional. If it is absent `select` waits for an unlimited amount of time. -It is also possible to proceed immediately if no channel is ready in the moment `select` is called -by adding an `else { ... }` branch. `else` and `` are mutually exclusive. - -The `select` command can be used as an *expression* of type `bool` -that becomes `false` if all channels are closed: - -```v wip -if select { - ch <- a { - // ... - } -} { - // channel was open -} else { - // channel is closed -} -``` - -#### Special Channel Features - -For special purposes there are some builtin fields and methods: - -```v -struct Abc { - x int -} - -a := 2.13 -ch := chan f64{} -res := ch.try_push(a) // try to perform `ch <- a` -println(res) -l := ch.len // number of elements in queue -c := ch.cap // maximum queue length -is_closed := ch.closed // bool flag - has `ch` been closed -println(l) -println(c) -mut b := Abc{} -ch2 := chan Abc{} -res2 := ch2.try_pop(mut b) // try to perform `b = <-ch2` -``` - -The `try_push/pop()` methods will return immediately with one of the results -`.success`, `.not_ready` or `.closed` - dependent on whether the object has been transferred or -the reason why not. -Usage of these methods and fields in production is not recommended - -algorithms based on them are often subject to race conditions. Especially `.len` and -`.closed` should not be used to make decisions. -Use `or` branches, error propagation or `select` instead (see [Syntax and Usage](#syntax-and-usage) -and [Channel Select](#channel-select) above). - -### Shared Objects - -Data can be exchanged between a thread and the calling thread via a shared variable. -Such variables should be created as `shared` and passed to the thread as such, too. -The underlying `struct` contains a hidden *mutex* that allows locking concurrent access -using `rlock` for read-only and `lock` for read/write access. - -```v -struct St { -mut: - x int // data to be shared -} - -fn (shared b St) g() { - lock b { - // read/modify/write b.x - } -} - -fn main() { - shared a := St{ - x: 10 - } - spawn a.g() - // ... - rlock a { - // read a.x - } -} -``` - -Shared variables must be structs, arrays or maps. - -## JSON - -Because of the ubiquitous nature of JSON, support for it is built directly into V. - -V generates code for JSON encoding and decoding. -No runtime reflection is used. This results in much better performance. - -### Decoding JSON - -```v -import json - -struct Foo { - x int -} - -struct User { - // Adding a [required] attribute will make decoding fail, if that - // field is not present in the input. - // If a field is not [required], but is missing, it will be assumed - // to have its default value, like 0 for numbers, or '' for strings, - // and decoding will not fail. - name string @[required] - age int - // Use the `skip` attribute to skip certain fields - foo Foo @[skip] - // If the field name is different in JSON, it can be specified - last_name string @[json: lastName] -} - -data := '{ "name": "Frodo", "lastName": "Baggins", "age": 25 }' -user := json.decode(User, data) or { - eprintln('Failed to decode json, error: ${err}') - return -} -println(user.name) -println(user.last_name) -println(user.age) -// You can also decode JSON arrays: -sfoos := '[{"x":123},{"x":456}]' -foos := json.decode([]Foo, sfoos)! -println(foos[0].x) -println(foos[1].x) -``` - -The `json.decode` function takes two arguments: -the first is the type into which the JSON value should be decoded and -the second is a string containing the JSON data. - -### Encoding JSON - -```v -import json - -struct User { - name string - score i64 -} - -mut data := map[string]int{} -user := &User{ - name: 'Pierre' - score: 1024 -} - -data['x'] = 42 -data['y'] = 360 - -println(json.encode(data)) // {"x":42,"y":360} -println(json.encode(user)) // {"name":"Pierre","score":1024} -``` - -The json module also supports anonymous struct fields, which helps with complex JSON apis with lots -of levels. - -## Testing - -### Asserts - -```v -fn foo(mut v []int) { - v[0] = 1 -} - -mut v := [20] -foo(mut v) -assert v[0] < 4 -``` - -An `assert` statement checks that its expression evaluates to `true`. If an assert fails, -the program will usually abort. Asserts should only be used to detect programming errors. When an -assert fails it is reported to *stderr*, and the values on each side of a comparison operator -(such as `<`, `==`) will be printed when possible. This is useful to easily find an -unexpected value. Assert statements can be used in any function, not just test ones, -which is handy when developing new functionality, to keep your invariants in check. - -> [!NOTE] -> All `assert` statements are *removed*, when you compile your program with the `-prod` flag. - -### Asserts with an extra message - -This form of the `assert` statement, will print the extra message when it fails. Note that -you can use any string expression there - string literals, functions returning a string, -strings that interpolate variables, etc. - -```v -fn test_assertion_with_extra_message_failure() { - for i in 0 .. 100 { - assert i * 2 - 45 < 75 + 10, 'assertion failed for i: ${i}' - } -} -``` - -### Asserts that do not abort your program - -When initially prototyping functionality and tests, it is sometimes desirable to -have asserts that do not stop the program, but just print their failures. That can -be achieved by tagging your assert containing functions with an `[assert_continues]` -tag, for example running this program: - -```v -@[assert_continues] -fn abc(ii int) { - assert ii == 2 -} - -for i in 0 .. 4 { - abc(i) -} -``` - -... will produce this output: - -``` -assert_continues_example.v:3: FAIL: fn main.abc: assert ii == 2 - left value: ii = 0 - right value: 2 -assert_continues_example.v:3: FAIL: fn main.abc: assert ii == 2 - left value: ii = 1 - right value: 2 -assert_continues_example.v:3: FAIL: fn main.abc: assert ii == 2 - left value: ii = 3 - right value: 2 -``` - -> [!NOTE] -> V also supports a command line flag `-assert continues`, which will change the -> behaviour of all asserts globally, as if you had tagged every function with `[assert_continues]`. - -### Test files - -```v -// hello.v -module main - -fn hello() string { - return 'Hello world' -} - -fn main() { - println(hello()) -} -``` - -```v failcompile -// hello_test.v -module main - -fn test_hello() { - assert hello() == 'Hello world' -} -``` - -To run the test file above, use `v hello_test.v`. This will check that the function `hello` is -producing the correct output. V executes all test functions in the file. - -> [!NOTE] -> All `_test.v` files (both external and internal ones), are compiled as *separate programs*. -> In other words, you may have as many `_test.v` files, and tests in them as you like, they will -> not affect the compilation of your other code in `.v` files normally at all, but only when you -> do explicitly `v file_test.v` or `v test .`. - -* All test functions have to be inside a test file whose name ends in `_test.v`. -* Test function names must begin with `test_` to mark them for execution. -* Normal functions can also be defined in test files, and should be called manually. Other - symbols can also be defined in test files e.g. types. -* There are two kinds of tests: external and internal. -* Internal tests must *declare* their module, just like all other .v - files from the same module. Internal tests can even call private functions in - the same module. -* External tests must *import* the modules which they test. They do not - have access to the private functions/types of the modules. They can test only - the external/public API that a module provides. - -In the example above, `test_hello` is an internal test that can call -the private function `hello()` because `hello_test.v` has `module main`, -just like `hello.v`, i.e. both are part of the same module. Note also that -since `module main` is a regular module like the others, internal tests can -be used to test private functions in your main program .v files too. - -You can also define these special test functions in a test file: - -* `testsuite_begin` which will be run *before* all other test functions. -* `testsuite_end` which will be run *after* all other test functions. - -If a test function has an error return type, any propagated errors will fail the test: - -```v -import strconv - -fn test_atoi() ! { - assert strconv.atoi('1')! == 1 - assert strconv.atoi('one')! == 1 // test will fail -} -``` - -### Running tests - -To run test functions in an individual test file, use `v foo_test.v`. - -To test an entire module, use `v test mymodule`. You can also use `v test .` to test -everything inside your current folder (and subfolders). You can pass the `-stats` -option to see more details about the individual tests run. - -You can put additional test data, including .v source files in a folder, named -`testdata`, right next to your _test.v files. V's test framework will *ignore* -such folders, while scanning for tests to run. This is useful, if you want to -put .v files with invalid V source code, or other tests, including known -failing ones, that should be run in a specific way/options by a parent _test.v -file. - -> [!NOTE] -> The path to the V compiler, is available through @VEXE, so a _test.v -> file, can easily run *other* test files like this: - -```v oksyntax -import os - -fn test_subtest() { - res := os.execute('${os.quoted_path(@VEXE)} other_test.v') - assert res.exit_code == 1 - assert res.output.contains('other_test.v does not exist') -} -``` - -## Memory management - -V avoids doing unnecessary allocations in the first place by using value types, -string buffers, promoting a simple abstraction-free code style. - -There are 4 ways to manage memory in V. - -The default is a minimal and a well performing tracing GC. - -The second way is autofree, it can be enabled with `-autofree`. It takes care of most objects -(~90-100%): the compiler inserts necessary free calls automatically during compilation. -Remaining small percentage of objects is freed via GC. The developer doesn't need to change -anything in their code. "It just works", like in Python, Go, or Java, except there's no -heavy GC tracing everything or expensive RC for each object. - -For developers willing to have more low level control, memory can be managed manually with -`-gc none`. - -Arena allocation is available via v `-prealloc`. - -### Control - -You can take advantage of V's autofree engine and define a `free()` method on custom -data types: - -```v -struct MyType {} - -@[unsafe] -fn (data &MyType) free() { - // ... -} -``` - -Just as the compiler frees C data types with C's `free()`, it will statically insert -`free()` calls for your data type at the end of each variable's lifetime. - -Autofree can be enabled with an `-autofree` flag. - -For developers willing to have more low level control, autofree can be disabled with -`-manualfree`, or by adding a `[manualfree]` on each function that wants to manage its -memory manually. (See [attributes](#attributes)). - -> [!NOTE] -> Autofree is still WIP. Until it stabilises and becomes the default, please -> avoid using it. Right now allocations are handled by a minimal and well performing GC -> until V's autofree engine is production ready. - -**Examples** - -```v -import strings - -fn draw_text(s string, x int, y int) { - // ... -} - -fn draw_scene() { - // ... - name1 := 'abc' - name2 := 'def ghi' - draw_text('hello ${name1}', 10, 10) - draw_text('hello ${name2}', 100, 10) - draw_text(strings.repeat(`X`, 10000), 10, 50) - // ... -} -``` - -The strings don't escape `draw_text`, so they are cleaned up when -the function exits. - -In fact, with the `-prealloc` flag, the first two calls won't result in any allocations at all. -These two strings are small, so V will use a preallocated buffer for them. - -```v -struct User { - name string -} - -fn test() []int { - number := 7 // stack variable - user := User{} // struct allocated on stack - numbers := [1, 2, 3] // array allocated on heap, will be freed as the function exits - println(number) - println(user) - println(numbers) - numbers2 := [4, 5, 6] // array that's being returned, won't be freed here - return numbers2 -} -``` - -### Stack and Heap - -#### Stack and Heap Basics - -Like with most other programming languages there are two locations where data can -be stored: - -* The *stack* allows fast allocations with almost zero administrative overhead. The - stack grows and shrinks with the function call depth – so every called - function has its stack segment that remains valid until the function returns. - No freeing is necessary, however, this also means that a reference to a stack - object becomes invalid on function return. Furthermore stack space is - limited (typically to a few Megabytes per thread). -* The *heap* is a large memory area (typically some Gigabytes) that is administrated - by the operating system. Heap objects are allocated and freed by special function - calls that delegate the administrative tasks to the OS. This means that they can - remain valid across several function calls, however, the administration is - expensive. - -#### V's default approach - -Due to performance considerations V tries to put objects on the stack if possible -but allocates them on the heap when obviously necessary. Example: - -```v -struct MyStruct { - n int -} - -struct RefStruct { - r &MyStruct -} - -fn main() { - q, w := f() - println('q: ${q.r.n}, w: ${w.n}') -} - -fn f() (RefStruct, &MyStruct) { - a := MyStruct{ - n: 1 - } - b := MyStruct{ - n: 2 - } - c := MyStruct{ - n: 3 - } - e := RefStruct{ - r: &b - } - x := a.n + c.n - println('x: ${x}') - return e, &c -} -``` - -Here `a` is stored on the stack since its address never leaves the function `f()`. -However a reference to `b` is part of `e` which is returned. Also a reference to -`c` is returned. For this reason `b` and `c` will be heap allocated. - -Things become less obvious when a reference to an object is passed as a function argument: - -```v -struct MyStruct { -mut: - n int -} - -fn main() { - mut q := MyStruct{ - n: 7 - } - w := MyStruct{ - n: 13 - } - x := q.f(&w) // references of `q` and `w` are passed - println('q: ${q}\nx: ${x}') -} - -fn (mut a MyStruct) f(b &MyStruct) int { - a.n += b.n - x := a.n * b.n - return x -} -``` - -Here the call `q.f(&w)` passes references to `q` and `w` because `a` is -`mut` and `b` is of type `&MyStruct` in `f()`'s declaration, so technically -these references are leaving `main()`. However the *lifetime* of these -references lies inside the scope of `main()` so `q` and `w` are allocated -on the stack. - -#### Manual Control for Stack and Heap - -In the last example the V compiler could put `q` and `w` on the stack -because it assumed that in the call `q.f(&w)` these references were only -used for reading and modifying the referred values – and not to pass the -references themselves somewhere else. This can be seen in a way that the -references to `q` and `w` are only *borrowed* to `f()`. - -Things become different if `f()` is doing something with a reference itself: - -```v -struct RefStruct { -mut: - r &MyStruct -} - -// see discussion below -@[heap] -struct MyStruct { - n int -} - -fn main() { - mut m := MyStruct{} - mut r := RefStruct{ - r: &m - } - r.g() - println('r: ${r}') -} - -fn (mut r RefStruct) g() { - s := MyStruct{ - n: 7 - } - r.f(&s) // reference to `s` inside `r` is passed back to `main() ` -} - -fn (mut r RefStruct) f(s &MyStruct) { - r.r = s // would trigger error without `[heap]` -} -``` - -Here `f()` looks quite innocent but is doing nasty things – it inserts a -reference to `s` into `r`. The problem with this is that `s` lives only as long -as `g()` is running but `r` is used in `main()` after that. For this reason -the compiler would complain about the assignment in `f()` because `s` *"might -refer to an object stored on stack"*. The assumption made in `g()` that the call -`r.f(&s)` would only borrow the reference to `s` is wrong. - -A solution to this dilemma is the `[heap]` [attribute](#attributes) at the declaration of -`struct MyStruct`. It instructs the compiler to *always* allocate `MyStruct`-objects -on the heap. This way the reference to `s` remains valid even after `g()` returns. -The compiler takes into consideration that `MyStruct` objects are always heap -allocated when checking `f()` and allows assigning the reference to `s` to the -`r.r` field. - -There is a pattern often seen in other programming languages: - -```v failcompile -fn (mut a MyStruct) f() &MyStruct { - // do something with a - return &a // would return address of borrowed object -} -``` - -Here `f()` is passed a reference `a` as receiver that is passed back to the caller and returned -as result at the same time. The intention behind such a declaration is method chaining like -`y = x.f().g()`. However, the problem with this approach is that a second reference -to `a` is created – so it is not only borrowed and `MyStruct` has to be -declared as `[heap]`. - -In V the better approach is: - -```v -struct MyStruct { -mut: - n int -} - -fn (mut a MyStruct) f() { - // do something with `a` -} - -fn (mut a MyStruct) g() { - // do something else with `a` -} - -fn main() { - x := MyStruct{} // stack allocated - mut y := x - y.f() - y.g() - // instead of `mut y := x.f().g() -} -``` - -This way the `[heap]` attribute can be avoided – resulting in better performance. - -However, stack space is very limited as mentioned above. For this reason the `[heap]` -attribute might be suitable for very large structures even if not required by use cases -like those mentioned above. - -There is an alternative way to manually control allocation on a case to case basis. This -approach is not recommended but shown here for the sake of completeness: - -```v -struct MyStruct { - n int -} - -struct RefStruct { -mut: - r &MyStruct -} - -// simple function - just to overwrite stack segment previously used by `g()` - -fn use_stack() { - x := 7.5 - y := 3.25 - z := x + y - println('${x} ${y} ${z}') -} - -fn main() { - mut m := MyStruct{} - mut r := RefStruct{ - r: &m - } - r.g() - use_stack() // to erase invalid stack contents - println('r: ${r}') -} - -fn (mut r RefStruct) g() { - s := &MyStruct{ // `s` explicitly refers to a heap object - n: 7 - } - // change `&MyStruct` -> `MyStruct` above and `r.f(s)` -> `r.f(&s)` below - // to see data in stack segment being overwritten - r.f(s) -} - -fn (mut r RefStruct) f(s &MyStruct) { - r.r = unsafe { s } // override compiler check -} -``` - -Here the compiler check is suppressed by the `unsafe` block. To make `s` be heap -allocated even without `[heap]` attribute the `struct` literal is prefixed with -an ampersand: `&MyStruct{...}`. - -This last step would not be required by the compiler but without it the reference -inside `r` becomes invalid (the memory area pointed to will be overwritten by -`use_stack()`) and the program might crash (or at least produce an unpredictable -final output). That's why this approach is *unsafe* and should be avoided! - -## ORM - -(This is still in an alpha state) - -V has a built-in ORM (object-relational mapping) which supports SQLite, MySQL and Postgres, -but soon it will support MS SQL and Oracle. - -V's ORM provides a number of benefits: - -- One syntax for all SQL dialects. (Migrating between databases becomes much easier.) -- Queries are constructed using V's syntax. (There's no need to learn another syntax.) -- Safety. (All queries are automatically sanitised to prevent SQL injection.) -- Compile time checks. (This prevents typos which can only be caught during runtime.) -- Readability and simplicity. (You don't need to manually parse the results of a query and - then manually construct objects from the parsed results.) - -```v -import db.sqlite - -// sets a custom table name. Default is struct name (case-sensitive) -@[table: 'customers'] -struct Customer { - id int @[primary; sql: serial] // a field named `id` of integer type must be the first field - name string - nr_orders int - country ?string -} - -db := sqlite.connect('customers.db')! - -// You can create tables from your struct declarations. For example the next query will issue SQL similar to this: -// CREATE TABLE IF NOT EXISTS `Customer` ( -// `id` INTEGER PRIMARY KEY, -// `name` TEXT NOT NULL, -// `nr_orders` INTEGER NOT NULL, -// `country` TEXT -// ) -sql db { - create table Customer -}! - -// insert a new customer: -new_customer := Customer{ - name: 'Bob' - country: 'uk' - nr_orders: 10 -} -sql db { - insert new_customer into Customer -}! - -us_customer := Customer{ - name: 'Martin' - country: 'us' - nr_orders: 5 -} -sql db { - insert us_customer into Customer -}! - -none_country_customer := Customer{ - name: 'Dennis' - country: none - nr_orders: 2 -} -sql db { - insert none_country_customer into Customer -}! - -// update a customer: -sql db { - update Customer set nr_orders = nr_orders + 1 where name == 'Bob' -}! - -// select count(*) from customers -nr_customers := sql db { - select count from Customer -}! -println('number of all customers: ${nr_customers}') - -// V's syntax can be used to build queries: -uk_customers := sql db { - select from Customer where country == 'uk' && nr_orders > 0 -}! -println('We found a total of ${uk_customers.len} customers matching the query.') -for c in uk_customers { - println('customer: ${c.id}, ${c.name}, ${c.country}, ${c.nr_orders}') -} - -none_country_customers := sql db { - select from Customer where country is none -}! -println('We found a total of ${none_country_customers.len} customers, with no country set.') -for c in none_country_customers { - println('customer: ${c.id}, ${c.name}, ${c.country}, ${c.nr_orders}') -} - -// delete a customer -sql db { - delete from Customer where name == 'Bob' -}! -``` - -For more examples and the docs, see [vlib/orm](https://github.com/vlang/v/tree/master/vlib/orm). diff --git a/aiprompts/veb.md b/aiprompts/v_veb_webserver/veb.md similarity index 100% rename from aiprompts/veb.md rename to aiprompts/v_veb_webserver/veb.md diff --git a/aiprompts/veb_assets.md b/aiprompts/v_veb_webserver/veb_assets.md similarity index 100% rename from aiprompts/veb_assets.md rename to aiprompts/v_veb_webserver/veb_assets.md diff --git a/aiprompts/veb_auth.md b/aiprompts/v_veb_webserver/veb_auth.md similarity index 100% rename from aiprompts/veb_auth.md rename to aiprompts/v_veb_webserver/veb_auth.md diff --git a/aiprompts/veb_csrf.md b/aiprompts/v_veb_webserver/veb_csrf.md similarity index 100% rename from aiprompts/veb_csrf.md rename to aiprompts/v_veb_webserver/veb_csrf.md diff --git a/aiprompts/veb_sse.md b/aiprompts/v_veb_webserver/veb_sse.md similarity index 100% rename from aiprompts/veb_sse.md rename to aiprompts/v_veb_webserver/veb_sse.md diff --git a/aiprompts/vtemplates.md b/aiprompts/v_veb_webserver/vtemplates.md similarity index 100% rename from aiprompts/vtemplates.md rename to aiprompts/v_veb_webserver/vtemplates.md diff --git a/aiprompts/vlang webserver veb instructions.md b/aiprompts/vlang webserver veb instructions.md deleted file mode 100644 index 99669400..00000000 --- a/aiprompts/vlang webserver veb instructions.md +++ /dev/null @@ -1,907 +0,0 @@ -# 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('

Login page

todo: make form

') - } 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('

Page not found!

') -} -``` - -## 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('

Hello world!

') -// 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) -} -```