diff --git a/Makefile b/Makefile index f936e4c6..ff0cc963 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,9 @@ .PHONY: run run: + API_KEY=test REPO_DIR=data LOG_LEVEL=DEBUG v run vieter + +.PHONY: watch +watch: API_KEY=test REPO_DIR=data LOG_LEVEL=DEBUG v watch run vieter .PHONY: fmt diff --git a/vieter/main.v b/vieter/main.v index 967dc9f7..29782987 100644 --- a/vieter/main.v +++ b/vieter/main.v @@ -1,16 +1,18 @@ module main -import vweb +import web import os import log +import io const port = 8000 +const buf_size = 1_000_000 struct App { - vweb.Context - api_key string [required; vweb_global] - repo_dir string [required; vweb_global] - logger log.Log [required; vweb_global] + web.Context + api_key string [required; web_global] + repo_dir string [required; web_global] + logger log.Log [required; web_global] } [noreturn] @@ -19,33 +21,83 @@ fn exit_with_message(code int, msg string) { exit(code) } -[post; '/publish'] -fn (mut app App) put_package(filename string) vweb.Result { - for _, files in app.files { - for file in files { - filepath := os.join_path_single(app.repo_dir, file.filename) +fn reader_to_file(mut reader io.BufferedReader, path string) ? { + // Open up a file for writing to + mut file := os.create(path) ? + defer { + file.close() + } - if os.exists(filepath) { - return app.text('File already exists.') + mut buf := []byte{len: buf_size} + + // Repeat as long as the stream still has data + for { + // TODO don't just endlessly loop if reading keeps failing + println('heey') + // TODO check if just breaking here is safe + bytes_read := reader.read(mut &buf) or { + println('youre here') + break + } + println(bytes_read) + + mut to_write := bytes_read + + for to_write > 0 { + // TODO don't just loop infinitely here + bytes_written := file.write(buf[bytes_read - to_write..bytes_read]) or { + println("$err.msg") + continue } + println(bytes_written) - os.write_file(filepath, file.data) or { - return app.text('Failed to upload file.') - } - - return app.text('yeet') - + to_write = to_write - bytes_written } } - return app.text('done') + println('File complete!') } +[put; '/pkgs/:pkg'] +fn (mut app App) put_package(pkg string) web.Result { + full_path := os.join_path_single(app.repo_dir, pkg) + + if os.exists(full_path) { + return app.text('File already exists.') + } + + reader_to_file(mut app.reader, full_path) or { + return app.text('Failed to upload file.') + } + + return app.text('just stop') +} + +// ['/publish'; post] +// fn (mut app App) put_package(filename string) web.Result { +// for _, files in app.files { +// for file in files { +// filepath := os.join_path_single(app.repo_dir, file.filename) + +// if os.exists(filepath) { +// return app.text('File already exists.') +// } + +// os.write_file(filepath, file.data) or { return app.text('Failed to upload file.') } + +// return app.text('yeet') +// } +// } + +// return app.text('done') +// } + fn main() { // Configure logger log_level_str := os.getenv_opt('LOG_LEVEL') or { 'WARN' } log_level := log.level_from_tag(log_level_str) or { - exit_with_message(1, 'Invalid log level. The allowed values are FATAL, ERROR, WARN, INFO & DEBUG.') } + exit_with_message(1, 'Invalid log level. The allowed values are FATAL, ERROR, WARN, INFO & DEBUG.') + } log_file := os.getenv_opt('LOG_FILE') or { 'vieter.log' } mut logger := log.Log{ @@ -62,7 +114,7 @@ fn main() { logger.info('Logger set up.') logger.flush() - // Configure vweb server + // Configure web server key := os.getenv_opt('API_KEY') or { exit_with_message(1, 'No API key was provided.') } repo_dir := os.getenv_opt('REPO_DIR') or { exit_with_message(1, 'No repo directory was configured.') @@ -70,14 +122,16 @@ fn main() { // We create the upload directory during startup if !os.is_dir(repo_dir) { - os.mkdir_all(repo_dir) or { exit_with_message(2, "Failed to create repo directory '$repo_dir'.") } + os.mkdir_all(repo_dir) or { + exit_with_message(2, "Failed to create repo directory '$repo_dir'.") + } println("Repo directory '$repo_dir' created.") } - vweb.run(&App{ - api_key: key, - repo_dir: repo_dir, + web.run(&App{ + api_key: key + repo_dir: repo_dir logger: logger }, port) } diff --git a/vieter/vweb/README.md b/vieter/vweb/README.md deleted file mode 100644 index 850048c4..00000000 --- a/vieter/vweb/README.md +++ /dev/null @@ -1,141 +0,0 @@ -# vweb - the V Web Server # - -A simple yet powerful web server with built-in routing, parameter handling, -templating, and other features. - -## Alpha level software ## - -Some features may not be complete, and there may still be bugs. However, it is -still a very useful tool. The [gitly](https://gitly.org/) site is based on vweb. - -## Features ## - -- **Very fast** performance of C on the web. -- **Small binary** hello world website is <100 KB. -- **Easy to deploy** just one binary file that also includes all templates. - No need to install any dependencies. -- **Templates are precompiled** all errors are visible at compilation time, - not at runtime. - -There is no formal documentation yet - here is a simple -[example](https://github.com/vlang/v/tree/master/examples/vweb/vweb_example.v) - -There's also the V forum, [vorum](https://github.com/vlang/vorum) - -`vorum.v` contains all GET and POST actions. - -```v ignore -pub fn (app mut App) index() { - posts := app.find_all_posts() - $vweb.html() -} - -// TODO ['/post/:id/:title'] -// TODO `fn (app App) post(id int)` -pub fn (app App) post() { - id := app.get_post_id() - post := app.retrieve_post(id) or { - app.redirect('/') - return - } - comments := app.find_comments(id) - show_form := true - $vweb.html() -} -``` - -`index.html` is an example of the V template language: - -```html -@for post in posts -
- @post.title - - @post.nr_comments - @post.time -
-@end -``` - -`$vweb.html()` compiles an HTML template into V during compilation, -and embeds the resulting code into the current action. - -That means that the template automatically has access to that action's entire environment. - -## Deploying vweb apps ## - -Everything, including HTML templates, is in one binary file. That's all you need to deploy. - -## Getting Started ## - -To start with vweb, you have to import the module `vweb`. After the import, -define a struct to hold vweb.Context (and any other variables your program will -need). - -The web server can be started by calling `vweb.run(&App{}, port)`. - -**Example:** - -```v ignore -import vweb - -struct App { - vweb.Context -} - -fn main() { - vweb.run(&App{}, 8080) -} -``` - -### Defining endpoints ### - -To add endpoints to your web server, you have to 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 response of the type `vweb.Result`. - -**Example:** - -```v ignore -// This endpoint can be accessed via http://localhost:port/hello -fn (mut app App) hello() vweb.Result { - return app.text('Hello') -} - -// This endpoint can be accessed via http://localhost:port/foo -["/foo"] -fn (mut app App) world() vweb.Result { - return app.text('World') -} -``` - -To create an HTTP POST endpoint, you simply add a `[post]` attribute before the function definition. - -**Example:** - -```v ignore -[post] -fn (mut app App) world() vweb.Result { - return app.text('World') -} -``` - -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 -['/hello/:user'] -fn (mut app App) hello_user(user string) vweb.Result { - return app.text('Hello $user') -} -``` - -You have access to the raw request data such as headers -or the request body by accessing `app` (which is `vweb.Context`). -If you want to read the request body, you can do that by calling `app.req.data`. -To read the request headers, you just call `app.req.header` and access the -header you want, e.g. `app.req.header.get(.content_type)`. See `struct Header` -for all available methods (`v doc net.http Header`). diff --git a/vieter/vweb/assets/assets.v b/vieter/vweb/assets/assets.v deleted file mode 100644 index 09a4ab97..00000000 --- a/vieter/vweb/assets/assets.v +++ /dev/null @@ -1,201 +0,0 @@ -module assets - -// this module provides an AssetManager for combining -// and caching javascript & css. -import os -import time -import crypto.md5 - -const ( - unknown_asset_type_error = 'vweb.assets: unknown asset type' -) - -struct AssetManager { -mut: - css []Asset - js []Asset -pub mut: - // when true assets will be minified - minify bool - // the directory to store the cached/combined files - cache_dir string -} - -struct Asset { - file_path string - last_modified time.Time -} - -// new_manager returns a new AssetManager -pub fn new_manager() &AssetManager { - return &AssetManager{} -} - -// add_css adds a css asset -pub fn (mut am AssetManager) add_css(file string) bool { - return am.add('css', file) -} - -// add_js adds a js asset -pub fn (mut am AssetManager) add_js(file string) bool { - return am.add('js', file) -} - -// combine_css returns the combined css as a string when to_file is false -// when to_file is true it combines the css to disk and returns the path of the file -pub fn (am AssetManager) combine_css(to_file bool) string { - return am.combine('css', to_file) -} - -// combine_js returns the combined js as a string when to_file is false -// when to_file is true it combines the css to disk and returns the path of the file -pub fn (am AssetManager) combine_js(to_file bool) string { - return am.combine('js', to_file) -} - -// include_css returns the html tag(s) for including the css files in a page. -// when combine is true the files are combined. -pub fn (am AssetManager) include_css(combine bool) string { - return am.include('css', combine) -} - -// include_js returns the html \n' - } - for asset in assets { - out += '\n' - } - } - return out -} - -// dont return option until size limit is removed -// fn (mut am AssetManager) add(asset_type, file string) ?bool { -fn (mut am AssetManager) add(asset_type string, file string) bool { - if !os.exists(file) { - // return error('vweb.assets: cannot add asset $file, it does not exist') - return false - } - asset := Asset{ - file_path: file - last_modified: time.Time{ - unix: os.file_last_mod_unix(file) - } - } - if asset_type == 'css' { - am.css << asset - } else if asset_type == 'js' { - am.js << asset - } else { - panic('$assets.unknown_asset_type_error ($asset_type).') - } - return true -} - -fn (am AssetManager) exists(asset_type string, file string) bool { - assets := am.get_assets(asset_type) - for asset in assets { - if asset.file_path == file { - return true - } - } - return false -} - -fn (am AssetManager) get_assets(asset_type string) []Asset { - if asset_type != 'css' && asset_type != 'js' { - panic('$assets.unknown_asset_type_error ($asset_type).') - } - assets := if asset_type == 'css' { am.css } else { am.js } - return assets -} - -// todo: implement proper minification -pub fn minify_css(css string) string { - mut lines := css.split('\n') - for i, _ in lines { - lines[i] = lines[i].trim_space() - } - return lines.join(' ') -} - -// todo: implement proper minification -pub fn minify_js(js string) string { - mut lines := js.split('\n') - for i, _ in lines { - lines[i] = lines[i].trim_space() - } - return lines.join(' ') -} diff --git a/vieter/vweb/assets/assets_test.v b/vieter/vweb/assets/assets_test.v deleted file mode 100644 index 6170f3ce..00000000 --- a/vieter/vweb/assets/assets_test.v +++ /dev/null @@ -1,179 +0,0 @@ -import vweb.assets -import os - -// clean_cache_dir used before and after tests that write to a cache directory. -// Because of parallel compilation and therefore test running, -// unique cache dirs are needed per test function. -fn clean_cache_dir(dir string) { - if os.is_dir(dir) { - os.rmdir_all(dir) or { panic(err) } - } -} - -fn base_cache_dir() string { - return os.join_path(os.temp_dir(), 'assets_test_cache') -} - -fn cache_dir(test_name string) string { - return os.join_path(base_cache_dir(), test_name) -} - -fn get_test_file_path(file string) string { - path := os.join_path(base_cache_dir(), file) - if !os.is_dir(base_cache_dir()) { - os.mkdir_all(base_cache_dir()) or { panic(err) } - } - if !os.exists(path) { - os.write_file(path, get_test_file_contents(file)) or { panic(err) } - } - return path -} - -fn get_test_file_contents(file string) string { - contents := match file { - 'test1.js' { '{"one": 1}\n' } - 'test2.js' { '{"two": 2}\n' } - 'test1.css' { '.one {\n\tcolor: #336699;\n}\n' } - 'test2.css' { '.two {\n\tcolor: #996633;\n}\n' } - else { 'wibble\n' } - } - return contents -} - -fn test_set_cache() { - mut am := assets.new_manager() - am.cache_dir = 'cache' -} - -fn test_set_minify() { - mut am := assets.new_manager() - am.minify = true -} - -fn test_add() { - mut am := assets.new_manager() - assert am.add('css', 'testx.css') == false - assert am.add('css', get_test_file_path('test1.css')) == true - assert am.add('js', get_test_file_path('test1.js')) == true - // assert am.add('css', get_test_file_path('test2.js')) == false // TODO: test extension on add -} - -fn test_add_css() { - mut am := assets.new_manager() - assert am.add_css('testx.css') == false - assert am.add_css(get_test_file_path('test1.css')) == true - // assert am.add_css(get_test_file_path('test1.js')) == false // TODO: test extension on add -} - -fn test_add_js() { - mut am := assets.new_manager() - assert am.add_js('testx.js') == false - assert am.add_css(get_test_file_path('test1.js')) == true - // assert am.add_css(get_test_file_path('test1.css')) == false // TODO: test extension on add -} - -fn test_combine_css() { - mut am := assets.new_manager() - am.cache_dir = cache_dir('test_combine_css') - clean_cache_dir(am.cache_dir) - am.add_css(get_test_file_path('test1.css')) - am.add_css(get_test_file_path('test2.css')) - // TODO: How do I test non-minified, is there a "here doc" format that keeps formatting? - am.minify = true - expected := '.one { color: #336699; } .two { color: #996633; } ' - actual := am.combine_css(false) - assert actual == expected - assert actual.contains(expected) - // Test cache path doesn't change when input files and minify setting do not. - path1 := am.combine_css(true) - clean_cache_dir(am.cache_dir) - path2 := am.combine_css(true) - assert path1 == path2 - clean_cache_dir(am.cache_dir) -} - -fn test_combine_js() { - mut am := assets.new_manager() - am.cache_dir = cache_dir('test_combine_js') - clean_cache_dir(am.cache_dir) - am.add_js(get_test_file_path('test1.js')) - am.add_js(get_test_file_path('test2.js')) - expected1 := '{"one": 1}' - expected2 := '{"two": 2}' - expected := expected1 + '\n' + expected2 + '\n' - actual := am.combine_js(false) - assert actual == expected - assert actual.contains(expected) - assert actual.contains(expected1) - assert actual.contains(expected2) - am.minify = true - clean_cache_dir(am.cache_dir) - expected3 := expected1 + ' ' + expected2 + ' ' - actual2 := am.combine_js(false) - assert actual2 == expected3 - assert actual2.contains(expected3) - // Test cache path doesn't change when input files and minify setting do not. - path1 := am.combine_js(true) - clean_cache_dir(am.cache_dir) - path2 := am.combine_js(true) - assert path1 == path2 - clean_cache_dir(am.cache_dir) -} - -fn test_include_css() { - mut am := assets.new_manager() - file1 := get_test_file_path('test1.css') - am.add_css(file1) - expected := '\n' - actual := am.include_css(false) - assert actual == expected - assert actual.contains(expected) - // Two lines of output. - file2 := get_test_file_path('test2.css') - am.add_css(file2) - am.cache_dir = cache_dir('test_include_css') - clean_cache_dir(am.cache_dir) - expected2 := expected + '\n' - actual2 := am.include_css(false) - assert actual2 == expected2 - assert actual2.contains(expected2) - // Combined output. - clean_cache_dir(am.cache_dir) - actual3 := am.include_css(true) - assert actual3.contains(expected2) == false - assert actual3.starts_with('\n' - actual := am.include_js(false) - assert actual == expected - assert actual.contains(expected) - // Two lines of output. - file2 := get_test_file_path('test2.js') - am.add_js(file2) - am.cache_dir = cache_dir('test_include_js') - clean_cache_dir(am.cache_dir) - expected2 := expected + '\n' - actual2 := am.include_js(false) - assert actual2 == expected2 - assert actual2.contains(expected2) - // Combined output. - clean_cache_dir(am.cache_dir) - actual3 := am.include_js(true) - assert actual3.contains(expected2) == false - assert actual3.starts_with('