Compare commits

..

No commits in common. "b86c6b5e16b59f536d0d2fd06e0b2663135e00f3" and "d538e25da3ff17b56707ea35efc6b685289683d5" have entirely different histories.

8 changed files with 146 additions and 152 deletions

View File

@ -0,0 +1,55 @@
module archive
import os
// Returns the .PKGINFO file's contents & the list of files.
pub fn pkg_info(pkg_path string) ?(string, []string) {
if !os.is_file(pkg_path) {
return error("'$pkg_path' doesn't exist or isn't a file.")
}
a := C.archive_read_new()
entry := C.archive_entry_new()
mut r := 0
// Sinds 2020, all newly built Arch packages use zstd
C.archive_read_support_filter_zstd(a)
// The content should always be a tarball
C.archive_read_support_format_tar(a)
// TODO find out where does this 10240 come from
r = C.archive_read_open_filename(a, &char(pkg_path.str), 10240)
defer {
C.archive_read_free(a)
}
if r != C.ARCHIVE_OK {
return error('Failed to open package.')
}
// We iterate over every header in search of the .PKGINFO one
mut buf := voidptr(0)
mut files := []string{}
for C.archive_read_next_header(a, &entry) == C.ARCHIVE_OK {
pathname := C.archive_entry_pathname(entry)
ignored_names := [c'.BUILDINFO', c'.INSTALL', c'.MTREE', c'.PKGINFO', c'.CHANGELOG']
if ignored_names.all(C.strcmp(it, pathname) != 0) {
unsafe {
files << cstring_to_vstring(pathname)
}
}
if C.strcmp(pathname, c'.PKGINFO') == 0 {
size := C.archive_entry_size(entry)
// TODO can this unsafe block be avoided?
buf = unsafe { malloc(size) }
C.archive_read_data(a, voidptr(buf), size)
} else {
C.archive_read_data_skip(a)
}
}
return unsafe { cstring_to_vstring(&char(buf)) }, files
}

View File

@ -0,0 +1,50 @@
module archive
#flag -larchive
#include "archive.h"
struct C.archive {}
// Create a new archive struct
fn C.archive_read_new() &C.archive
// Configure the archive to work with zstd compression
fn C.archive_read_support_filter_zstd(&C.archive)
// Configure the archive to work with a tarball content
fn C.archive_read_support_format_tar(&C.archive)
// Open an archive for reading
fn C.archive_read_open_filename(&C.archive, &char, int) int
// Go to next entry header in archive
fn C.archive_read_next_header(&C.archive, &&C.archive_entry) int
// Skip reading the current entry
fn C.archive_read_data_skip(&C.archive)
// Free an archive
fn C.archive_read_free(&C.archive) int
// Read an archive entry's contents into a pointer
fn C.archive_read_data(&C.archive, voidptr, int)
#include "archive_entry.h"
struct C.archive_entry {}
// Create a new archive_entry struct
fn C.archive_entry_new() &C.archive_entry
// Get the filename of the given entry
fn C.archive_entry_pathname(&C.archive_entry) &char
// Get an entry's file size
// Note: this function actually returns an i64, but as this can't be used as an arugment to malloc, we'll just roll with it & assume an entry is never bigger than 4 gigs
fn C.archive_entry_size(&C.archive_entry) int
#include <string.h>
// Compare two C strings; 0 means they're equal
fn C.strcmp(&char, &char) int

View File

@ -5,6 +5,7 @@ import os
import log import log
import io import io
import pkg import pkg
import archive
import repo import repo
const port = 8000 const port = 8000
@ -109,5 +110,7 @@ fn main() {
return return
} }
// println(info) // println(info)
print(res) println(res.info)
print(res.files)
println(res.info.to_desc())
} }

108
src/pkg.v
View File

@ -1,65 +1,14 @@
module pkg module pkg
import archive
import time import time
import os
#flag -larchive
#include "archive.h"
struct C.archive {}
// Create a new archive struct
fn C.archive_read_new() &C.archive
// Configure the archive to work with zstd compression
fn C.archive_read_support_filter_zstd(&C.archive)
// Configure the archive to work with a tarball content
fn C.archive_read_support_format_tar(&C.archive)
// Open an archive for reading
fn C.archive_read_open_filename(&C.archive, &char, int) int
// Go to next entry header in archive
fn C.archive_read_next_header(&C.archive, &&C.archive_entry) int
// Skip reading the current entry
fn C.archive_read_data_skip(&C.archive)
// Free an archive
fn C.archive_read_free(&C.archive) int
// Read an archive entry's contents into a pointer
fn C.archive_read_data(&C.archive, voidptr, int)
#include "archive_entry.h"
struct C.archive_entry {}
// Create a new archive_entry struct
fn C.archive_entry_new() &C.archive_entry
// Get the filename of the given entry
fn C.archive_entry_pathname(&C.archive_entry) &char
// Get an entry's file size
// Note: this function actually returns an i64, but as this can't be used as an arugment to malloc, we'll just roll with it & assume an entry is never bigger than 4 gigs
fn C.archive_entry_size(&C.archive_entry) int
#include <string.h>
// Compare two C strings; 0 means they're equal
fn C.strcmp(&char, &char) int
// Represents a read archive
struct Pkg { struct Pkg {
pub: pub:
info PkgInfo [required] info PkgInfo [required]
files []string [required] files []string [required]
} }
// Represents the contents of a .PKGINFO file
struct PkgInfo { struct PkgInfo {
mut: mut:
// Single values // Single values
@ -89,7 +38,6 @@ mut:
checkdepends []string checkdepends []string
} }
// parse_pkg_info_string parses a PkgInfo object from a string
fn parse_pkg_info_string(pkg_info_str &string) ?PkgInfo { fn parse_pkg_info_string(pkg_info_str &string) ?PkgInfo {
mut pkg_info := PkgInfo{} mut pkg_info := PkgInfo{}
@ -141,57 +89,9 @@ fn parse_pkg_info_string(pkg_info_str &string) ?PkgInfo {
return pkg_info return pkg_info
} }
// read_pkg extracts the file list & .PKGINFO contents from an archive
// NOTE: this command currently only supports zstd-compressed tarballs
pub fn read_pkg(pkg_path string) ?Pkg { pub fn read_pkg(pkg_path string) ?Pkg {
if !os.is_file(pkg_path) { pkg_info_str, files := archive.pkg_info(pkg_path) ?
return error("'$pkg_path' doesn't exist or isn't a file.") pkg_info := parse_pkg_info_string(pkg_info_str) ?
}
a := C.archive_read_new()
entry := C.archive_entry_new()
mut r := 0
// Sinds 2020, all newly built Arch packages use zstd
C.archive_read_support_filter_zstd(a)
// The content should always be a tarball
C.archive_read_support_format_tar(a)
// TODO find out where does this 10240 come from
r = C.archive_read_open_filename(a, &char(pkg_path.str), 10240)
defer {
C.archive_read_free(a)
}
if r != C.ARCHIVE_OK {
return error('Failed to open package.')
}
mut buf := voidptr(0)
mut files := []string{}
for C.archive_read_next_header(a, &entry) == C.ARCHIVE_OK {
pathname := C.archive_entry_pathname(entry)
ignored_names := [c'.BUILDINFO', c'.INSTALL', c'.MTREE', c'.PKGINFO', c'.CHANGELOG']
if ignored_names.all(C.strcmp(it, pathname) != 0) {
unsafe {
files << cstring_to_vstring(pathname)
}
}
if C.strcmp(pathname, c'.PKGINFO') == 0 {
size := C.archive_entry_size(entry)
// TODO can this unsafe block be avoided?
buf = unsafe { malloc(size) }
C.archive_read_data(a, voidptr(buf), size)
} else {
C.archive_read_data_skip(a)
}
}
pkg_info := parse_pkg_info_string(unsafe { cstring_to_vstring(&char(buf)) }) ?
return Pkg{ return Pkg{
info: pkg_info info: pkg_info
@ -199,7 +99,7 @@ pub fn read_pkg(pkg_path string) ?Pkg {
} }
} }
// to_desc returns a desc file valid string representation // Represent a PkgInfo struct as a desc file
pub fn (p &PkgInfo) to_desc() string { pub fn (p &PkgInfo) to_desc() string {
// TODO calculate md5 & sha256 instead of believing the file // TODO calculate md5 & sha256 instead of believing the file
mut desc := '' mut desc := ''

View File

@ -1,6 +1,7 @@
module repo module repo
import os import os
import archive
const pkgs_subpath = 'pkgs' const pkgs_subpath = 'pkgs'
@ -21,37 +22,35 @@ pub:
pkg_dir string [required] pkg_dir string [required]
} }
// contains returns whether the repository contains the given package. // Returns whether the repository contains the given package.
pub fn (r &Repo) contains(pkg string) bool { pub fn (r &Repo) contains(pkg string) bool {
return os.exists(os.join_path(r.repo_dir, 'files', pkg)) return os.exists(os.join_path(r.repo_dir, 'files', pkg))
} }
// add adds the given package to the repo. If false, the package was already // Adds the given package to the repo. If false, the package was already
// present in the repository. // present in the repository.
pub fn (r &Repo) add(pkg string) ?bool { pub fn (r &Repo) add(pkg string) ?bool {
return false return false
} }
// generate re-generates the db & files archives. // Re-generate the db & files archives.
fn (r &Repo) genenerate() ? { fn (r &Repo) genenerate() ? {
} }
// pkg_path returns path to the given package, prepended with the repo's path. // Returns path to the given package, prepended with the repo's path.
pub fn (r &Repo) pkg_path(pkg string) string { pub fn (r &Repo) pkg_path(pkg string) string {
return os.join_path_single(r.pkg_dir, pkg) return os.join_path_single(r.pkg_dir, pkg)
} }
// exists checks whether a package file exists
pub fn (r &Repo) exists(pkg string) bool { pub fn (r &Repo) exists(pkg string) bool {
return os.exists(r.pkg_path(pkg)) return os.exists(r.pkg_path(pkg))
} }
// db_path returns the full path to the database file // Returns the full path to the database file
pub fn (r &Repo) db_path() string { pub fn (r &Repo) db_path() string {
return os.join_path_single(r.repo_dir, 'repo.tar.gz') return os.join_path_single(r.repo_dir, 'repo.tar.gz')
} }
// add_package adds a package to the repository
pub fn (r &Repo) add_package(pkg_path string) ? { pub fn (r &Repo) add_package(pkg_path string) ? {
mut res := os.Result{} mut res := os.Result{}

View File

@ -7,7 +7,6 @@ import time
const prefixes = ['B', 'KB', 'MB', 'GB'] const prefixes = ['B', 'KB', 'MB', 'GB']
// pretty_bytes converts a byte count to human-readable version
fn pretty_bytes(bytes int) string { fn pretty_bytes(bytes int) string {
mut i := 0 mut i := 0
mut n := f32(bytes) mut n := f32(bytes)
@ -24,7 +23,6 @@ fn is_pkg_name(s string) bool {
return s.contains('.pkg') return s.contains('.pkg')
} }
// get_root handles a GET request for a file on the root
['/:filename'; get] ['/:filename'; get]
fn (mut app App) get_root(filename string) web.Result { fn (mut app App) get_root(filename string) web.Result {
mut full_path := '' mut full_path := ''
@ -90,7 +88,6 @@ fn (mut app App) get_root(filename string) web.Result {
// return app.text('Package added successfully.') // return app.text('Package added successfully.')
// } // }
// add_package PUT a new package to the server
['/add'; put] ['/add'; put]
pub fn (mut app App) add_package() web.Result { pub fn (mut app App) add_package() web.Result {
return app.text('') return app.text('')

View File

@ -2,34 +2,28 @@ module web
import log import log
// log reate a log message with the given level
pub fn (mut ctx Context) log(msg &string, level log.Level) { pub fn (mut ctx Context) log(msg &string, level log.Level) {
lock ctx.logger { lock ctx.logger {
ctx.logger.send_output(msg, level) ctx.logger.send_output(msg, level)
} }
} }
// lfatal create a log message with the fatal level
pub fn (mut ctx Context) lfatal(msg &string) { pub fn (mut ctx Context) lfatal(msg &string) {
ctx.log(msg, log.Level.fatal) ctx.log(msg, log.Level.fatal)
} }
// lerror create a log message with the error level
pub fn (mut ctx Context) lerror(msg &string) { pub fn (mut ctx Context) lerror(msg &string) {
ctx.log(msg, log.Level.error) ctx.log(msg, log.Level.error)
} }
// lwarn create a log message with the warn level
pub fn (mut ctx Context) lwarn(msg &string) { pub fn (mut ctx Context) lwarn(msg &string) {
ctx.log(msg, log.Level.warn) ctx.log(msg, log.Level.warn)
} }
// linfo create a log message with the info level
pub fn (mut ctx Context) linfo(msg &string) { pub fn (mut ctx Context) linfo(msg &string) {
ctx.log(msg, log.Level.info) ctx.log(msg, log.Level.info)
} }
// ldebug create a log message with the debug level
pub fn (mut ctx Context) ldebug(msg &string) { pub fn (mut ctx Context) ldebug(msg &string) {
ctx.log(msg, log.Level.debug) ctx.log(msg, log.Level.debug)
} }

View File

@ -187,14 +187,14 @@ struct Route {
} }
// Defining this method is optional. // Defining this method is optional.
// init_server is called at server start. // This method called at server start.
// You can use it for initializing globals. // You can use it for initializing globals.
pub fn (ctx Context) init_server() { pub fn (ctx Context) init_server() {
eprintln('init_server() has been deprecated, please init your web app in `fn main()`') eprintln('init_server() has been deprecated, please init your web app in `fn main()`')
} }
// Defining this method is optional. // Defining this method is optional.
// before_request is called before every request (aka middleware). // This method called before every request (aka middleware).
// Probably you can use it for check user session cookie or add header. // Probably you can use it for check user session cookie or add header.
pub fn (ctx Context) before_request() {} pub fn (ctx Context) before_request() {}
@ -206,7 +206,7 @@ pub struct Cookie {
http_only bool http_only bool
} }
// send_response_to_client sends a response to the client // web intern function
[manualfree] [manualfree]
pub fn (mut ctx Context) send_response_to_client(mimetype string, res string) bool { pub fn (mut ctx Context) send_response_to_client(mimetype string, res string) bool {
if ctx.done { if ctx.done {
@ -230,33 +230,33 @@ pub fn (mut ctx Context) send_response_to_client(mimetype string, res string) bo
return true return true
} }
// html HTTP_OK with s as payload with content-type `text/html` // Response HTTP_OK with s as payload with content-type `text/html`
pub fn (mut ctx Context) html(s string) Result { pub fn (mut ctx Context) html(s string) Result {
ctx.send_response_to_client('text/html', s) ctx.send_response_to_client('text/html', s)
return Result{} return Result{}
} }
// text HTTP_OK with s as payload with content-type `text/plain` // Response HTTP_OK with s as payload with content-type `text/plain`
pub fn (mut ctx Context) text(s string) Result { pub fn (mut ctx Context) text(s string) Result {
ctx.send_response_to_client('text/plain', s) ctx.send_response_to_client('text/plain', s)
return Result{} return Result{}
} }
// json<T> HTTP_OK with json_s as payload with content-type `application/json` // Response HTTP_OK with json_s as payload with content-type `application/json`
pub fn (mut ctx Context) json<T>(j T) Result { pub fn (mut ctx Context) json<T>(j T) Result {
json_s := json.encode(j) json_s := json.encode(j)
ctx.send_response_to_client('application/json', json_s) ctx.send_response_to_client('application/json', json_s)
return Result{} return Result{}
} }
// json_pretty<T> Response HTTP_OK with a pretty-printed JSON result // Response HTTP_OK with a pretty-printed JSON result
pub fn (mut ctx Context) json_pretty<T>(j T) Result { pub fn (mut ctx Context) json_pretty<T>(j T) Result {
json_s := json.encode_pretty(j) json_s := json.encode_pretty(j)
ctx.send_response_to_client('application/json', json_s) ctx.send_response_to_client('application/json', json_s)
return Result{} return Result{}
} }
// file Response HTTP_OK with file as payload // Response HTTP_OK with file as payload
// This function manually implements responses because it needs to stream the file contents // This function manually implements responses because it needs to stream the file contents
pub fn (mut ctx Context) file(f_path string) Result { pub fn (mut ctx Context) file(f_path string) Result {
if ctx.done { if ctx.done {
@ -329,13 +329,13 @@ pub fn (mut ctx Context) file(f_path string) Result {
return Result{} return Result{}
} }
// ok Response HTTP_OK with s as payload // Response HTTP_OK with s as payload
pub fn (mut ctx Context) ok(s string) Result { pub fn (mut ctx Context) ok(s string) Result {
ctx.send_response_to_client(ctx.content_type, s) ctx.send_response_to_client(ctx.content_type, s)
return Result{} return Result{}
} }
// server_error Response a server error // Response a server error
pub fn (mut ctx Context) server_error(ecode int) Result { pub fn (mut ctx Context) server_error(ecode int) Result {
$if debug { $if debug {
eprintln('> ctx.server_error ecode: $ecode') eprintln('> ctx.server_error ecode: $ecode')
@ -347,7 +347,7 @@ pub fn (mut ctx Context) server_error(ecode int) Result {
return Result{} return Result{}
} }
// redirect Redirect to an url // Redirect to an url
pub fn (mut ctx Context) redirect(url string) Result { pub fn (mut ctx Context) redirect(url string) Result {
if ctx.done { if ctx.done {
return Result{} return Result{}
@ -360,7 +360,7 @@ pub fn (mut ctx Context) redirect(url string) Result {
return Result{} return Result{}
} }
// not_found Send an not_found response // Send an not_found response
pub fn (mut ctx Context) not_found() Result { pub fn (mut ctx Context) not_found() Result {
if ctx.done { if ctx.done {
return Result{} return Result{}
@ -370,7 +370,7 @@ pub fn (mut ctx Context) not_found() Result {
return Result{} return Result{}
} }
// set_cookie Sets a cookie // Sets a cookie
pub fn (mut ctx Context) set_cookie(cookie Cookie) { pub fn (mut ctx Context) set_cookie(cookie Cookie) {
mut cookie_data := []string{} mut cookie_data := []string{}
mut secure := if cookie.secure { 'Secure;' } else { '' } mut secure := if cookie.secure { 'Secure;' } else { '' }
@ -383,17 +383,17 @@ pub fn (mut ctx Context) set_cookie(cookie Cookie) {
ctx.add_header('Set-Cookie', '$cookie.name=$cookie.value; $data') ctx.add_header('Set-Cookie', '$cookie.name=$cookie.value; $data')
} }
// set_content_type Sets the response content type // Sets the response content type
pub fn (mut ctx Context) set_content_type(typ string) { pub fn (mut ctx Context) set_content_type(typ string) {
ctx.content_type = typ ctx.content_type = typ
} }
// set_cookie_with_expire_date Sets a cookie with a `expire_data` // Sets a cookie with a `expire_data`
pub fn (mut ctx Context) set_cookie_with_expire_date(key string, val string, expire_date time.Time) { pub fn (mut ctx Context) set_cookie_with_expire_date(key string, val string, expire_date time.Time) {
ctx.add_header('Set-Cookie', '$key=$val; Secure; HttpOnly; expires=$expire_date.utc_string()') ctx.add_header('Set-Cookie', '$key=$val; Secure; HttpOnly; expires=$expire_date.utc_string()')
} }
// get_cookie Gets a cookie by a key // Gets a cookie by a key
pub fn (ctx &Context) get_cookie(key string) ?string { // TODO refactor pub fn (ctx &Context) get_cookie(key string) ?string { // TODO refactor
mut cookie_header := ctx.get_header('cookie') mut cookie_header := ctx.get_header('cookie')
if cookie_header == '' { if cookie_header == '' {
@ -413,7 +413,7 @@ pub fn (ctx &Context) get_cookie(key string) ?string { // TODO refactor
return error('Cookie not found') return error('Cookie not found')
} }
// set_status Sets the response status // Sets the response status
pub fn (mut ctx Context) set_status(code int, desc string) { pub fn (mut ctx Context) set_status(code int, desc string) {
if code < 100 || code > 599 { if code < 100 || code > 599 {
ctx.status = '500 Internal Server Error' ctx.status = '500 Internal Server Error'
@ -422,12 +422,12 @@ pub fn (mut ctx Context) set_status(code int, desc string) {
} }
} }
// add_header Adds an header to the response with key and val // Adds an header to the response with key and val
pub fn (mut ctx Context) add_header(key string, val string) { pub fn (mut ctx Context) add_header(key string, val string) {
ctx.header.add_custom(key, val) or {} ctx.header.add_custom(key, val) or {}
} }
// get_header Returns the header data from the key // Returns the header data from the key
pub fn (ctx &Context) get_header(key string) string { pub fn (ctx &Context) get_header(key string) string {
return ctx.req.header.get_custom(key) or { '' } return ctx.req.header.get_custom(key) or { '' }
} }
@ -436,7 +436,7 @@ interface DbInterface {
db voidptr db voidptr
} }
// run runs the app // run_app
[manualfree] [manualfree]
pub fn run<T>(global_app &T, port int) { pub fn run<T>(global_app &T, port int) {
mut l := net.listen_tcp(.ip6, ':$port') or { panic('failed to listen $err.code $err') } mut l := net.listen_tcp(.ip6, ':$port') or { panic('failed to listen $err.code $err') }
@ -478,7 +478,6 @@ pub fn run<T>(global_app &T, port int) {
} }
} }
// handle_conn handles a connection
[manualfree] [manualfree]
fn handle_conn<T>(mut conn net.TcpConn, mut app T, routes map[string]Route) { fn handle_conn<T>(mut conn net.TcpConn, mut app T, routes map[string]Route) {
conn.set_read_timeout(30 * time.second) conn.set_read_timeout(30 * time.second)
@ -616,7 +615,6 @@ fn handle_conn<T>(mut conn net.TcpConn, mut app T, routes map[string]Route) {
conn.write(web.http_404.bytes()) or {} conn.write(web.http_404.bytes()) or {}
} }
// route_matches returns wether a route matches
fn route_matches(url_words []string, route_words []string) ?[]string { fn route_matches(url_words []string, route_words []string) ?[]string {
// URL path should be at least as long as the route path // URL path should be at least as long as the route path
// except for the catchall route (`/:path...`) // except for the catchall route (`/:path...`)
@ -659,7 +657,7 @@ fn route_matches(url_words []string, route_words []string) ?[]string {
return params return params
} }
// serve_if_static<T> checks if request is for a static file and serves it // check if request is for a static file and serves it
// returns true if we served a static file, false otherwise // returns true if we served a static file, false otherwise
[manualfree] [manualfree]
fn serve_if_static<T>(mut app T, url urllib.URL) bool { fn serve_if_static<T>(mut app T, url urllib.URL) bool {
@ -678,7 +676,6 @@ fn serve_if_static<T>(mut app T, url urllib.URL) bool {
return true return true
} }
// scan_static_directory makes a static route for each file in a directory
fn (mut ctx Context) scan_static_directory(directory_path string, mount_path string) { fn (mut ctx Context) scan_static_directory(directory_path string, mount_path string) {
files := os.ls(directory_path) or { panic(err) } files := os.ls(directory_path) or { panic(err) }
if files.len > 0 { if files.len > 0 {
@ -698,7 +695,7 @@ fn (mut ctx Context) scan_static_directory(directory_path string, mount_path str
} }
} }
// handle_static Handles a directory static // Handles a directory static
// If `root` is set the mount path for the dir will be in '/' // If `root` is set the mount path for the dir will be in '/'
pub fn (mut ctx Context) handle_static(directory_path string, root bool) bool { pub fn (mut ctx Context) handle_static(directory_path string, root bool) bool {
if ctx.done || !os.exists(directory_path) { if ctx.done || !os.exists(directory_path) {
@ -727,7 +724,7 @@ pub fn (mut ctx Context) mount_static_folder_at(directory_path string, mount_pat
return true return true
} }
// serve_static Serves a file static // Serves a file static
// `url` is the access path on the site, `file_path` is the real path to the file, `mime_type` is the file type // `url` is the access path on the site, `file_path` is the real path to the file, `mime_type` is the file type
pub fn (mut ctx Context) serve_static(url string, file_path string) { pub fn (mut ctx Context) serve_static(url string, file_path string) {
ctx.static_files[url] = file_path ctx.static_files[url] = file_path
@ -736,7 +733,7 @@ pub fn (mut ctx Context) serve_static(url string, file_path string) {
ctx.static_mime_types[url] = web.mime_types[ext] ctx.static_mime_types[url] = web.mime_types[ext]
} }
// ip Returns the ip address from the current user // Returns the ip address from the current user
pub fn (ctx &Context) ip() string { pub fn (ctx &Context) ip() string {
mut ip := ctx.req.header.get(.x_forwarded_for) or { '' } mut ip := ctx.req.header.get(.x_forwarded_for) or { '' }
if ip == '' { if ip == '' {
@ -752,23 +749,22 @@ pub fn (ctx &Context) ip() string {
return ip return ip
} }
// error Set s to the form error // Set s to the form error
pub fn (mut ctx Context) error(s string) { pub fn (mut ctx Context) error(s string) {
println('web error: $s') println('web error: $s')
ctx.form_error = s ctx.form_error = s
} }
// not_found Returns an empty result // Returns an empty result
pub fn not_found() Result { pub fn not_found() Result {
return Result{} return Result{}
} }
// send_string
fn send_string(mut conn net.TcpConn, s string) ? { fn send_string(mut conn net.TcpConn, s string) ? {
conn.write(s.bytes()) ? conn.write(s.bytes()) ?
} }
// filter Do not delete. // Do not delete.
// It used by `vlib/v/gen/c/str_intp.v:130` for string interpolation inside web templates // It used by `vlib/v/gen/c/str_intp.v:130` for string interpolation inside web templates
// TODO: move it to template render // TODO: move it to template render
fn filter(s string) string { fn filter(s string) string {