This commit is contained in:
2025-07-21 06:30:42 +02:00
parent ddf5fcbbcc
commit 2127fb2ec0
35 changed files with 29 additions and 3539 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,124 @@
module net.html
net/html is an **HTML Parser** written in pure V.
## Usage
```v
import net.html
fn main() {
doc := html.parse('<html><body><h1 class="title">Hello world!</h1></body></html>')
tag := doc.get_tags(name: 'h1')[0] // <h1>Hello world!</h1>
println(tag.name) // h1
println(tag.content) // Hello world!
println(tag.attributes) // {'class':'title'}
println(tag.str()) // <h1 class="title">Hello world!</h1>
}
```
More examples found on [`parser_test.v`](parser_test.v) and [`html_test.v`](html_test.v)
fn parse(text string) DocumentObjectModel
parse parses and returns the DOM from the given text.
Note: this function converts tags to lowercase. E.g. <MyTag>content</MyTag> is parsed as <mytag>content</mytag>.
fn parse_file(filename string) DocumentObjectModel
parse_file parses and returns the DOM from the contents of a file.
Note: this function converts tags to lowercase. E.g. <MyTag>content</MyTag> is parsed as <mytag>content</mytag>.
enum CloseTagType {
in_name
new_tag
}
struct DocumentObjectModel {
mut:
root &Tag = unsafe { nil }
constructed bool
btree BTree
all_tags []&Tag
all_attributes map[string][]&Tag
close_tags map[string]bool // add a counter to see count how many times is closed and parse correctly
attributes map[string][]string
tag_attributes map[string][][]&Tag
tag_type map[string][]&Tag
debug_file os.File
}
The W3C Document Object Model (DOM) is a platform and language-neutral interface that allows programs and scripts to dynamically access and update the content, structure, and style of a document.
https://www.w3.org/TR/WD-DOM/introduction.html
fn (dom DocumentObjectModel) get_root() &Tag
get_root returns the root of the document.
fn (dom DocumentObjectModel) get_tag(name string) []&Tag
get_tag retrieves all tags in the document that have the given tag name.
fn (dom DocumentObjectModel) get_tags(options GetTagsOptions) []&Tag
get_tags returns all tags stored in the document.
fn (dom DocumentObjectModel) get_tags_by_class_name(names ...string) []&Tag
get_tags_by_class_name retrieves all tags recursively in the document root that have the given class name(s).
fn (dom DocumentObjectModel) get_tag_by_attribute(name string) []&Tag
get_tag_by_attribute retrieves all tags in the document that have the given attribute name.
fn (dom DocumentObjectModel) get_tags_by_attribute(name string) []&Tag
get_tags_by_attribute retrieves all tags in the document that have the given attribute name.
fn (mut dom DocumentObjectModel) get_tags_by_attribute_value(name string, value string) []&Tag
get_tags_by_attribute_value retrieves all tags in the document that have the given attribute name and value.
fn (mut dom DocumentObjectModel) get_tag_by_attribute_value(name string, value string) []&Tag
get_tag_by_attribute_value retrieves all tags in the document that have the given attribute name and value.
struct GetTagsOptions {
pub:
name string
}
struct Parser {
mut:
dom DocumentObjectModel
lexical_attributes LexicalAttributes = LexicalAttributes{
current_tag: &Tag{}
}
filename string = 'direct-parse'
initialized bool
tags []&Tag
debug_file os.File
}
Parser is responsible for reading the HTML strings and converting them into a `DocumentObjectModel`.
fn (mut parser Parser) add_code_tag(name string)
This function is used to add a tag for the parser ignore it's content. For example, if you have an html or XML with a custom tag, like `<script>`, using this function, like `add_code_tag('script')` will make all `script` tags content be jumped, so you still have its content, but will not confuse the parser with it's `>` or `<`.
fn (mut parser Parser) split_parse(data string)
split_parse parses the HTML fragment
fn (mut parser Parser) parse_html(data string)
parse_html parses the given HTML string
fn (mut parser Parser) finalize()
finalize finishes the parsing stage .
fn (mut parser Parser) get_dom() DocumentObjectModel
get_dom returns the parser's current DOM representation.
struct Tag {
pub mut:
name string
content string
children []&Tag
attributes map[string]string // attributes will be like map[name]value
last_attribute string
class_set datatypes.Set[string]
parent &Tag = unsafe { nil }
position_in_parent int
closed bool
close_type CloseTagType = .in_name
}
Tag holds the information of an HTML tag.
fn (tag Tag) text() string
text returns the text contents of the tag.
fn (tag &Tag) str() string
fn (tag &Tag) get_tag(name string) ?&Tag
get_tag retrieves the first found child tag in the tag that has the given tag name.
fn (tag &Tag) get_tags(name string) []&Tag
get_tags retrieves all child tags recursively in the tag that have the given tag name.
fn (tag &Tag) get_tag_by_attribute(name string) ?&Tag
get_tag_by_attribute retrieves the first found child tag in the tag that has the given attribute name.
fn (tag &Tag) get_tags_by_attribute(name string) []&Tag
get_tags_by_attribute retrieves all child tags recursively in the tag that have the given attribute name.
fn (tag &Tag) get_tag_by_attribute_value(name string, value string) ?&Tag
get_tag_by_attribute_value retrieves the first found child tag in the tag that has the given attribute name and value.
fn (tag &Tag) get_tags_by_attribute_value(name string, value string) []&Tag
get_tags_by_attribute_value retrieves all child tags recursively in the tag that have the given attribute name and value.
fn (tag &Tag) get_tag_by_class_name(names ...string) ?&Tag
get_tag_by_class_name retrieves the first found child tag in the tag that has the given class name(s).
fn (tag &Tag) get_tags_by_class_name(names ...string) []&Tag
get_tags_by_class_name retrieves all child tags recursively in the tag that have the given class name(s).

105
aiprompts/v_advanced/io.md Normal file
View File

@@ -0,0 +1,105 @@
module io
## Description
`io` provides common interfaces for buffered reading/writing of data.
const read_all_len = 10 * 1024
const read_all_grow_len = 1024
fn cp(mut src Reader, mut dst Writer) !
cp copies from `src` to `dst` by allocating a maximum of 1024 bytes buffer for reading until either EOF is reached on `src` or an error occurs. An error is returned if an error is encountered during write.
fn make_readerwriter(r Reader, w Writer) ReaderWriterImpl
make_readerwriter takes a rstream and a wstream and makes an rwstream with them.
fn new_buffered_reader(o BufferedReaderConfig) &BufferedReader
new_buffered_reader creates a new BufferedReader.
fn new_multi_writer(writers ...Writer) Writer
new_multi_writer returns a Writer that writes to all writers. The write function of the returned Writer writes to all writers of the MultiWriter, returns the length of bytes written, and if any writer fails to write the full length an error is returned and writing to other writers stops, and if any writer returns an error the error is returned immediately and writing to other writers stops.
fn read_all(config ReadAllConfig) ![]u8
read_all reads all bytes from a reader until either a 0 length read or if read_to_end_of_stream is true then the end of the stream (`none`).
fn read_any(mut r Reader) ![]u8
read_any reads any available bytes from a reader (until the reader returns a read of 0 length).
interface RandomReader {
read_from(pos u64, mut buf []u8) !int
}
RandomReader represents a stream of readable data from at a random location.
interface RandomWriter {
write_to(pos u64, buf []u8) !int
}
RandomWriter is the interface that wraps the `write_to` method, which writes `buf.len` bytes to the underlying data stream at a random `pos`.
interface Reader {
// read reads up to buf.len bytes and places
// them into buf.
// A type that implements this should return
// `io.Eof` on end of stream (EOF) instead of just returning 0
mut:
read(mut buf []u8) !int
}
Reader represents a stream of data that can be read.
interface ReaderWriter {
Reader
Writer
}
ReaderWriter represents a stream that can be read and written.
interface Writer {
mut:
write(buf []u8) !int
}
Writer is the interface that wraps the `write` method, which writes `buf.len` bytes to the underlying data stream.
fn (mut r ReaderWriterImpl) read(mut buf []u8) !int
read reads up to `buf.len` bytes into `buf`. It returns the number of bytes read or any error encountered.
fn (mut r ReaderWriterImpl) write(buf []u8) !int
write writes `buf.len` bytes from `buf` to the underlying data stream. It returns the number of bytes written or any error encountered.
struct BufferedReadLineConfig {
pub:
delim u8 = `\n` // line delimiter
}
BufferedReadLineConfig are options that can be given to the read_line() function.
struct BufferedReader {
mut:
reader Reader
buf []u8
offset int // current offset in the buffer
len int
fails int // how many times fill_buffer has read 0 bytes in a row
mfails int // maximum fails, after which we can assume that the stream has ended
pub mut:
end_of_stream bool // whether we reached the end of the upstream reader
total_read int // total number of bytes read
}
BufferedReader provides a buffered interface for a reader.
fn (mut r BufferedReader) read(mut buf []u8) !int
read fufills the Reader interface.
fn (mut r BufferedReader) free()
free deallocates the memory for a buffered reader's internal buffer.
fn (r BufferedReader) end_of_stream() bool
end_of_stream returns whether the end of the stream was reached.
fn (mut r BufferedReader) read_line(config BufferedReadLineConfig) !string
read_line attempts to read a line from the buffered reader. It will read until it finds the specified line delimiter such as (\n, the default or \0) or the end of stream.
struct BufferedReaderConfig {
pub:
reader Reader
cap int = 128 * 1024 // large for fast reading of big(ish) files
retries int = 2 // how many times to retry before assuming the stream ended
}
BufferedReaderConfig are options that can be given to a buffered reader.
struct Eof {
Error
}
/ Eof error means that we reach the end of the stream.
struct MultiWriter {
pub mut:
writers []Writer
}
MultiWriter writes to all its writers.
fn (mut m MultiWriter) write(buf []u8) !int
write writes to all writers of the MultiWriter. Returns the length of bytes written. If any writer fails to write the full length an error is returned and writing to other writers stops. If any writer returns an error the error is returned immediately and writing to other writers stops.
struct NotExpected {
cause string
code int
}
NotExpected is a generic error that means that we receave a not expected error.
struct ReadAllConfig {
pub:
read_to_end_of_stream bool
reader Reader
}
ReadAllConfig allows options to be passed for the behaviour of read_all.

354
aiprompts/v_advanced/net.md Normal file
View File

@@ -0,0 +1,354 @@
module net
## Description
`net` provides networking functions. It is mostly a wrapper to BSD sockets, so you can listen on a port, connect to remote TCP/UDP services, and communicate with them.
const msg_nosignal = 0x4000
const err_connection_refused = error_with_code('net: connection refused', errors_base + 10)
const err_option_wrong_type = error_with_code('net: set_option_xxx option wrong type',
errors_base + 3)
const opts_can_set = [
SocketOption.broadcast,
.debug,
.dont_route,
.keep_alive,
.linger,
.oob_inline,
.receive_buf_size,
.receive_low_size,
.receive_timeout,
.send_buf_size,
.send_low_size,
.send_timeout,
.ipv6_only,
]
const error_eagain = C.EAGAIN
const err_port_out_of_range = error_with_code('net: port out of range', errors_base + 5)
const opts_bool = [SocketOption.broadcast, .debug, .dont_route, .error, .keep_alive, .oob_inline]
const err_connect_failed = error_with_code('net: connect failed', errors_base + 7)
const errors_base = 0
Well defined errors that are returned from socket functions
const opts_int = [
SocketOption.receive_buf_size,
.receive_low_size,
.receive_timeout,
.send_buf_size,
.send_low_size,
.send_timeout,
]
const error_eintr = C.EINTR
const error_ewouldblock = C.EWOULDBLOCK
const err_no_udp_remote = error_with_code('net: no udp remote', errors_base + 6)
const error_einprogress = C.EINPROGRESS
const err_timed_out_code = errors_base + 9
const err_connect_timed_out = error_with_code('net: connect timed out', errors_base + 8)
const err_new_socket_failed = error_with_code('net: new_socket failed to create socket',
errors_base + 1)
const msg_dontwait = C.MSG_DONTWAIT
const infinite_timeout = time.infinite
infinite_timeout should be given to functions when an infinite_timeout is wanted (i.e. functions only ever return with data)
const no_timeout = time.Duration(0)
no_timeout should be given to functions when no timeout is wanted (i.e. all functions return instantly)
const err_timed_out = error_with_code('net: op timed out', errors_base + 9)
const tcp_default_read_timeout = 30 * time.second
const err_option_not_settable = error_with_code('net: set_option_xxx option not settable',
errors_base + 2)
const tcp_default_write_timeout = 30 * time.second
fn addr_from_socket_handle(handle int) Addr
addr_from_socket_handle returns an address, based on the given integer socket `handle`
fn close(handle int) !
close a socket, given its file descriptor `handle`. In non-blocking mode, if `close()` does not succeed immediately, it causes an error to be propagated to `TcpSocket.close()`, which is not intended. Therefore, `select` is used just like `connect()`.
fn default_tcp_dialer() Dialer
default_tcp_dialer will give you an instance of Dialer, that is suitable for making new tcp connections.
fn dial_tcp(oaddress string) !&TcpConn
dial_tcp will try to create a new TcpConn to the given address.
fn dial_tcp_with_bind(saddr string, laddr string) !&TcpConn
dial_tcp_with_bind will bind the given local address `laddr` and dial.
fn dial_udp(raddr string) !&UdpConn
fn error_code() int
fn listen_tcp(family AddrFamily, saddr string, options ListenOptions) !&TcpListener
fn listen_udp(laddr string) !&UdpConn
fn new_ip(port u16, addr [4]u8) Addr
new_ip creates a new Addr from the IPv4 address family, based on the given port and addr
fn new_ip6(port u16, addr [16]u8) Addr
new_ip6 creates a new Addr from the IP6 address family, based on the given port and addr
fn peer_addr_from_socket_handle(handle int) !Addr
peer_addr_from_socket_handle retrieves the ip address and port number, given a socket handle
fn resolve_addrs(addr string, family AddrFamily, @type SocketType) ![]Addr
resolve_addrs converts the given `addr`, `family` and `@type` to a list of addresses
fn resolve_addrs_fuzzy(addr string, @type SocketType) ![]Addr
resolve_addrs converts the given `addr` and `@type` to a list of addresses
fn resolve_ipaddrs(addr string, family AddrFamily, typ SocketType) ![]Addr
resolve_ipaddrs converts the given `addr`, `family` and `typ` to a list of addresses
fn set_blocking(handle int, state bool) !
set_blocking will change the state of the socket to either blocking, when state is true, or non blocking (false).
fn shutdown(handle int, config ShutdownConfig) int
shutdown shutsdown a socket, given its file descriptor `handle`. By default it shuts it down in both directions, both for reading and for writing. You can change that using `net.shutdown(handle, how: .read)` or `net.shutdown(handle, how: .write)` In non-blocking mode, `shutdown()` may not succeed immediately, so `select` is also used to make sure that the function doesn't return an incorrect result.
fn socket_error(potential_code int) !int
fn socket_error_message(potential_code int, s string) !int
fn split_address(addr string) !(string, u16)
split_address splits an address into its host name and its port
fn tcp_socket_from_handle_raw(sockfd int) TcpSocket
tcp_socket_from_handle_raw is similar to tcp_socket_from_handle, but it does not modify any socket options
fn validate_port(port int) !u16
validate_port checks whether a port is valid and returns the port or an error
fn wrap_error(error_code int) !
interface Connection {
addr() !Addr
peer_addr() !Addr
mut:
read(mut []u8) !int
write([]u8) !int
close() !
}
Connection provides a generic SOCK_STREAM style interface that protocols can use as a base connection object to support TCP, UNIX Domain Sockets and various proxying solutions.
interface Dialer {
dial(address string) !Connection
}
Dialer is an abstract dialer interface for producing connections to adresses.
fn (mut s TcpSocket) set_option_bool(opt SocketOption, value bool) !
fn (mut s TcpSocket) set_option_int(opt SocketOption, value int) !
fn (mut s TcpSocket) set_dualstack(on bool) !
fn (mut s TcpSocket) bind(addr string) !
bind a local rddress for TcpSocket
fn (mut s UdpSocket) set_option_bool(opt SocketOption, value bool) !
fn (mut s UdpSocket) set_dualstack(on bool) !
enum AddrFamily {
unix = C.AF_UNIX
ip = C.AF_INET
ip6 = C.AF_INET6
unspec = C.AF_UNSPEC
}
AddrFamily are the available address families
enum ShutdownDirection {
read
write
read_and_write
}
ShutdownDirection is used by `net.shutdown`, for specifying the direction for which the communication will be cut.
enum SocketOption {
// TODO: SO_ACCEPT_CONN is not here because windows doesn't support it
// and there is no easy way to define it
broadcast = C.SO_BROADCAST
debug = C.SO_DEBUG
dont_route = C.SO_DONTROUTE
error = C.SO_ERROR
keep_alive = C.SO_KEEPALIVE
linger = C.SO_LINGER
oob_inline = C.SO_OOBINLINE
reuse_addr = C.SO_REUSEADDR
receive_buf_size = C.SO_RCVBUF
receive_low_size = C.SO_RCVLOWAT
receive_timeout = C.SO_RCVTIMEO
send_buf_size = C.SO_SNDBUF
send_low_size = C.SO_SNDLOWAT
send_timeout = C.SO_SNDTIMEO
socket_type = C.SO_TYPE
ipv6_only = C.IPV6_V6ONLY
}
enum SocketType {
udp = C.SOCK_DGRAM
tcp = C.SOCK_STREAM
seqpacket = C.SOCK_SEQPACKET
}
SocketType are the available sockets
struct Addr {
pub:
len u8
f u8
addr AddrData
}
fn (a Addr) family() AddrFamily
family returns the family/kind of the given address `a`
fn (a Addr) len() u32
len returns the length in bytes of the address `a`, depending on its family
fn (a Addr) port() !u16
port returns the ip or ip6 port of the given address `a`
fn (a Addr) str() string
str returns a string representation of the address `a`
struct C.addrinfo {
mut:
ai_family int
ai_socktype int
ai_flags int
ai_protocol int
ai_addrlen int
ai_addr voidptr
ai_canonname voidptr
ai_next voidptr
}
struct C.fd_set {}
struct C.sockaddr_in {
mut:
sin_len u8
sin_family u8
sin_port u16
sin_addr u32
sin_zero [8]char
}
struct C.sockaddr_in6 {
mut:
// 1 + 1 + 2 + 4 + 16 + 4 = 28;
sin6_len u8 // 1
sin6_family u8 // 1
sin6_port u16 // 2
sin6_flowinfo u32 // 4
sin6_addr [16]u8 // 16
sin6_scope_id u32 // 4
}
struct C.sockaddr_un {
mut:
sun_len u8
sun_family u8
sun_path [max_unix_path]char
}
struct Ip {
port u16
addr [4]u8
// Pad to size so that socket functions
// dont complain to us (see in.h and bind())
// TODO(emily): I would really like to use
// some constant calculations here
// so that this doesnt have to be hardcoded
sin_pad [8]u8
}
fn (a Ip) str() string
str returns a string representation of `a`
struct Ip6 {
port u16
flow_info u32
addr [16]u8
scope_id u32
}
fn (a Ip6) str() string
str returns a string representation of `a`
struct ListenOptions {
pub:
dualstack bool = true
backlog int = 128
}
struct ShutdownConfig {
pub:
how ShutdownDirection = .read_and_write
}
struct Socket {
pub:
handle int
}
fn (s &Socket) address() !Addr
address gets the address of a socket
struct TCPDialer {}
TCPDialer is a concrete instance of the Dialer interface, for creating tcp connections.
fn (t TCPDialer) dial(address string) !Connection
dial will try to create a new abstract connection to the given address. It will return an error, if that is not possible.
struct TcpConn {
pub mut:
sock TcpSocket
handle int
write_deadline time.Time
read_deadline time.Time
read_timeout time.Duration
write_timeout time.Duration
is_blocking bool = true
}
fn (c &TcpConn) addr() !Addr
fn (mut c TcpConn) close() !
close closes the tcp connection
fn (mut con TcpConn) get_blocking() bool
get_blocking returns whether the connection is in a blocking state, that is calls to .read_line, C.recv etc will block till there is new data arrived, instead of returning immediately.
fn (c &TcpConn) peer_addr() !Addr
peer_addr retrieves the ip address and port number used by the peer
fn (c &TcpConn) peer_ip() !string
peer_ip retrieves the ip address used by the peer, and returns it as a string
fn (c TcpConn) read(mut buf []u8) !int
read reads data from the tcp connection into the mutable buffer `buf`. The number of bytes read is limited to the length of the buffer `buf.len`. The returned value is the number of read bytes (between 0 and `buf.len`).
fn (mut c TcpConn) read_deadline() !time.Time
fn (mut con TcpConn) read_line() string
read_line is a *simple*, *non customizable*, blocking line reader. It will return a line, ending with LF, or just '', on EOF.
Note: if you want more control over the buffer, please use a buffered IO reader instead: `io.new_buffered_reader({reader: io.make_reader(con)})`
fn (mut con TcpConn) read_line_max(max_line_len int) string
read_line_max is a *simple*, *non customizable*, blocking line reader. It will return a line, ending with LF, '' on EOF. It stops reading, when the result line length exceeds max_line_len.
fn (c TcpConn) read_ptr(buf_ptr &u8, len int) !int
read_ptr reads data from the tcp connection to the given buffer. It reads at most `len` bytes. It returns the number of actually read bytes, which can vary between 0 to `len`.
fn (c &TcpConn) read_timeout() time.Duration
fn (mut con TcpConn) set_blocking(state bool) !
set_blocking will change the state of the connection to either blocking, when state is true, or non blocking (false). The default for `net` tcp connections is the blocking mode. Calling .read_line will set the connection to blocking mode. In general, changing the blocking mode after a successful connection may cause unexpected surprises, so this function is not recommended to be called anywhere but for this file.
fn (mut c TcpConn) set_read_deadline(deadline time.Time)
fn (mut c TcpConn) set_read_timeout(t time.Duration)
fn (mut c TcpConn) set_sock() !
set_sock initialises the c.sock field. It should be called after `.accept_only()!`.
Note: just use `.accept()!`. In most cases it is simpler, and calls `.set_sock()!` for you.
fn (mut c TcpConn) set_write_deadline(deadline time.Time)
fn (mut c TcpConn) set_write_timeout(t time.Duration)
fn (c TcpConn) str() string
fn (c TcpConn) wait_for_read() !
fn (mut c TcpConn) wait_for_write() !
fn (mut c TcpConn) write(bytes []u8) !int
write blocks and attempts to write all data
fn (mut c TcpConn) write_deadline() !time.Time
fn (mut c TcpConn) write_ptr(b &u8, len int) !int
write_ptr blocks and attempts to write all data
fn (mut c TcpConn) write_string(s string) !int
write_string blocks and attempts to write all data
fn (c &TcpConn) write_timeout() time.Duration
struct TcpListener {
pub mut:
sock TcpSocket
accept_timeout time.Duration
accept_deadline time.Time
is_blocking bool = true
}
fn (mut l TcpListener) accept() !&TcpConn
accept a tcp connection from an external source to the listener `l`.
fn (mut l TcpListener) accept_only() !&TcpConn
accept_only accepts a tcp connection from an external source to the listener `l`. Unlike `accept`, `accept_only` *will not call* `.set_sock()!` on the result, and is thus faster.
Note: you *need* to call `.set_sock()!` manually, before using theconnection after calling `.accept_only()!`, but that does not have to happen in the same thread that called `.accept_only()!`. The intention of this API, is to have a more efficient way to accept connections, that are later processed by a thread pool, while the main thread remains active, so that it can accept other connections. See also vlib/vweb/vweb.v .
If you do not need that, just call `.accept()!` instead, which will call `.set_sock()!` for you.
fn (c &TcpListener) accept_deadline() !time.Time
fn (mut c TcpListener) set_accept_deadline(deadline time.Time)
fn (c &TcpListener) accept_timeout() time.Duration
fn (mut c TcpListener) set_accept_timeout(t time.Duration)
fn (mut c TcpListener) wait_for_accept() !
fn (mut c TcpListener) close() !
fn (c &TcpListener) addr() !Addr
struct UdpConn {
pub mut:
sock UdpSocket
mut:
write_deadline time.Time
read_deadline time.Time
read_timeout time.Duration
write_timeout time.Duration
}
fn (mut c UdpConn) write_ptr(b &u8, len int) !int
sock := UdpSocket{ handle: sbase.handle l: local r: resolve_wrapper(raddr) } }
fn (mut c UdpConn) write(buf []u8) !int
fn (mut c UdpConn) write_string(s string) !int
fn (mut c UdpConn) write_to_ptr(addr Addr, b &u8, len int) !int
fn (mut c UdpConn) write_to(addr Addr, buf []u8) !int
write_to blocks and writes the buf to the remote addr specified
fn (mut c UdpConn) write_to_string(addr Addr, s string) !int
write_to_string blocks and writes the buf to the remote addr specified
fn (mut c UdpConn) read(mut buf []u8) !(int, Addr)
read reads from the socket into buf up to buf.len returning the number of bytes read
fn (c &UdpConn) read_deadline() !time.Time
fn (mut c UdpConn) set_read_deadline(deadline time.Time)
fn (c &UdpConn) write_deadline() !time.Time
fn (mut c UdpConn) set_write_deadline(deadline time.Time)
fn (c &UdpConn) read_timeout() time.Duration
fn (mut c UdpConn) set_read_timeout(t time.Duration)
fn (c &UdpConn) write_timeout() time.Duration
fn (mut c UdpConn) set_write_timeout(t time.Duration)
fn (mut c UdpConn) wait_for_read() !
fn (mut c UdpConn) wait_for_write() !
fn (c &UdpConn) str() string
fn (mut c UdpConn) close() !
struct Unix {
path [max_unix_path]char
}

View File

@@ -0,0 +1,187 @@
## Compile time reflection
$ is used as a prefix for compile time (also referred to as 'comptime') operations.
Having built-in JSON support is nice, but V also allows you to create efficient serializers for any data format. V has compile time if and for constructs:
.fields
You can iterate over struct fields using .fields, it also works with generic types (e.g. T.fields) and generic arguments (e.g. param.fields where fn gen[T](param T) {).
struct User {
name string
age int
}
fn main() {
$for field in User.fields {
$if field.typ is string {
println('${field.name} is of type string')
}
}
}
// Output:
// name is of type string
.values
You can read Enum values and their attributes.
enum Color {
red @[RED] // first attribute
blue @[BLUE] // second attribute
}
fn main() {
$for e in Color.values {
println(e.name)
println(e.attrs)
}
}
// Output:
// red
// ['RED']
// blue
// ['BLUE']
.attributes
You can read Struct attributes.
@[COLOR]
struct Foo {
a int
}
fn main() {
$for e in Foo.attributes {
println(e)
}
}
// Output:
// StructAttribute{
// name: 'COLOR'
// has_arg: false
// arg: ''
// kind: plain
// }
.variants
You can read variant types from Sum type.
type MySum = int | string
fn main() {
$for v in MySum.variants {
$if v.typ is int {
println('has int type')
} $else $if v.typ is string {
println('has string type')
}
}
}
// Output:
// has int type
// has string type
.methods
You can retrieve information about struct methods.
struct Foo {
}
fn (f Foo) test() int {
return 123
}
fn (f Foo) test2() string {
return 'foo'
}
fn main() {
foo := Foo{}
$for m in Foo.methods {
$if m.return_type is int {
print('${m.name} returns int: ')
println(foo.$method())
} $else $if m.return_type is string {
print('${m.name} returns string: ')
println(foo.$method())
}
}
}
// Output:
// test returns int: 123
// test2 returns string: foo
.params
You can retrieve information about struct method params.
struct Test {
}
fn (t Test) foo(arg1 int, arg2 string) {
}
fn main() {
$for m in Test.methods {
$for param in m.params {
println('${typeof(param.typ).name}: ${param.name}')
}
}
}
// Output:
// int: arg1
// string: arg2
## Example
```v
// An example deserializer implementation
struct User {
name string
age int
}
fn main() {
data := 'name=Alice\nage=18'
user := decode[User](data)
println(user)
}
fn decode[T](data string) T {
mut result := T{}
// compile-time `for` loop
// T.fields gives an array of a field metadata type
$for field in T.fields {
$if field.typ is string {
// $(string_expr) produces an identifier
result.$(field.name) = get_string(data, field.name)
} $else $if field.typ is int {
result.$(field.name) = get_int(data, field.name)
}
}
return result
}
fn get_string(data string, field_name string) string {
for line in data.split_into_lines() {
key_val := line.split('=')
if key_val[0] == field_name {
return key_val[1]
}
}
return ''
}
fn get_int(data string, field string) int {
return get_string(data, field).int()
}
// `decode<User>` generates:
// fn decode_User(data string) User {
// mut result := User{}
// result.name = get_string(data, 'name')
// result.age = get_int(data, 'age')
// return result
// }
```

View File

@@ -0,0 +1,516 @@
# Regex Library
#### Differences with PCRE:
> regex is not PCRE compatible.
- The basic element is the token not the sequence of symbols, and the mostsimple token, is a single character.
| the OR operator acts on tokens, for example abc|ebc is notabc OR ebc. Instead it is evaluated like ab, followed by c OR e, followed by bc, because the token is the base element, not the sequence of symbols.
- Two char classes with an OR in the middle is a syntax error.
- The match operation stops at the end of the string. It does NOT stopat new line characters.
- The tokens are the atomic units, used by this regex engine. They can be one of the following:
- Simple char, This token is a simple single character like a or b etc.
- Match positional delimiters
- ^ Matches the start of the string.
- $ Matches the end of the string.
#### Char class (cc)
- The character classes match all the chars specified inside. Use square brackets [ ] to enclose them.
- The sequence of the chars in the character class, is evaluated with an OR op.
- For example, the cc [abc], matches any character, that is a or b or c, but it doesn't match C or z.
- Inside a cc, it is possible to specify a "range" of characters, for example [ad-h] is equivalent to writing [adefgh].
- A cc can have different ranges at the same time, for example [a-zA-Z0-9] matches all the latin lowercase, uppercase and numeric characters.
- It is possible to negate the meaning of a cc, using the caret char at the start of the cc like this: [^abc] . That matches every char that is NOT a or b or c.
- A cc can contain meta-chars like: [a-z\d], that match all the lowercase latin chars a-z and all the digits \d.
- It is possible to mix all the properties of the char class together.
- Note > In order to match the - (minus) char, it must be preceded by > a backslash in the cc, for example [\-_\d\a] will match: > - - minus, > - _ underscore, > - \d numeric chars, > - \a lower case chars.
#### Meta-chars
- A meta-char is specified by a backslash, before a character. For example \w is the meta-char w.
- A meta-char can match different types of characters.
- \w matches a word char [a-zA-Z0-9_]
- \W matches a non word char
- \d matches a digit [0-9]
- \D matches a non digit
- \s matches a space char, one of [' ','\t','\n','\r','\v','\f']
- \S matches a non space char
- \a matches only a lowercase char [a-z]
- \A matches only an uppercase char [A-Z]
- \x41 match a byte of value 0x41, A in ascii code
- \X414C match two consecutive bytes of value 0x414c, AL in ascii code
#### Quantifiers
Each token can have a quantifier, that specifies how many times the character must be matched.
Short quantifiers
- ? matches 0 or 1 time, a?b matches both ab or b
- + matches at least 1 time, for example, a+ matches both aaa or a
- * matches 0 or more times, for example, a*b matches aaab, ab or b
Long quantifiers
- {x} matches exactly x times, a{2} matches aa, but not aaa or a
- {min,} matches at least min times, a{2,} matches aaa or aa, not a
- {,max} matches at least 0 times and at maximum max times,for example, a{,2} matches a and aa, but doesn't match aaa- {min,max} matches from min times, to max times, for examplea{2,3} matches aa and aaa, but doesn't match a or aaaa
- A long quantifier, may have a greedy off flag, that is the ? character after the brackets. {2,4}? means to match the minimum number of possible tokens, in this case 2.
#### dot char
The dot is a particular meta-char, that matches "any char".
input:
'''abccc ddeef'''
The following table shows the query strings and the result of parsing source string.
| query string | result |
|--------------|---------|
| `.*c` | `abc` |
| `.*dd` | `abcc dd` |
| `ab.*e` | `abccc dde`|
| `ab.{3} .*e` | `abccc dde`|
- The dot matches any character, until the next token match is satisfied.
- Important Note: Consecutive dots, for example ..., are not allowed. > This will cause a syntax error. Use a quantifier instead.
OR token
The token |, means a logic OR operation between two consecutive tokens, i.e. a|b matches a character that is a or b.
The OR token can work in a "chained way": a|(b)|cd means test first a, if the char is not a, then test the group (b), and if the group doesn't match too, finally test the token c.
Note > Unlike in PCRE, the OR operation works at token level! > It doesn't work at concatenation level!
Note > Two char classes with an OR in the middle is a syntax error.
That also means, that a query string like abc|bde is not equal to (abc)|(bde), but instead to ab(c|b)de. The OR operation works only for c|b, not at char concatenation level.
#### Groups
Groups are a method to create complex patterns with repetitions of blocks of tokens.
The groups are delimited by round brackets `( )`.
Groups can be nested. Like all other tokens, groups can have a quantifier too.
- `c(pa)+z` match `cpapaz` or `cpaz` or `cpapapaz`.
- `(c(pa)+z ?)+` matches `cpaz cpapaz cpapapaz` or `cpapaz`
Let's analyze this last case, first we have the group `#0`, that is the most outer round brackets `(...)+`. This group has a quantifier `+`, that says to match its content at least one time.
Then we have a simple char token `c`, and a second group `#1`: `(pa)+`. This group also tries to match the sequence `pa`, at least one time, as specified by the `+` quantifier.
Then, we have another simple token `z` and another simple token `?`, i.e. the space char (ascii code 32) followed by the `?` quantifier, which means that the preceding space should be matched 0 or 1 time.
This explains why the `(c(pa)+z ?)+` query string, can match `cpaz cpapaz cpapapaz`.
In this implementation the groups are "capture groups". This means that the last temporal result for each group, can be retrieved from the `RE` struct.
The "capture groups" are stored as indexes in the field `groups`, that is an `[]int` inside the `RE` struct.
example
```v
import regex
text := 'cpaz cpapaz cpapapaz'
query := r'(c(pa)+z ?)+'
mut re := regex.regex_opt(query) or { panic(err) }
println(re.get_query())
// #0(c#1(pa)+z ?)+
// #0 and #1 are the ids of the groups, are shown if re.debug is 1 or 2
start, end := re.match_string(text)
// [start=0, end=20] match => [cpaz cpapaz cpapapaz]
mut gi := 0
for gi < re.groups.len {
if re.groups[gi] >= 0 {
println('${gi / 2} :[${text[re.groups[gi]..re.groups[gi + 1]]}]')
}
gi += 2
}
// groups captured
// 0 :[cpapapaz]
// 1 :[pa]
```
Note > To show the group id number in the result of the get_query() > the flag debug of the RE object must be 1 or 2
In order to simplify the use of the captured groups, it is possible to use the utility function: get_group_list.
This function returns a list of groups using this support struct:
```v
pub struct Re_group {
pub:
start int = -1
end int = -1
}
```
Groups example:
This simple function converts an HTML RGB value with 3 or 6 hex digits to an u32 value, this function is not optimized and it is only for didatical purpose. Example: #A0B0CC #A9F
```v
import regex
fn convert_html_rgb(in_col string) u32 {
mut n_digit := if in_col.len == 4 { 1 } else { 2 }
mut col_mul := if in_col.len == 4 { 4 } else { 0 }
// this is the regex query, it uses the V string interpolation to customize the regex query
// Note: If you want to use escaped code you must use the r"" (raw) strings,
// *** please remember that the V interpoaltion doesn't work on raw strings. ***
query :='#([a-fA-F0-9]{${n_digit}})([a-fA-F0-9]{${n_digit}})([a-fA-F0-9]{${n_digit}})'
mut re := regex.regex_opt(query) or { panic(err) }
start, end := re.match_string(in_col)
println('start: ${start}, end: ${end}')
mut res := u32(0)
if start >= 0 {
group_list := re.get_group_list() // this is the utility function
r := ('0x' + in_col[group_list[0].start..group_list[0].end]).int() << col_mul
g := ('0x' + in_col[group_list[1].start..group_list[1].end]).int() << col_mul
b := ('0x' + in_col[group_list[2].start..group_list[2].end]).int() << col_mul
println('r: ${r} g: ${g} b: ${b}')
res = u32(r) << 16 | u32(g) << 8 | u32(b)
}
return res
}
```
Other utility functions are get_group_by_id and get_group_bounds_by_id that get directly the string of a group using its id:
```v
txt := 'my used string....'
for g_index := 0; g_index < re.group_count; g_index++ {
println('#${g_index} [${re.get_group_by_id(txt, g_index)}] \
}] bounds: ${re.get_group_bounds_by_id(g_index)}')
}
```
More helper functions are listed in the Groups query functions section.
Groups Continuous saving
In particular situations, it is useful to have a continuous group saving. This is possible by initializing the group_csave field in the RE struct.
This feature allows you to collect data in a continuous/streaming way.
In the example, we can pass a text, followed by an integer list, that we wish to collect. To achieve this task, we can use the continuous group saving, by enabling the right flag: re.group_csave_flag = true.
The .group_csave array will be filled then, following this logic:
re.group_csave[0] - number of total saved records re.group_csave[1+n*3] - id of the saved group re.group_csave[2+n*3] - start index in the source string of the saved group re.group_csave[3+n*3] - end index in the source string of the saved group
The regex will save groups, until it finishes, or finds that the array has no more space. If the space ends, no error is raised, and further records will not be saved.
```v
import regex
txt := 'http://www.ciao.mondo/hello/pippo12_/pera.html'
query := r'(?P<format>https?)|(?P<format>ftps?)://(?P<token>[\w_]+.)+'
mut re := regex.regex_opt(query) or { panic(err) }
// println(re.get_code()) // uncomment to see the print of the regex execution code
re.debug = 2 // enable maximum log
println('String: ${txt}')
println('Query : ${re.get_query()}')
re.debug = 0 // disable log
re.group_csave_flag = true
start, end := re.match_string(txt)
if start >= 0 {
println('Match (${start}, ${end}) => [${txt[start..end]}]')
} else {
println('No Match')
}
if re.group_csave_flag == true && start >= 0 && re.group_csave.len > 0 {
println('cg: ${re.group_csave}')
mut cs_i := 1
for cs_i < re.group_csave[0] * 3 {
g_id := re.group_csave[cs_i]
st := re.group_csave[cs_i + 1]
en := re.group_csave[cs_i + 2]
println('cg[${g_id}] ${st} ${en}:[${txt[st..en]}]')
cs_i += 3
}
}
```
The output will be:
```v
String: http://www.ciao.mondo/hello/pippo12_/pera.html
Query : #0(?P<format>https?)|{8,14}#0(?P<format>ftps?)://#1(?P<token>[\w_]+.)+
Match (0, 46) => [http://www.ciao.mondo/hello/pippo12_/pera.html]
cg: [8, 0, 0, 4, 1, 7, 11, 1, 11, 16, 1, 16, 22, 1, 22, 28, 1, 28, 37, 1, 37, 42, 1, 42, 46]
cg[0] 0 4:[http]
cg[1] 7 11:[www.]
cg[1] 11 16:[ciao.]
cg[1] 16 22:[mondo/]
cg[1] 22 28:[hello/]
cg[1] 28 37:[pippo12_/]
cg[1] 37 42:[pera.]
cg[1] 42 46:[html]
```v
#### Named capturing groups
This regex module supports partially the question mark ? PCRE syntax for groups.
- `(?:abcd)` non capturing group: the content of the group will not be saved.
- `(?P<mygroup>abcdef)` named group: the group content is saved and labeled as mygroup.
The label of the groups is saved in the `group_map` of the `RE` struct, that is a map from `string` to `int`, where the value is the index in `group_csave` list of indexes.
Here is an example for how to use them:
```v
import regex
txt := 'http://www.ciao.mondo/hello/pippo12_/pera.html'
query := r'(?P<format>https?)|(?P<format>ftps?)://(?P<token>[\w_]+.)+'
mut re := regex.regex_opt(query) or { panic(err) }
// println(re.get_code()) // uncomment to see the print of the regex execution code
re.debug = 2 // enable maximum log
println('String: ${txt}')
println('Query : ${re.get_query()}')
re.debug = 0 // disable log
start, end := re.match_string(txt)
if start >= 0 {
println('Match (${start}, ${end}) => [${txt[start..end]}]')
} else {
println('No Match')
}
for name in re.group_map.keys() {
println('group:${name} \t=> [${re.get_group_by_name(txt, name)}] \
}] bounds: ${re.get_group_bounds_by_name(name)}')
}
```
Output:
```
String: http://www.ciao.mondo/hello/pippo12_/pera.html
Query : #0(?P<format>https?)|{8,14}#0(?P<format>ftps?)://#1(?P<token>[\w_]+.)+
Match (0, 46) => [http://www.ciao.mondo/hello/pippo12_/pera.html]
group:format => [http] bounds: (0, 4)
group:token => [html] bounds: (42, 46)
```
In order to simplify the use of the named groups, it is possible to use a name map in the re struct, using the function `re.get_group_by_name`.
Here is a more complex example of using them:
```v
import regex
// This function demonstrate the use of the named groups
fn convert_html_rgb_n(in_col string) u32 {
mut n_digit := if in_col.len == 4 { 1 } else { 2 }
mut col_mul := if in_col.len == 4 { 4 } else { 0 }
query := r'#(?P<red>[a-fA-F0-9]{${n_digit}})(?P<green>[a-fA-F0-9]{${n_digit}})(?P<blue>[a-fA-F0-9]{${n_digit}})'
mut re := regex.regex_opt(query) or { panic(err) }
start, end := re.match_string(in_col)
println('start: ${start}, end: ${end}')
mut res := u32(0)
if start >= 0 {
red_s, red_e := re.get_group_bounds_by_name('red')
r := ('0x' + in_col[red_s..red_e]).int() << col_mul
green_s, green_e := re.get_group_bounds_by_name('green')
g := ('0x' + in_col[green_s..green_e]).int() << col_mul
blue_s, blue_e := re.get_group_bounds_by_name('blue')
b := ('0x' + in_col[blue_s..blue_e]).int() << col_mul
println('r: ${r} g: ${g} b: ${b}')
res = u32(r) << 16 | u32(g) << 8 | u32(b)
}
return res
}
```
Other utilities are `get_group_by_name` and `get_group_bounds_by_name`, that return the string of a group using its name:
```v
txt := 'my used string....'
for name in re.group_map.keys() {
println('group:${name} \t=> [${re.get_group_by_name(txt, name)}] \
}] bounds: ${re.get_group_bounds_by_name(name)}')
}
```
#### Groups query functions
These functions are helpers to query the captured groups
```v
// get_group_bounds_by_name get a group boundaries by its name
pub fn (re RE) get_group_bounds_by_name(group_name string) (int, int)
// get_group_by_name get a group string by its name
pub fn (re RE) get_group_by_name(group_name string) string
// get_group_by_id get a group boundaries by its id
pub fn (re RE) get_group_bounds_by_id(group_id int) (int, int)
// get_group_by_id get a group string by its id
pub fn (re RE) get_group_by_id(in_txt string, group_id int) string
struct Re_group {
pub:
start int = -1
end int = -1
}
// get_group_list return a list of Re_group for the found groups
pub fn (re RE) get_group_list() []Re_group
// get_group_list return a list of Re_group for the found groups
pub fn (re RE) get_group_list() []Re_group
Flags
It is possible to set some flags in the regex parser, that change the behavior of the parser itself.
```
#### init the regex struct with flags (optional)
```v
mut re := regex.new()
re.flag = regex.f_bin
// f_bin: parse a string as bytes, utf-8 management disabled.
// f_efm: exit on the first char matches in the query, used by thefind function.
//f_ms: matches only if the index of the start match is 0,same as ^ at the start of the query string.
// f_me: matches only if the end index of the match is the last charof the input string, same as $ end of query string.
// f_nl: stop the matching if found a new line char \n or \r
```
## Simplified initializer
```v
// regex create a regex object from the query string and compile it
pub fn regex_opt(in_query string) ?RE
// new create a RE of small size, usually sufficient for ordinary use
pub fn new() RE
//After an initializer is used, the regex expression must be compiled with:
// compile_opt compile RE pattern string, returning an error if the compilation fails
pub fn (mut re RE) compile_opt(pattern string) !
```
## Matching Functions
```v
// match_string try to match the input string, return start and end index if found else start is -1
pub fn (mut re RE) match_string(in_txt string) (int, int)
```
## Find functions
```v
// find try to find the first match in the input string
// return start and end index if found else start is -1
pub fn (mut re RE) find(in_txt string) (int, int)
// find_all find all the "non overlapping" occurrences of the matching pattern
// return a list of start end indexes like: [3,4,6,8]
// the matches are [3,4] and [6,8]
pub fn (mut re RE) find_all(in_txt string) []int
// find_all_str find all the "non overlapping" occurrences of the match pattern
// return a list of strings
// the result is like ['first match','secon match']
pub fn (mut re RE) find_all_str(in_txt string) []string
```
## Replace functions
```v
// replace return a string where the matches are replaced with the repl_str string,
// this function supports groups in the replace string
pub fn (mut re RE) replace(in_txt string, repl string) string
//replace string can include groups references:
txt := 'Today it is a good day.'
query := r'(a\w)[ ,.]'
mut re := regex.regex_opt(query)?
res := re.replace(txt, r'__[\0]__')
```
in above example we used the group 0 in the replace string: \0, the result will be:
Today it is a good day. => Tod__[ay]__it is a good d__[ay]__
Note > In the replace strings can be used only groups from 0 to 9.
If the usage of groups in the replace process, is not needed, it is possible to use a quick function:
```v
// replace_simple return a string where the matches are replaced with the replace string
pub fn (mut re RE) replace_simple(in_txt string, repl string) string
//If it is needed to replace N instances of the found strings it is possible to use:
// replace_n return a string where the first `count` matches are replaced with the repl_str string
// `count` indicate the number of max replacements that will be done.
// if count is > 0 the replace began from the start of the string toward the end
// if count is < 0 the replace began from the end of the string toward the start
// if count is 0 do nothing
pub fn (mut re RE) replace_n(in_txt string, repl_str string, count int) string
//For complex find and replace operations, you can use replace_by_fn . The replace_by_fn, uses a custom replace callback function, thus allowing customizations. The custom callback function is called for every non overlapped find.
// The custom callback function must be of the type:
// type of function used for custom replace
// in_txt source text
// start index of the start of the match in in_txt
// end index of the end of the match in in_txt
// --- the match is in in_txt[start..end] ---
fn (re RE, in_txt string, start int, end int) string
The following example will clarify its usage:
```
customized replace function example
```v
import regex
//
// it will be called on each non overlapped find
fn my_repl(re regex.RE, in_txt string, start int, end int) string {
g0 := re.get_group_by_id(in_txt, 0)
g1 := re.get_group_by_id(in_txt, 1)
g2 := re.get_group_by_id(in_txt, 2)
return'*${g0}*${g1}*${g2}*'
}
fn main() {
txt := 'today [John] is gone to his house with (Jack) and [Marie].'
query := r'(.)(\A\w+)(.)'
mut re := regex.regex_opt(query) or { panic(err) }
result := re.replace_by_fn(txt, my_repl)
println(result)
}
```
Output:
```txt
today *[*John*]* is gone to his house with *(*Jack*)* and *[*Marie*]*.
```

View File

@@ -0,0 +1,49 @@
module net.smtp
fn new_client(config Client) !&Client
new_client returns a new SMTP client and connects to it
enum BodyType {
text
html
}
struct Attachment {
filename string
bytes []u8
cid string
}
struct Client {
mut:
conn net.TcpConn
ssl_conn &ssl.SSLConn = unsafe { nil }
reader ?&io.BufferedReader
pub:
server string
port int = 25
username string
password string
from string
ssl bool
starttls bool
pub mut:
is_open bool
encrypted bool
}
fn (mut c Client) reconnect() !
reconnect reconnects to the SMTP server if the connection was closed
fn (mut c Client) send(config Mail) !
send sends an email
fn (mut c Client) quit() !
quit closes the connection to the server
struct Mail {
pub:
from string
to string
cc string
bcc string
date time.Time = time.now()
subject string
body_type BodyType
body string
attachments []Attachment
boundary string
}

View File

@@ -0,0 +1,311 @@
## Description
V's `time` module, provides utilities for working with time and dates:
- parsing of time values expressed in one of the commonly used standard time/date formats
- formatting of time values
- arithmetic over times/durations
- converting between local time and UTC (timezone support)
- stop watches for accurately measuring time durations
- sleeping for a period of time
## Examples
You can see the current time. [See](https://play.vlang.io/?query=c121a6dda7):
```v
import time
println(time.now())
```
`time.Time` values can be compared, [see](https://play.vlang.io/?query=133d1a0ce5):
```v
import time
const time_to_test = time.Time{
year: 1980
month: 7
day: 11
hour: 21
minute: 23
second: 42
nanosecond: 123456789
}
println(time_to_test.format())
assert '1980-07-11 21:23' == time_to_test.format()
assert '1980-07-11 21:23:42' == time_to_test.format_ss()
assert '1980-07-11 21:23:42.123' == time_to_test.format_ss_milli()
assert '1980-07-11 21:23:42.123456' == time_to_test.format_ss_micro()
assert '1980-07-11 21:23:42.123456789' == time_to_test.format_ss_nano()
```
You can also parse strings to produce time.Time values,
[see](https://play.vlang.io/p/b02ca6027f):
```v
import time
s := '2018-01-27 12:48:34'
t := time.parse(s) or { panic('failing format: ${s} | err: ${err}') }
println(t)
println(t.unix())
```
V's time module also has these parse methods:
```v ignore
fn parse(s string) !Time
fn parse_iso8601(s string) !Time
fn parse_rfc2822(s string) !Time
fn parse_rfc3339(s string) !Time
```
Another very useful feature of the `time` module is the stop watch,
for when you want to measure short time periods, elapsed while you
executed other tasks. [See](https://play.vlang.io/?query=f6c008bc34):
```v
import time
fn do_something() {
time.sleep(510 * time.millisecond)
}
fn main() {
sw := time.new_stopwatch()
do_something()
println('Note: do_something() took: ${sw.elapsed().milliseconds()} ms')
}
```
```vlang
module time
const second = Duration(1000 * millisecond)
const long_months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
'September', 'October', 'November', 'December']
const nanosecond = Duration(1)
const absolute_zero_year = i64(-292277022399)
const days_string = 'MonTueWedThuFriSatSun'
const long_days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']!
const month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]!
const seconds_per_hour = 60 * seconds_per_minute
const millisecond = Duration(1000 * microsecond)
const days_per_4_years = days_in_year * 4 + 1
const microsecond = Duration(1000 * nanosecond)
const days_per_400_years = days_in_year * 400 + 97
const minute = Duration(60 * second)
const days_before = [
0,
31,
31 + 28,
31 + 28 + 31,
31 + 28 + 31 + 30,
31 + 28 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
]!
const months_string = 'JanFebMarAprMayJunJulAugSepOctNovDec'
const seconds_per_week = 7 * seconds_per_day
const hour = Duration(60 * minute)
const days_per_100_years = days_in_year * 100 + 24
const seconds_per_minute = 60
const days_in_year = 365
const infinite = Duration(i64(9223372036854775807))
const seconds_per_day = 24 * seconds_per_hour
fn date_from_days_after_unix_epoch(days int) Time
fn day_of_week(y int, m int, d int) int
fn days_from_unix_epoch(year int, month int, day int) int
fn days_in_month(month int, year int) !int
fn is_leap_year(year int) bool
fn new(t Time) Time
fn new_stopwatch(opts StopWatchOptions) StopWatch
fn new_time(t Time) Time
fn now() Time
fn offset() int
fn parse(s string) !Time
fn parse_format(s string, format string) !Time
fn parse_iso8601(s string) !Time
fn parse_rfc2822(s string) !Time
fn parse_rfc3339(s string) !Time
fn portable_timegm(t &C.tm) i64
fn since(t Time) Duration
fn sleep(duration Duration)
fn sys_mono_now() u64
fn ticks() i64
fn unix(epoch i64) Time
fn unix2(epoch i64, microsecond int) Time
fn unix_microsecond(epoch i64, microsecond int) Time
fn unix_nanosecond(abs_unix_timestamp i64, nanosecond int) Time
fn utc() Time
fn Time.new(t Time) Time
type Duration = i64
fn (d Duration) days() f64
fn (d Duration) debug() string
fn (d Duration) hours() f64
fn (d Duration) microseconds() i64
fn (d Duration) milliseconds() i64
fn (d Duration) minutes() f64
fn (d Duration) nanoseconds() i64
fn (d Duration) seconds() f64
fn (d Duration) str() string
fn (d Duration) sys_milliseconds() int
fn (d Duration) timespec() C.timespec
enum FormatDate {
ddmmyy
ddmmyyyy
mmddyy
mmddyyyy
mmmd
mmmdd
mmmddyy
mmmddyyyy
no_date
yyyymmdd
yymmdd
}
enum FormatDelimiter {
dot
hyphen
slash
space
no_delimiter
}
enum FormatTime {
hhmm12
hhmm24
hhmmss12
hhmmss24
hhmmss24_milli
hhmmss24_micro
hhmmss24_nano
no_time
}
struct C.mach_timebase_info_data_t {
numer u32
denom u32
}
struct C.timespec {
pub mut:
tv_sec i64
tv_nsec i64
}
struct C.timeval {
pub:
tv_sec u64
tv_usec u64
}
struct C.tm {
pub mut:
tm_sec int
tm_min int
tm_hour int
tm_mday int
tm_mon int
tm_year int
tm_wday int
tm_yday int
tm_isdst int
tm_gmtoff int
}
struct StopWatch {
mut:
elapsed u64
pub mut:
start u64
end u64
}
fn (mut t StopWatch) start()
fn (mut t StopWatch) restart()
fn (mut t StopWatch) stop()
fn (mut t StopWatch) pause()
fn (t StopWatch) elapsed() Duration
struct StopWatchOptions {
pub:
auto_start bool = true
}
struct Time {
unix i64
pub:
year int
month int
day int
hour int
minute int
second int
nanosecond int
is_local bool // used to make time.now().local().local() == time.now().local()
microsecond int @[deprecated: 'use t.nanosecond / 1000 instead'; deprecated_after: '2023-08-05']
}
fn (lhs Time) - (rhs Time) Duration
fn (t1 Time) < (t2 Time) bool
fn (t1 Time) == (t2 Time) bool
fn (t Time) add(duration_in_nanosecond Duration) Time
fn (t Time) add_days(days int) Time
fn (t Time) add_seconds(seconds int) Time
fn (t Time) as_local() Time
fn (t Time) as_utc() Time
fn (t Time) clean() string
fn (t Time) clean12() string
fn (t Time) custom_format(s string) string
fn (t Time) day_of_week() int
fn (t Time) days_from_unix_epoch() int
fn (t Time) ddmmy() string
fn (t Time) debug() string
fn (t Time) format() string
fn (t Time) format_rfc3339() string
fn (t Time) format_rfc3339_nano() string
fn (t Time) format_ss() string
fn (t Time) format_ss_micro() string
fn (t Time) format_ss_milli() string
fn (t Time) format_ss_nano() string
fn (t Time) get_fmt_date_str(fmt_dlmtr FormatDelimiter, fmt_date FormatDate) string
fn (t Time) get_fmt_str(fmt_dlmtr FormatDelimiter, fmt_time FormatTime, fmt_date FormatDate) string
fn (t Time) get_fmt_time_str(fmt_time FormatTime) string
fn (t Time) hhmm() string
fn (t Time) hhmm12() string
fn (t Time) hhmmss() string
fn (t Time) http_header_string() string
fn (t Time) is_utc() bool
fn (t Time) local() Time
fn (t Time) local_to_utc() Time
fn (t Time) long_weekday_str() string
fn (t Time) md() string
fn (t Time) relative() string
fn (t Time) relative_short() string
fn (t Time) smonth() string
fn (t Time) str() string
fn (t Time) strftime(fmt string) string
fn (t Time) unix() i64
fn (t Time) unix_micro() i64
fn (t Time) unix_milli() i64
fn (t Time) unix_nano() i64
fn (t Time) unix_time() i64
fn (t Time) unix_time_micro() i64
fn (t Time) unix_time_milli() i64
fn (t Time) unix_time_nano() i64
fn (t Time) utc_string() string
fn (u Time) utc_to_local() Time
fn (t Time) weekday_str() string
fn (t Time) year_day() int
fn (t Time) ymmdd() string
struct TimeParseError {
Error
code int
message string
}
fn (err TimeParseError) msg() string
```

File diff suppressed because it is too large Load Diff