v test-fmt: reformat some skipped files, comment on the remaining ones
parent
e36f11750b
commit
3795aaab5c
|
@ -2,10 +2,8 @@ module main
|
|||
|
||||
// This tool regenerates V's bootstrap .c files
|
||||
// every time the V master branch is updated.
|
||||
|
||||
// if run with the --serve flag it will run in webhook
|
||||
// server mode awaiting a request to http://host:port/genhook
|
||||
|
||||
// available command line flags:
|
||||
// --work-dir gen_vc's working directory
|
||||
// --purge force purge the local repositories
|
||||
|
@ -15,7 +13,6 @@ module main
|
|||
// --log-file path to log file used when --log-to is 'file'
|
||||
// --dry-run dont push anything to remote repo
|
||||
// --force force update even if already up to date
|
||||
|
||||
import os
|
||||
import log
|
||||
import flag
|
||||
|
@ -24,54 +21,54 @@ import vweb
|
|||
import net.urllib
|
||||
|
||||
// git credentials
|
||||
const(
|
||||
const (
|
||||
git_username = os.getenv('GITUSER')
|
||||
git_password = os.getenv('GITPASS')
|
||||
)
|
||||
|
||||
// repository
|
||||
const(
|
||||
const (
|
||||
// git repo
|
||||
git_repo_v = 'github.com/vlang/v'
|
||||
git_repo_vc = 'github.com/vlang/vc'
|
||||
git_repo_v = 'github.com/vlang/v'
|
||||
git_repo_vc = 'github.com/vlang/vc'
|
||||
// local repo directories
|
||||
git_repo_dir_v = 'v'
|
||||
git_repo_dir_vc = 'vc'
|
||||
)
|
||||
|
||||
// gen_vc
|
||||
const(
|
||||
const (
|
||||
// name
|
||||
app_name = 'gen_vc'
|
||||
app_name = 'gen_vc'
|
||||
// version
|
||||
app_version = '0.1.2'
|
||||
app_version = '0.1.2'
|
||||
// description
|
||||
app_description = 'This tool regenerates V\'s bootstrap .c files every time the V master branch is updated.'
|
||||
app_description = "This tool regenerates V\'s bootstrap .c files every time the V master branch is updated."
|
||||
// assume something went wrong if file size less than this
|
||||
too_short_file_limit = 5000
|
||||
// create a .c file for these os's
|
||||
vc_build_oses = [
|
||||
vc_build_oses = [
|
||||
'nix', // all nix based os
|
||||
'windows'
|
||||
'windows',
|
||||
]
|
||||
)
|
||||
|
||||
// default options (overridden by flags)
|
||||
const(
|
||||
const (
|
||||
// gen_vc working directory
|
||||
work_dir = '/tmp/gen_vc'
|
||||
work_dir = '/tmp/gen_vc'
|
||||
// dont push anything to remote repo
|
||||
dry_run = false
|
||||
dry_run = false
|
||||
// server port
|
||||
server_port = 7171
|
||||
// log file
|
||||
log_file = '$work_dir/log.txt'
|
||||
log_file = '$work_dir/log.txt'
|
||||
// log_to is either 'file' or 'terminal'
|
||||
log_to = 'terminal'
|
||||
log_to = 'terminal'
|
||||
)
|
||||
|
||||
// errors
|
||||
const(
|
||||
const (
|
||||
err_msg_build = 'error building'
|
||||
err_msg_make = 'make failed'
|
||||
err_msg_gen_c = 'failed to generate .c file'
|
||||
|
@ -81,9 +78,9 @@ const(
|
|||
struct GenVC {
|
||||
// logger
|
||||
// flag options
|
||||
options FlagOptions
|
||||
options FlagOptions
|
||||
mut:
|
||||
logger &log.Log
|
||||
logger &log.Log
|
||||
// true if error was experienced running generate
|
||||
gen_error bool
|
||||
}
|
||||
|
@ -109,23 +106,21 @@ struct FlagOptions {
|
|||
|
||||
fn main() {
|
||||
mut fp := flag.new_flag_parser(os.args.clone())
|
||||
|
||||
fp.application(app_name)
|
||||
fp.version(app_version)
|
||||
fp.description(app_description)
|
||||
fp.skip_executable()
|
||||
|
||||
show_help:=fp.bool('help', 0, false, 'Show this help screen\n')
|
||||
show_help := fp.bool('help', 0, false, 'Show this help screen\n')
|
||||
flag_options := parse_flags(mut fp)
|
||||
|
||||
if show_help { println( fp.usage() ) exit(0) }
|
||||
|
||||
if show_help {
|
||||
println(fp.usage())
|
||||
exit(0)
|
||||
}
|
||||
fp.finalize() or {
|
||||
eprintln(err)
|
||||
println(fp.usage())
|
||||
return
|
||||
}
|
||||
|
||||
eprintln(err)
|
||||
println(fp.usage())
|
||||
return
|
||||
}
|
||||
// webhook server mode
|
||||
if flag_options.serve {
|
||||
vweb.run<WebhookServer>(flag_options.port)
|
||||
|
@ -142,7 +137,7 @@ fn new_gen_vc(flag_options FlagOptions) &GenVC {
|
|||
mut logger := &log.Log{}
|
||||
logger.set_level(.debug)
|
||||
if flag_options.log_to == 'file' {
|
||||
logger.set_full_logpath( flag_options.log_file )
|
||||
logger.set_full_logpath(flag_options.log_file)
|
||||
}
|
||||
return &GenVC{
|
||||
options: flag_options
|
||||
|
@ -156,11 +151,11 @@ pub fn (mut ws WebhookServer) init_once() {
|
|||
flag_options := parse_flags(mut fp)
|
||||
ws.gen_vc = new_gen_vc(flag_options)
|
||||
ws.gen_vc.init()
|
||||
//ws.gen_vc = new_gen_vc(flag_options)
|
||||
// ws.gen_vc = new_gen_vc(flag_options)
|
||||
}
|
||||
|
||||
pub fn (mut ws WebhookServer) init() {
|
||||
//ws.init_once()
|
||||
// ws.init_once()
|
||||
}
|
||||
|
||||
pub fn (mut ws WebhookServer) index() {
|
||||
|
@ -185,18 +180,17 @@ pub fn (mut ws WebhookServer) genhook() {
|
|||
pub fn (ws &WebhookServer) reset() {
|
||||
}
|
||||
|
||||
|
||||
// parse flags to FlagOptions struct
|
||||
fn parse_flags(mut fp flag.FlagParser) FlagOptions {
|
||||
return FlagOptions{
|
||||
serve : fp.bool('serve', 0, false, 'run in webhook server mode')
|
||||
work_dir : fp.string('work-dir', 0, work_dir, 'gen_vc working directory')
|
||||
purge : fp.bool('purge', 0, false, 'force purge the local repositories')
|
||||
port : fp.int('port', 0, server_port, 'port for web server to listen on')
|
||||
log_to : fp.string('log-to', 0, log_to, 'log to is \'file\' or \'terminal\'')
|
||||
log_file : fp.string('log-file', 0, log_file, 'log file to use when log-to is \'file\'')
|
||||
dry_run : fp.bool('dry-run', 0, dry_run, 'when specified dont push anything to remote repo')
|
||||
force : fp.bool('force', 0, false, 'force update even if already up to date')
|
||||
serve: fp.bool('serve', 0, false, 'run in webhook server mode')
|
||||
work_dir: fp.string('work-dir', 0, work_dir, 'gen_vc working directory')
|
||||
purge: fp.bool('purge', 0, false, 'force purge the local repositories')
|
||||
port: fp.int('port', 0, server_port, 'port for web server to listen on')
|
||||
log_to: fp.string('log-to', 0, log_to, "log to is \'file\' or \'terminal\'")
|
||||
log_file: fp.string('log-file', 0, log_file, "log file to use when log-to is \'file\'")
|
||||
dry_run: fp.bool('dry-run', 0, dry_run, 'when specified dont push anything to remote repo')
|
||||
force: fp.bool('force', 0, false, 'force update even if already up to date')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,11 +206,12 @@ fn (mut gen_vc GenVC) init() {
|
|||
fn (mut gen_vc GenVC) generate() {
|
||||
// set errors to false
|
||||
gen_vc.gen_error = false
|
||||
|
||||
// check if gen_vc dir exists
|
||||
if !os.is_dir(gen_vc.options.work_dir) {
|
||||
// try create
|
||||
os.mkdir(gen_vc.options.work_dir) or { panic(err) }
|
||||
os.mkdir(gen_vc.options.work_dir) or {
|
||||
panic(err)
|
||||
}
|
||||
// still dosen't exist... we have a problem
|
||||
if !os.is_dir(gen_vc.options.work_dir) {
|
||||
gen_vc.logger.error('error creating directory: $gen_vc.options.work_dir')
|
||||
|
@ -224,10 +219,8 @@ fn (mut gen_vc GenVC) generate() {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
// cd to gen_vc dir
|
||||
os.chdir(gen_vc.options.work_dir)
|
||||
|
||||
// if we are not running with the --serve flag (webhook server)
|
||||
// rather than deleting and re-downloading the repo each time
|
||||
// first check to see if the local v repo is behind master
|
||||
|
@ -242,60 +235,49 @@ fn (mut gen_vc GenVC) generate() {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
// delete repos
|
||||
gen_vc.purge_repos()
|
||||
|
||||
// clone repos
|
||||
gen_vc.cmd_exec('git clone --depth 1 https://$git_repo_v $git_repo_dir_v')
|
||||
gen_vc.cmd_exec('git clone --depth 1 https://$git_repo_vc $git_repo_dir_vc')
|
||||
|
||||
// get output of git log -1 (last commit)
|
||||
git_log_v := gen_vc.cmd_exec('git -C $git_repo_dir_v log -1 --format="commit %H%nDate: %ci%nDate Unix: %ct%nSubject: %s"')
|
||||
git_log_vc := gen_vc.cmd_exec('git -C $git_repo_dir_vc log -1 --format="Commit %H%nDate: %ci%nDate Unix: %ct%nSubject: %s"')
|
||||
|
||||
// date of last commit in each repo
|
||||
ts_v := git_log_v.find_between('Date:', '\n').trim_space()
|
||||
ts_vc := git_log_vc.find_between('Date:', '\n').trim_space()
|
||||
|
||||
// parse time as string to time.Time
|
||||
last_commit_time_v := time.parse(ts_v) or {
|
||||
last_commit_time_v := time.parse(ts_v) or {
|
||||
panic(err)
|
||||
}
|
||||
last_commit_time_vc := time.parse(ts_vc) or {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// git dates are in users local timezone and v time.parse does not parse
|
||||
// timezones at the moment, so for now get unix timestamp from output also
|
||||
t_unix_v := git_log_v.find_between('Date Unix:', '\n').trim_space().int()
|
||||
t_unix_vc := git_log_vc.find_between('Date Unix:', '\n').trim_space().int()
|
||||
|
||||
// last commit hash in v repo
|
||||
last_commit_hash_v := git_log_v.find_between('commit', '\n').trim_space()
|
||||
last_commit_hash_v_short := last_commit_hash_v[..7]
|
||||
|
||||
// subject
|
||||
last_commit_subject := git_log_v.find_between('Subject:', '\n').trim_space().replace('"', '\\"')
|
||||
|
||||
last_commit_subject := git_log_v.find_between('Subject:', '\n').trim_space().replace('"',
|
||||
'\\"')
|
||||
// log some info
|
||||
gen_vc.logger.debug('last commit time ($git_repo_v): ' + last_commit_time_v.format_ss())
|
||||
gen_vc.logger.debug('last commit time ($git_repo_vc): ' + last_commit_time_vc.format_ss())
|
||||
gen_vc.logger.debug('last commit hash ($git_repo_v): $last_commit_hash_v')
|
||||
gen_vc.logger.debug('last commit subject ($git_repo_v): $last_commit_subject')
|
||||
|
||||
// if vc repo already has a newer commit than the v repo, assume it's up to date
|
||||
if t_unix_vc >= t_unix_v && !gen_vc.options.force {
|
||||
gen_vc.logger.warn('vc repository is already up to date.')
|
||||
return
|
||||
}
|
||||
|
||||
// try build v for current os (linux in this case)
|
||||
gen_vc.cmd_exec('make -C $git_repo_dir_v')
|
||||
v_exec := '$git_repo_dir_v/v'
|
||||
// check if make was successful
|
||||
gen_vc.assert_file_exists_and_is_not_too_short(v_exec, err_msg_make)
|
||||
|
||||
// build v.c for each os
|
||||
for os_name in vc_build_oses {
|
||||
vc_suffix := if os_name == 'nix' { '' } else { '_${os_name[..3]}' }
|
||||
|
@ -312,7 +294,6 @@ fn (mut gen_vc GenVC) generate() {
|
|||
// add new .c file to local vc repo
|
||||
gen_vc.cmd_exec('git -C $git_repo_dir_vc add $c_file')
|
||||
}
|
||||
|
||||
// check if the vc repo actually changed
|
||||
git_status := gen_vc.cmd_exec('git -C $git_repo_dir_vc status')
|
||||
if git_status.contains('nothing to commit') {
|
||||
|
@ -344,7 +325,7 @@ fn (mut gen_vc GenVC) command_execute(cmd string, dry bool) string {
|
|||
gen_vc.logger.info('cmd: $cmd')
|
||||
r := os.exec(cmd) or {
|
||||
gen_vc.logger.error('$err_msg_cmd_x: "$cmd" could not start.')
|
||||
gen_vc.logger.error( err )
|
||||
gen_vc.logger.error(err)
|
||||
// something went wrong, better start fresh next time
|
||||
gen_vc.purge_repos()
|
||||
gen_vc.gen_error = true
|
||||
|
@ -383,7 +364,7 @@ fn (mut gen_vc GenVC) purge_repos() {
|
|||
}
|
||||
|
||||
// check if file size is too short
|
||||
fn (mut gen_vc GenVC) assert_file_exists_and_is_not_too_short(f string, emsg string){
|
||||
fn (mut gen_vc GenVC) assert_file_exists_and_is_not_too_short(f, emsg string) {
|
||||
if !os.exists(f) {
|
||||
gen_vc.logger.error('$err_msg_build: $emsg .')
|
||||
gen_vc.gen_error = true
|
||||
|
|
|
@ -5,7 +5,7 @@ import flag
|
|||
import scripting
|
||||
|
||||
const (
|
||||
remote_v_repo_url = 'https://github.com/vlang/v'
|
||||
remote_v_repo_url = 'https://github.com/vlang/v'
|
||||
remote_vc_repo_url = 'https://github.com/vlang/vc'
|
||||
)
|
||||
|
||||
|
@ -34,35 +34,35 @@ pub fn validate_commit_exists(commit string) {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn line_to_timestamp_and_commit(line string) (int,string) {
|
||||
pub fn line_to_timestamp_and_commit(line string) (int, string) {
|
||||
parts := line.split(' ')
|
||||
return parts[0].int(),parts[1]
|
||||
return parts[0].int(), parts[1]
|
||||
}
|
||||
|
||||
pub fn normalized_workpath_for_commit(workdir string, commit string) string {
|
||||
pub fn normalized_workpath_for_commit(workdir, commit string) string {
|
||||
nc := 'v_at_' + commit.replace('^', '_').replace('-', '_').replace('/', '_')
|
||||
return os.real_path(workdir + os.path_separator + nc)
|
||||
}
|
||||
|
||||
pub fn prepare_vc_source(vcdir string, cdir string, commit string) (string,string) {
|
||||
pub fn prepare_vc_source(vcdir, cdir, commit string) (string, string) {
|
||||
scripting.chdir(cdir)
|
||||
// Building a historic v with the latest vc is not always possible ...
|
||||
// It is more likely, that the vc *at the time of the v commit*,
|
||||
// or slightly before that time will be able to build the historic v:
|
||||
vline := scripting.run('git rev-list -n1 --timestamp "$commit" ')
|
||||
v_timestamp,v_commithash := vgit.line_to_timestamp_and_commit(vline)
|
||||
vgit.check_v_commit_timestamp_before_self_rebuilding(v_timestamp)
|
||||
v_timestamp, v_commithash := line_to_timestamp_and_commit(vline)
|
||||
check_v_commit_timestamp_before_self_rebuilding(v_timestamp)
|
||||
scripting.chdir(vcdir)
|
||||
scripting.run('git checkout master')
|
||||
vcbefore := scripting.run('git rev-list HEAD -n1 --timestamp --before=$v_timestamp ')
|
||||
_,vccommit_before := vgit.line_to_timestamp_and_commit(vcbefore)
|
||||
_, vccommit_before := line_to_timestamp_and_commit(vcbefore)
|
||||
scripting.run('git checkout "$vccommit_before" ')
|
||||
scripting.run('wc *.c')
|
||||
scripting.chdir(cdir)
|
||||
return v_commithash,vccommit_before
|
||||
return v_commithash, vccommit_before
|
||||
}
|
||||
|
||||
pub fn clone_or_pull( remote_git_url string, local_worktree_path string ) {
|
||||
pub fn clone_or_pull(remote_git_url, local_worktree_path string) {
|
||||
// NB: after clone_or_pull, the current repo branch is === HEAD === master
|
||||
if os.is_dir(local_worktree_path) && os.is_dir(os.join_path(local_worktree_path, '.git')) {
|
||||
// Already existing ... Just pulling in this case is faster usually.
|
||||
|
@ -76,20 +76,20 @@ pub fn clone_or_pull( remote_git_url string, local_worktree_path string ) {
|
|||
|
||||
pub struct VGitContext {
|
||||
pub:
|
||||
cc string = 'cc' // what compiler to use
|
||||
workdir string = '/tmp' // the base working folder
|
||||
commit_v string = 'master' // the commit-ish that needs to be prepared
|
||||
path_v string // where is the local working copy v repo
|
||||
path_vc string // where is the local working copy vc repo
|
||||
v_repo_url string // the remote v repo URL
|
||||
vc_repo_url string // the remote vc repo URL
|
||||
cc string = 'cc' // what compiler to use
|
||||
workdir string = '/tmp' // the base working folder
|
||||
commit_v string = 'master' // the commit-ish that needs to be prepared
|
||||
path_v string // where is the local working copy v repo
|
||||
path_vc string // where is the local working copy vc repo
|
||||
v_repo_url string // the remote v repo URL
|
||||
vc_repo_url string // the remote vc repo URL
|
||||
pub mut:
|
||||
// these will be filled by vgitcontext.compile_oldv_if_needed()
|
||||
commit_v__hash string // the git commit of the v repo that should be prepared
|
||||
commit_vc_hash string // the git commit of the vc repo, corresponding to commit_v__hash
|
||||
vexename string // v or v.exe
|
||||
vexepath string // the full absolute path to the prepared v/v.exe
|
||||
vvlocation string // v.v or compiler/ or cmd/v, depending on v version
|
||||
vexename string // v or v.exe
|
||||
vexepath string // the full absolute path to the prepared v/v.exe
|
||||
vvlocation string // v.v or compiler/ or cmd/v, depending on v version
|
||||
}
|
||||
|
||||
pub fn (mut vgit_context VGitContext) compile_oldv_if_needed() {
|
||||
|
@ -100,18 +100,17 @@ pub fn (mut vgit_context VGitContext) compile_oldv_if_needed() {
|
|||
if 'windows' == os.user_os() {
|
||||
command_for_building_v_from_c_source = '$vgit_context.cc -std=c99 -municode -w -o cv.exe "$vgit_context.path_vc/v_win.c" '
|
||||
command_for_selfbuilding = './cv.exe -o $vgit_context.vexename {SOURCE}'
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
command_for_building_v_from_c_source = '$vgit_context.cc -std=gnu11 -w -o cv "$vgit_context.path_vc/v.c" -lm -lpthread'
|
||||
command_for_selfbuilding = './cv -o $vgit_context.vexename {SOURCE}'
|
||||
}
|
||||
scripting.chdir(vgit_context.workdir)
|
||||
clone_or_pull( vgit_context.v_repo_url, vgit_context.path_v )
|
||||
clone_or_pull( vgit_context.vc_repo_url, vgit_context.path_vc )
|
||||
|
||||
clone_or_pull(vgit_context.v_repo_url, vgit_context.path_v)
|
||||
clone_or_pull(vgit_context.vc_repo_url, vgit_context.path_vc)
|
||||
scripting.chdir(vgit_context.path_v)
|
||||
scripting.run('git checkout $vgit_context.commit_v')
|
||||
v_commithash,vccommit_before := vgit.prepare_vc_source(vgit_context.path_vc, vgit_context.path_v, vgit_context.commit_v)
|
||||
v_commithash, vccommit_before := prepare_vc_source(vgit_context.path_vc, vgit_context.path_v,
|
||||
vgit_context.commit_v)
|
||||
vgit_context.commit_v__hash = v_commithash
|
||||
vgit_context.commit_vc_hash = vccommit_before
|
||||
if os.exists('cmd/v') {
|
||||
|
@ -128,25 +127,24 @@ pub fn (mut vgit_context VGitContext) compile_oldv_if_needed() {
|
|||
scripting.run(command_for_building_v_from_c_source)
|
||||
build_cmd := command_for_selfbuilding.replace('{SOURCE}', vgit_context.vvlocation)
|
||||
scripting.run(build_cmd)
|
||||
|
||||
// At this point, there exists a file vgit_context.vexepath
|
||||
// which should be a valid working V executable.
|
||||
}
|
||||
|
||||
pub struct VGitOptions {
|
||||
pub mut:
|
||||
workdir string // the working folder (typically /tmp), where the tool will write
|
||||
v_repo_url string // the url of the V repository. It can be a local folder path, if you want to eliminate network operations...
|
||||
vc_repo_url string // the url of the vc repository. It can be a local folder path, if you want to eliminate network operations...
|
||||
show_help bool // whether to show the usage screen
|
||||
verbose bool // should the tool be much more verbose
|
||||
workdir string // the working folder (typically /tmp), where the tool will write
|
||||
v_repo_url string // the url of the V repository. It can be a local folder path, if you want to eliminate network operations...
|
||||
vc_repo_url string // the url of the vc repository. It can be a local folder path, if you want to eliminate network operations...
|
||||
show_help bool // whether to show the usage screen
|
||||
verbose bool // should the tool be much more verbose
|
||||
}
|
||||
|
||||
pub fn add_common_tool_options(mut context VGitOptions, mut fp flag.FlagParser) []string {
|
||||
tdir := os.temp_dir()
|
||||
context.workdir = os.real_path(fp.string('workdir', `w`, tdir, 'A writable base folder. Default: $tdir'))
|
||||
context.v_repo_url = fp.string('vrepo', 0, vgit.remote_v_repo_url, 'The url of the V repository. You can clone it locally too. See also --vcrepo below.')
|
||||
context.vc_repo_url = fp.string('vcrepo', 0, vgit.remote_vc_repo_url, 'The url of the vc repository. You can clone it
|
||||
context.v_repo_url = fp.string('vrepo', 0, remote_v_repo_url, 'The url of the V repository. You can clone it locally too. See also --vcrepo below.')
|
||||
context.vc_repo_url = fp.string('vcrepo', 0, remote_vc_repo_url, 'The url of the vc repository. You can clone it
|
||||
${flag.space}beforehand, and then just give the local folder
|
||||
${flag.space}path here. That will eliminate the network ops
|
||||
${flag.space}done by this tool, which is useful, if you want
|
||||
|
@ -154,31 +152,25 @@ ${flag.space}to script it/run it in a restrictive vps/docker.
|
|||
')
|
||||
context.show_help = fp.bool('help', `h`, false, 'Show this help screen.')
|
||||
context.verbose = fp.bool('verbose', `v`, false, 'Be more verbose.')
|
||||
|
||||
if context.show_help {
|
||||
println(fp.usage())
|
||||
exit(0)
|
||||
}
|
||||
|
||||
if context.verbose {
|
||||
scripting.set_verbose(true)
|
||||
}
|
||||
|
||||
if os.is_dir(context.v_repo_url) {
|
||||
context.v_repo_url = os.real_path( context.v_repo_url )
|
||||
context.v_repo_url = os.real_path(context.v_repo_url)
|
||||
}
|
||||
|
||||
if os.is_dir(context.vc_repo_url) {
|
||||
context.vc_repo_url = os.real_path( context.vc_repo_url )
|
||||
context.vc_repo_url = os.real_path(context.vc_repo_url)
|
||||
}
|
||||
|
||||
commits := fp.finalize() or {
|
||||
eprintln('Error: ' + err)
|
||||
exit(1)
|
||||
}
|
||||
for commit in commits {
|
||||
vgit.validate_commit_exists(commit)
|
||||
validate_commit_exists(commit)
|
||||
}
|
||||
|
||||
return commits
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import scripting
|
|||
import vgit
|
||||
|
||||
const (
|
||||
tool_version = '0.0.3'
|
||||
tool_version = '0.0.3'
|
||||
tool_description = ' Checkout an old V and compile it as it was on specific commit.
|
||||
This tool is useful, when you want to discover when something broke.
|
||||
It is also useful, when you just want to experiment with an older historic V.
|
||||
|
@ -30,25 +30,25 @@ const (
|
|||
struct Context {
|
||||
mut:
|
||||
vgo vgit.VGitOptions
|
||||
commit_v string='master' // the commit from which you want to produce a working v compiler (this may be a commit-ish too)
|
||||
commit_vc string='master' // this will be derived from commit_v
|
||||
commit_v string = 'master' // the commit from which you want to produce a working v compiler (this may be a commit-ish too)
|
||||
commit_vc string = 'master' // this will be derived from commit_v
|
||||
commit_v_hash string // this will be filled from the commit-ish commit_v using rev-list. It IS a commit hash.
|
||||
path_v string // the full path to the v folder inside workdir.
|
||||
path_vc string // the full path to the vc folder inside workdir.
|
||||
cmd_to_run string // the command that you want to run *in* the oldv repo
|
||||
cc string='cc' // the C compiler to use for bootstrapping.
|
||||
cc string = 'cc' // the C compiler to use for bootstrapping.
|
||||
cleanup bool // should the tool run a cleanup first
|
||||
}
|
||||
|
||||
fn (mut c Context) compile_oldv_if_needed() {
|
||||
mut vgit_context := vgit.VGitContext{
|
||||
workdir: c.vgo.workdir
|
||||
v_repo_url: c.vgo.v_repo_url
|
||||
workdir: c.vgo.workdir
|
||||
v_repo_url: c.vgo.v_repo_url
|
||||
vc_repo_url: c.vgo.vc_repo_url
|
||||
cc: c.cc
|
||||
commit_v: c.commit_v
|
||||
path_v: c.path_v
|
||||
path_vc: c.path_vc
|
||||
cc: c.cc
|
||||
commit_v: c.commit_v
|
||||
path_v: c.path_v
|
||||
path_vc: c.path_vc
|
||||
}
|
||||
vgit_context.compile_oldv_if_needed()
|
||||
c.commit_v_hash = vgit_context.commit_v__hash
|
||||
|
@ -69,10 +69,8 @@ fn main() {
|
|||
fp.arguments_description('VCOMMIT')
|
||||
fp.skip_executable()
|
||||
fp.limit_free_args(1, 1)
|
||||
|
||||
context.cleanup = fp.bool('clean', 0, true, 'Clean before running (slower).')
|
||||
context.cmd_to_run = fp.string('command', `c`, '', 'Command to run in the old V repo.\n')
|
||||
|
||||
commits := vgit.add_common_tool_options(mut context.vgo, mut fp)
|
||||
if commits.len > 0 {
|
||||
context.commit_v = commits[0]
|
||||
|
@ -83,7 +81,7 @@ fn main() {
|
|||
context.path_v = vgit.normalized_workpath_for_commit(context.vgo.workdir, context.commit_v)
|
||||
context.path_vc = vgit.normalized_workpath_for_commit(context.vgo.workdir, 'vc')
|
||||
if !os.is_dir(context.vgo.workdir) {
|
||||
eprintln('Work folder: ${context.vgo.workdir} , does not exist.')
|
||||
eprintln('Work folder: $context.vgo.workdir , does not exist.')
|
||||
exit(2)
|
||||
}
|
||||
ecc := os.getenv('CC')
|
||||
|
@ -94,9 +92,7 @@ fn main() {
|
|||
scripting.rmrf(context.path_v)
|
||||
scripting.rmrf(context.path_vc)
|
||||
}
|
||||
|
||||
context.compile_oldv_if_needed()
|
||||
|
||||
scripting.chdir(context.path_v)
|
||||
scripting.cprintln('# v commit hash: $context.commit_v_hash')
|
||||
scripting.cprintln('# checkout folder: $context.path_v')
|
||||
|
@ -108,5 +104,4 @@ fn main() {
|
|||
println(cmdres.output)
|
||||
exit(cmdres.exit_code)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@ import scripting
|
|||
import vgit
|
||||
|
||||
const (
|
||||
tool_version = '0.0.5'
|
||||
tool_description = ' Compares V executable size and performance,
|
||||
tool_version = '0.0.5'
|
||||
tool_description = " Compares V executable size and performance,
|
||||
between 2 commits from V\'s local git history.
|
||||
When only one commit is given, it is compared to master.
|
||||
'
|
||||
"
|
||||
)
|
||||
|
||||
struct Context {
|
||||
|
@ -44,64 +44,46 @@ fn (c Context) compare_versions() {
|
|||
c.prepare_v(c.b, c.commit_before)
|
||||
c.prepare_v(c.a, c.commit_after)
|
||||
scripting.chdir(c.vgo.workdir)
|
||||
|
||||
if c.vflags.len > 0 {
|
||||
os.setenv('VFLAGS', c.vflags, true)
|
||||
}
|
||||
|
||||
// The first is the baseline, against which all the others will be compared.
|
||||
// It is the fastest, since hello_world.v has only a single println in it,
|
||||
mut perf_files := []string{}
|
||||
perf_files << c.compare_v_performance('source_hello', [
|
||||
'vprod @DEBUG@ -o source.c examples/hello_world.v',
|
||||
'vprod -o source.c examples/hello_world.v',
|
||||
'v @DEBUG@ -o source.c examples/hello_world.v',
|
||||
'v -o source.c examples/hello_world.v',
|
||||
])
|
||||
|
||||
perf_files << c.compare_v_performance('source_v', [
|
||||
'vprod @DEBUG@ -o source.c @COMPILER@',
|
||||
'vprod -o source.c @COMPILER@',
|
||||
'v @DEBUG@ -o source.c @COMPILER@',
|
||||
'v -o source.c @COMPILER@',
|
||||
])
|
||||
|
||||
perf_files << c.compare_v_performance('binary_hello', [
|
||||
'vprod -o hello examples/hello_world.v',
|
||||
'v -o hello examples/hello_world.v',
|
||||
])
|
||||
|
||||
perf_files << c.compare_v_performance('binary_v', [
|
||||
'vprod -o binary @COMPILER@',
|
||||
'v -o binary @COMPILER@',
|
||||
])
|
||||
|
||||
perf_files <<
|
||||
c.compare_v_performance('source_hello', ['vprod @DEBUG@ -o source.c examples/hello_world.v', 'vprod -o source.c examples/hello_world.v', 'v @DEBUG@ -o source.c examples/hello_world.v', 'v -o source.c examples/hello_world.v'])
|
||||
perf_files <<
|
||||
c.compare_v_performance('source_v', ['vprod @DEBUG@ -o source.c @COMPILER@', 'vprod -o source.c @COMPILER@', 'v @DEBUG@ -o source.c @COMPILER@', 'v -o source.c @COMPILER@'])
|
||||
perf_files <<
|
||||
c.compare_v_performance('binary_hello', ['vprod -o hello examples/hello_world.v', 'v -o hello examples/hello_world.v'])
|
||||
perf_files <<
|
||||
c.compare_v_performance('binary_v', ['vprod -o binary @COMPILER@', 'v -o binary @COMPILER@'])
|
||||
println('All performance files:')
|
||||
for f in perf_files {
|
||||
println(' $f')
|
||||
}
|
||||
}
|
||||
|
||||
fn (c &Context) prepare_v(cdir string, commit string) {
|
||||
fn (c &Context) prepare_v(cdir, commit string) {
|
||||
mut cc := os.getenv('CC')
|
||||
if cc == '' {
|
||||
cc = 'cc'
|
||||
}
|
||||
mut vgit_context := vgit.VGitContext{
|
||||
cc: cc
|
||||
commit_v: commit
|
||||
path_v: cdir
|
||||
path_vc: c.vc
|
||||
workdir: c.vgo.workdir
|
||||
v_repo_url: c.vgo.v_repo_url
|
||||
cc: cc
|
||||
commit_v: commit
|
||||
path_v: cdir
|
||||
path_vc: c.vc
|
||||
workdir: c.vgo.workdir
|
||||
v_repo_url: c.vgo.v_repo_url
|
||||
vc_repo_url: c.vgo.vc_repo_url
|
||||
}
|
||||
vgit_context.compile_oldv_if_needed()
|
||||
scripting.chdir(cdir)
|
||||
println('Making a v compiler in $cdir')
|
||||
scripting.run('./v -cc ${cc} -o v $vgit_context.vvlocation')
|
||||
scripting.run('./v -cc $cc -o v $vgit_context.vvlocation')
|
||||
println('Making a vprod compiler in $cdir')
|
||||
scripting.run('./v -cc ${cc} -prod -o vprod $vgit_context.vvlocation')
|
||||
scripting.run('./v -cc $cc -prod -o vprod $vgit_context.vvlocation')
|
||||
println('Stripping and compressing cv v and vprod binaries in $cdir')
|
||||
scripting.run('cp cv cv_stripped')
|
||||
scripting.run('cp v v_stripped')
|
||||
|
@ -117,17 +99,19 @@ fn (c &Context) prepare_v(cdir string, commit string) {
|
|||
scripting.show_sizes_of_files(['$cdir/v', '$cdir/v_stripped', '$cdir/v_stripped_upxed'])
|
||||
scripting.show_sizes_of_files(['$cdir/vprod', '$cdir/vprod_stripped', '$cdir/vprod_stripped_upxed'])
|
||||
vversion := scripting.run('$cdir/v -version')
|
||||
vcommit := scripting.run('git rev-parse --short --verify HEAD')
|
||||
println('V version is: ${vversion} , local source commit: ${vcommit}')
|
||||
vcommit := scripting.run('git rev-parse --short --verify HEAD')
|
||||
println('V version is: $vversion , local source commit: $vcommit')
|
||||
if vgit_context.vvlocation == 'cmd/v' {
|
||||
if os.exists('vlib/v/ast/ast.v') {
|
||||
println('Source lines of the compiler: ' + scripting.run('find cmd/v/ vlib/v/ -name "*.v" | grep -v /tests/ | xargs wc | tail -n -1'))
|
||||
println('Source lines of the compiler: ' +
|
||||
scripting.run('find cmd/v/ vlib/v/ -name "*.v" | grep -v /tests/ | xargs wc | tail -n -1'))
|
||||
} else {
|
||||
println('Source lines of the compiler: ' + scripting.run('wc cmd/v/*.v vlib/compiler/*.v | tail -n -1'))
|
||||
println('Source lines of the compiler: ' +
|
||||
scripting.run('wc cmd/v/*.v vlib/compiler/*.v | tail -n -1'))
|
||||
}
|
||||
} else if vgit_context.vvlocation == 'v.v' {
|
||||
println('Source lines of the compiler: ' + scripting.run('wc v.v vlib/compiler/*.v | tail -n -1'))
|
||||
}else{
|
||||
} else {
|
||||
println('Source lines of the compiler: ' + scripting.run('wc compiler/*.v | tail -n -1'))
|
||||
}
|
||||
}
|
||||
|
@ -147,8 +131,8 @@ fn (c Context) compare_v_performance(label string, commands []string) string {
|
|||
} else {
|
||||
source_location_b = if os.exists('$c.b/v.v') { 'v.v ' } else { 'compiler/ ' }
|
||||
}
|
||||
timestamp_a,_ := vgit.line_to_timestamp_and_commit(scripting.run('cd $c.a/ ; git rev-list -n1 --timestamp HEAD'))
|
||||
timestamp_b,_ := vgit.line_to_timestamp_and_commit(scripting.run('cd $c.b/ ; git rev-list -n1 --timestamp HEAD'))
|
||||
timestamp_a, _ := vgit.line_to_timestamp_and_commit(scripting.run('cd $c.a/ ; git rev-list -n1 --timestamp HEAD'))
|
||||
timestamp_b, _ := vgit.line_to_timestamp_and_commit(scripting.run('cd $c.b/ ; git rev-list -n1 --timestamp HEAD'))
|
||||
debug_option_a := if timestamp_a > 1570877641 { '-cg ' } else { '-debug ' }
|
||||
debug_option_b := if timestamp_b > 1570877641 { '-cg ' } else { '-debug ' }
|
||||
mut hyperfine_commands_arguments := []string{}
|
||||
|
@ -156,14 +140,17 @@ fn (c Context) compare_v_performance(label string, commands []string) string {
|
|||
println(cmd)
|
||||
}
|
||||
for cmd in commands {
|
||||
hyperfine_commands_arguments << " \'cd ${c.b:-34s} ; ./$cmd \' ".replace_each(['@COMPILER@', source_location_b, '@DEBUG@', debug_option_b])
|
||||
hyperfine_commands_arguments <<
|
||||
" \'cd ${c.b:-34s} ; ./$cmd \' ".replace_each(['@COMPILER@', source_location_b, '@DEBUG@', debug_option_b])
|
||||
}
|
||||
for cmd in commands {
|
||||
hyperfine_commands_arguments << " \'cd ${c.a:-34s} ; ./$cmd \' ".replace_each(['@COMPILER@', source_location_a, '@DEBUG@', debug_option_a])
|
||||
hyperfine_commands_arguments <<
|
||||
" \'cd ${c.a:-34s} ; ./$cmd \' ".replace_each(['@COMPILER@', source_location_a, '@DEBUG@', debug_option_a])
|
||||
}
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
cmd_stats_file := os.real_path([c.vgo.workdir, 'v_performance_stats_${label}.json'].join(os.path_separator))
|
||||
comparison_cmd := 'hyperfine $c.hyperfineopts ' + '--export-json ${cmd_stats_file} ' + '--time-unit millisecond ' + '--style full --warmup $c.warmups ' + hyperfine_commands_arguments.join(' ')
|
||||
comparison_cmd := 'hyperfine $c.hyperfineopts ' + '--export-json $cmd_stats_file ' + '--time-unit millisecond ' +
|
||||
'--style full --warmup $c.warmups ' + hyperfine_commands_arguments.join(' ')
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
if c.vgo.verbose {
|
||||
println(comparison_cmd)
|
||||
|
@ -175,7 +162,8 @@ fn (c Context) compare_v_performance(label string, commands []string) string {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
scripting.used_tools_must_exist(['cp', 'rm', 'strip', 'make', 'git', 'upx', 'cc', 'wc', 'tail', 'find', 'xargs', 'hyperfine'])
|
||||
scripting.used_tools_must_exist(['cp', 'rm', 'strip', 'make', 'git', 'upx', 'cc', 'wc', 'tail',
|
||||
'find', 'xargs', 'hyperfine'])
|
||||
mut context := new_context()
|
||||
mut fp := flag.new_flag_parser(os.args)
|
||||
fp.application(os.file_name(os.executable()))
|
||||
|
@ -184,12 +172,10 @@ fn main() {
|
|||
fp.arguments_description('COMMIT_BEFORE [COMMIT_AFTER]')
|
||||
fp.skip_executable()
|
||||
fp.limit_free_args(1, 2)
|
||||
|
||||
context.vflags = fp.string('vflags', 0, '', 'Additional options to pass to the v commands, for example "-cc tcc"')
|
||||
context.hyperfineopts = fp.string('hyperfine_options', 0, '',
|
||||
'Additional options passed to hyperfine.
|
||||
context.hyperfineopts = fp.string('hyperfine_options', 0, '', 'Additional options passed to hyperfine.
|
||||
${flag.space}For example on linux, you may want to pass:
|
||||
${flag.space}--hyperfine_options "--prepare \'sync; echo 3 | sudo tee /proc/sys/vm/drop_caches\'"
|
||||
$flag.space--hyperfine_options "--prepare \'sync; echo 3 | sudo tee /proc/sys/vm/drop_caches\'"
|
||||
')
|
||||
commits := vgit.add_common_tool_options(mut context.vgo, mut fp)
|
||||
context.commit_before = commits[0]
|
||||
|
@ -204,6 +190,5 @@ ${flag.space}--hyperfine_options "--prepare \'sync; echo 3 | sudo tee /proc/sys/
|
|||
eprintln(msg)
|
||||
exit(2)
|
||||
}
|
||||
|
||||
context.compare_versions()
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ module main
|
|||
|
||||
import os
|
||||
import term
|
||||
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// / This file will get compiled as part of the main program,
|
||||
// / for a _test.v file.
|
||||
|
@ -14,29 +15,26 @@ import term
|
|||
fn cb_assertion_failed(i &VAssertMetaInfo) {
|
||||
use_color := term.can_show_color_on_stderr()
|
||||
use_relative_paths := match os.getenv('VERROR_PATHS') {
|
||||
'absolute' {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
'absolute' { false }
|
||||
else { true }
|
||||
}
|
||||
final_filename := if use_relative_paths { i.fpath } else { os.real_path(i.fpath) }
|
||||
final_funcname := i.fn_name.replace('main.', '').replace('__', '.')
|
||||
final_src := if use_color { term.bold(i.src) } else { i.src }
|
||||
eprintln('')
|
||||
eprintln('$final_filename:${i.line_nr+1}: failed assert in function ${final_funcname}')
|
||||
eprintln('Source : `${final_src}`')
|
||||
eprintln('$final_filename:${i.line_nr+1}: failed assert in function $final_funcname')
|
||||
eprintln('Source : `$final_src`')
|
||||
if i.op.len > 0 && i.op != 'call' {
|
||||
mut slvalue := '${i.lvalue}'
|
||||
mut srvalue := '${i.rvalue}'
|
||||
lpostfix := if slvalue == i.llabel { '.' } else { '<= `${i.llabel}`' }
|
||||
rpostfix := if srvalue == i.rlabel { '.' } else { '<= `${i.rlabel}`' }
|
||||
mut slvalue := '$i.lvalue'
|
||||
mut srvalue := '$i.rvalue'
|
||||
lpostfix := if slvalue == i.llabel { '.' } else { '<= `$i.llabel`' }
|
||||
rpostfix := if srvalue == i.rlabel { '.' } else { '<= `$i.rlabel`' }
|
||||
if use_color {
|
||||
slvalue = term.bold(term.yellow(slvalue))
|
||||
srvalue = term.bold(term.yellow(srvalue))
|
||||
}
|
||||
eprintln(' left value: ${slvalue} ${lpostfix}')
|
||||
eprintln(' right value: ${srvalue} ${rpostfix}')
|
||||
eprintln(' left value: $slvalue $lpostfix')
|
||||
eprintln(' right value: $srvalue $rpostfix')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
module main
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////
|
||||
// / This file will get compiled as a part of the same module,
|
||||
// / in which a given _test.v file is, when v is given -stats argument
|
||||
// / The methods defined here are called back by the test program's
|
||||
// / main function, so that customizing the look & feel of the results
|
||||
// / main function, so that customizing the look & feel of the results
|
||||
// / is easy, since it is done in normal V code, instead of in embedded C ...
|
||||
// /////////////////////////////////////////////////////////////////////
|
||||
import os
|
||||
|
@ -77,7 +78,8 @@ fn (b &BenchedTests) fn_name() string {
|
|||
// Called at the end of the test program produced by `v -stats file_test.v`
|
||||
fn (mut b BenchedTests) end_testing() {
|
||||
b.bench.stop()
|
||||
println(inner_indent + b.bench.total_message('running V tests in "' + os.file_name(b.test_suit_file) + '"'))
|
||||
println(inner_indent + b.bench.total_message('running V tests in "' + os.file_name(b.test_suit_file) +
|
||||
'"'))
|
||||
}
|
||||
|
||||
// ///////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -5,37 +5,19 @@ import testing
|
|||
|
||||
const (
|
||||
known_failing_exceptions = [
|
||||
'examples/vweb/vweb_example.v',
|
||||
'cmd/tools/gen_vc.v',
|
||||
'cmd/tools/modules/vgit/vgit.v', // generics
|
||||
'cmd/tools/preludes/live_main.v',
|
||||
'cmd/tools/preludes/live_shared.v',
|
||||
'cmd/tools/preludes/tests_assertions.v',
|
||||
'cmd/tools/preludes/tests_with_stats.v',
|
||||
'cmd/tools/performance_compare.v', // generics
|
||||
'cmd/tools/oldv.v', // generics
|
||||
'tutorials/code/blog/article.v',
|
||||
'tutorials/code/blog/blog.v',
|
||||
'vlib/arrays/arrays.v',
|
||||
'vlib/arrays/arrays_test.v',
|
||||
'vlib/builtin/js/hashmap.v',
|
||||
'vlib/v/tests/fn_variadic_test.v',
|
||||
'vlib/v/tests/generic_test.v',
|
||||
'vlib/crypto/aes/aes.v',
|
||||
'vlib/crypto/aes/aes_cbc.v',
|
||||
'vlib/crypto/aes/block_generic.v',
|
||||
'vlib/crypto/aes/const.v',
|
||||
'vlib/crypto/aes/cypher_generic.v',
|
||||
'vlib/crypto/rc4/rc4.v',
|
||||
'vlib/eventbus/eventbus_test.v',
|
||||
'vlib/os/bare/bare_example_linux.v',
|
||||
'vlib/szip/szip.v',
|
||||
'vlib/uiold/examples/users_gui/users.v',
|
||||
'vlib/vweb/assets/assets.v',
|
||||
'vlib/vweb/vweb.v',
|
||||
'vlib/v/gen/js/tests/life.v',
|
||||
'vlib/builtin/bare/linuxsys_bare.v',
|
||||
'vlib/os/os.v',
|
||||
'vlib/v/tests/generics_test.v', // struct Repo<T, U> { => struct Repo {
|
||||
'vlib/crypto/aes/aes.v', // pub fn (c &AesCipher) encrypt(mut dst, mut src []byte) {
|
||||
'vlib/crypto/aes/block_generic.v', // fn expand_key_generic(key []byte, mut enc, mut dec []u32) {
|
||||
'vlib/crypto/aes/const.v', // multiple narrow columns of []string turned to 1 long single column, otherwise works
|
||||
'vlib/crypto/rc4/rc4.v', // pub fn (mut c Cipher) xor_key_stream(mut dst, mut src []byte) {
|
||||
'vlib/vweb/vweb.v', // $for method in T.methods { => $for method in T(methods) { , `return // xx` => parse expr error
|
||||
'vlib/v/gen/js/tests/life.v', // error: unexpected `,`, expecting ), on JS.setInterval(fn () { show(game) game = step(game) }, 500)
|
||||
'vlib/builtin/js/builtin.v', // JS.console.error(s) => JS.error(s), JS.process.exit(c) => JS.exit(c)
|
||||
'vlib/builtin/js/jsfns_node.js.v',
|
||||
'vlib/builtin/js/jsfns.js.v',
|
||||
'vlib/builtin/js/jsfns_browser.js.v',
|
||||
'vlib/builtin/bare/linuxsys_bare.v', // error: expr(): bad token `asm`, on `asm {}`
|
||||
'vlib/os/os.v', // embeded comments, mib := [1/* CTL_KERN */, 14/* KERN_PROC */, 12/* KERN_PROC_PATHNAME */, -1] => comment the rest of the line
|
||||
]
|
||||
)
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ const (
|
|||
struct App {
|
||||
pub mut:
|
||||
vweb vweb.Context // TODO embed
|
||||
cnt int
|
||||
cnt int
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
@ -31,9 +31,9 @@ pub fn (mut app App) json_endpoint() vweb.Result {
|
|||
pub fn (mut app App) index() vweb.Result {
|
||||
app.cnt++
|
||||
show := true
|
||||
//app.vweb.text('Hello world from vweb')
|
||||
// app.vweb.text('Hello world from vweb')
|
||||
hello := 'Hello world from vweb'
|
||||
numbers := [1,2,3]
|
||||
numbers := [1, 2, 3]
|
||||
return $vweb.html()
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,9 @@ pub fn (mut app App) text() vweb.Result {
|
|||
}
|
||||
|
||||
pub fn (mut app App) cookie() vweb.Result {
|
||||
app.vweb.set_cookie(name:'cookie', value:'test')
|
||||
app.vweb.set_cookie({
|
||||
name: 'cookie'
|
||||
value: 'test'
|
||||
})
|
||||
return app.vweb.text('Headers: $app.vweb.headers')
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ pub fn (app &App) index_html() vweb.Result {
|
|||
return $vweb.html()
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn (app &App) index() vweb.Result {
|
||||
articles := app.find_all_articles()
|
||||
return $vweb.html()
|
||||
|
@ -53,7 +52,7 @@ pub fn (mut app App) new_article() vweb.Result {
|
|||
app.vweb.text('Empty text/title')
|
||||
return vweb.Result{}
|
||||
}
|
||||
article := Article {
|
||||
article := Article{
|
||||
title: title
|
||||
text: text
|
||||
}
|
||||
|
|
|
@ -5,15 +5,16 @@ module arrays
|
|||
// - idx_min / idx_max - return the index of the first minumum / maximum
|
||||
// - shuffle - randomize array items order in place (allowing exit after n items)
|
||||
// - merge - combine two sorted arrays and maintain sorted order
|
||||
|
||||
import rand
|
||||
|
||||
// min returns the minimum
|
||||
[direct_array_access]
|
||||
pub fn min<T>(a []T) T {
|
||||
if a.len==0 { panic('.min called on an empty array') }
|
||||
if a.len == 0 {
|
||||
panic('.min called on an empty array')
|
||||
}
|
||||
mut val := a[0]
|
||||
for i in 0..a.len {
|
||||
for i in 0 .. a.len {
|
||||
if a[i] < val {
|
||||
val = a[i]
|
||||
}
|
||||
|
@ -24,9 +25,11 @@ pub fn min<T>(a []T) T {
|
|||
// max returns the maximum
|
||||
[direct_array_access]
|
||||
pub fn max<T>(a []T) T {
|
||||
if a.len==0 { panic('.max called on an empty array') }
|
||||
if a.len == 0 {
|
||||
panic('.max called on an empty array')
|
||||
}
|
||||
mut val := a[0]
|
||||
for i in 0..a.len {
|
||||
for i in 0 .. a.len {
|
||||
if a[i] > val {
|
||||
val = a[i]
|
||||
}
|
||||
|
@ -37,10 +40,12 @@ pub fn max<T>(a []T) T {
|
|||
// idx_min returns the index of the first minimum
|
||||
[direct_array_access]
|
||||
pub fn idx_min<T>(a []T) int {
|
||||
if a.len==0 { panic('.idxmin called on an empty array') }
|
||||
if a.len == 0 {
|
||||
panic('.idxmin called on an empty array')
|
||||
}
|
||||
mut idx := 0
|
||||
mut val := a[0]
|
||||
for i in 0..a.len {
|
||||
for i in 0 .. a.len {
|
||||
if a[i] < val {
|
||||
val = a[i]
|
||||
idx = i
|
||||
|
@ -52,10 +57,12 @@ pub fn idx_min<T>(a []T) int {
|
|||
// idx_max returns the index of the first maximum
|
||||
[direct_array_access]
|
||||
pub fn idx_max<T>(a []T) int {
|
||||
if a.len==0 { panic('.idxmax called on an empty array') }
|
||||
if a.len == 0 {
|
||||
panic('.idxmax called on an empty array')
|
||||
}
|
||||
mut idx := 0
|
||||
mut val := a[0]
|
||||
for i in 0..a.len {
|
||||
for i in 0 .. a.len {
|
||||
if a[i] > val {
|
||||
val = a[i]
|
||||
idx = i
|
||||
|
@ -67,10 +74,12 @@ pub fn idx_max<T>(a []T) int {
|
|||
// shuffle randomizes the first n items of an array in place (all if n=0)
|
||||
[direct_array_access]
|
||||
pub fn shuffle<T>(mut a []T, n int) {
|
||||
if n < 0 || n > a.len { panic("shuffle's argument 'n' must be in range [0,a.len]") }
|
||||
cnt := if n==0 { a.len-1 } else { n }
|
||||
for i in 0..cnt {
|
||||
x := rand.int_in_range(i,a.len)
|
||||
if n < 0 || n > a.len {
|
||||
panic("shuffle's argument 'n' must be in range [0,a.len]")
|
||||
}
|
||||
cnt := if n == 0 { a.len - 1 } else { n }
|
||||
for i in 0 .. cnt {
|
||||
x := rand.int_in_range(i, a.len)
|
||||
// swap
|
||||
a_i := a[i]
|
||||
a[i] = a[x]
|
||||
|
@ -78,17 +87,15 @@ pub fn shuffle<T>(mut a []T, n int) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// merge two sorted arrays (ascending) and maintain sorted order
|
||||
[direct_array_access]
|
||||
pub fn merge<T>(a []T, b []T) []T {
|
||||
mut m := []T{len:a.len + b.len}
|
||||
pub fn merge<T>(a, b []T) []T {
|
||||
mut m := []T{len: a.len + b.len}
|
||||
mut ia := 0
|
||||
mut ib := 0
|
||||
mut j := 0
|
||||
|
||||
// TODO efficient approach to merge_desc where: a[ia] >= b[ib]
|
||||
for ia<a.len && ib<b.len {
|
||||
for ia < a.len && ib < b.len {
|
||||
if a[ia] <= b[ib] {
|
||||
m[j] = a[ia]
|
||||
ia++
|
||||
|
@ -98,21 +105,17 @@ pub fn merge<T>(a []T, b []T) []T {
|
|||
}
|
||||
j++
|
||||
}
|
||||
|
||||
// a leftovers
|
||||
for ia < a.len {
|
||||
m[j] = a[ia]
|
||||
ia++
|
||||
j++
|
||||
}
|
||||
|
||||
// b leftovers
|
||||
for ib < b.len {
|
||||
m[j] = b[ib]
|
||||
ib++
|
||||
j++
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
|
|
|
@ -4,94 +4,78 @@ import rand
|
|||
|
||||
fn test_min() {
|
||||
a := [8, 2, 6, 4]
|
||||
assert min<int>(a)==2
|
||||
assert min<int>(a[2..])==4
|
||||
|
||||
assert min<int>(a) == 2
|
||||
assert min<int>(a[2..]) == 4
|
||||
b := [f32(5.1), 3.1, 1.1, 9.1]
|
||||
assert min<f32>(b) == f32(1.1)
|
||||
assert min<f32>(b[..2]) == f32(3.1)
|
||||
|
||||
c := [byte(4), 9, 3, 1]
|
||||
assert min<byte>(c) == byte(1)
|
||||
assert min<byte>(c[..3]) == byte(3)
|
||||
}
|
||||
|
||||
|
||||
fn test_max() {
|
||||
a := [8, 2, 6, 4]
|
||||
assert max<int>(a)==8
|
||||
assert max<int>(a[1..])==6
|
||||
|
||||
assert max<int>(a) == 8
|
||||
assert max<int>(a[1..]) == 6
|
||||
b := [f32(5.1), 3.1, 1.1, 9.1]
|
||||
assert max<f32>(b) == f32(9.1)
|
||||
assert max<f32>(b[..3]) == f32(5.1)
|
||||
|
||||
c := [byte(4), 9, 3, 1]
|
||||
assert max<byte>(c) == byte(9)
|
||||
assert max<byte>(c[2..]) == byte(3)
|
||||
}
|
||||
|
||||
|
||||
fn test_idx_min() {
|
||||
a := [8, 2, 6, 4]
|
||||
assert idx_min<int>(a)==1
|
||||
|
||||
assert idx_min<int>(a) == 1
|
||||
b := [f32(5.1), 3.1, 1.1, 9.1]
|
||||
assert idx_min<f32>(b) == 2
|
||||
|
||||
c := [byte(4), 9, 3, 1]
|
||||
assert idx_min<byte>(c) == 3
|
||||
}
|
||||
|
||||
|
||||
fn test_idx_max() {
|
||||
a := [8, 2, 6, 4]
|
||||
assert idx_max<int>(a)==0
|
||||
|
||||
assert idx_max<int>(a) == 0
|
||||
b := [f32(5.1), 3.1, 1.1, 9.1]
|
||||
assert idx_max<f32>(b) == 3
|
||||
|
||||
c := [byte(4), 9, 3, 1]
|
||||
assert idx_max<byte>(c) == 1
|
||||
}
|
||||
|
||||
|
||||
fn test_shuffle() {
|
||||
rand.seed([u32(1),2]) // set seed to produce same results in order
|
||||
|
||||
a := [1,2,3,4,5,6,7,8,9,10]
|
||||
rand.seed([u32(1), 2]) // set seed to produce same results in order
|
||||
a := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
mut b := a.clone()
|
||||
mut c := a.clone()
|
||||
shuffle<int>(mut b, 0)
|
||||
shuffle<int>(mut c, 0)
|
||||
assert b == [6, 4, 5, 1, 9, 2, 10, 3, 8, 7]
|
||||
assert c == [1, 6, 5, 8, 7, 2, 10, 9, 3, 4]
|
||||
|
||||
// test shuffling a slice
|
||||
mut d := a.clone()
|
||||
shuffle<int>(mut d[..5], 0)
|
||||
assert d == [5, 2, 1, 3, 4, 6, 7, 8, 9, 10]
|
||||
assert d[5..] == a[5..]
|
||||
|
||||
// test shuffling n items
|
||||
mut e := a.clone()
|
||||
shuffle<int>(mut e, 5)
|
||||
assert e[..5] == [10, 3, 1, 8, 4]
|
||||
assert e[5..] == [6, 7, 5, 9, 2]
|
||||
|
||||
// test shuffling empty array
|
||||
mut f := a[..0]
|
||||
shuffle<int>(mut f,0)
|
||||
shuffle<int>(mut f, 0)
|
||||
assert f == []int{}
|
||||
}
|
||||
|
||||
fn test_merge() {
|
||||
a := [1,3,5,5,7]
|
||||
b := [2,4,4,5,6,8]
|
||||
a := [1, 3, 5, 5, 7]
|
||||
b := [2, 4, 4, 5, 6, 8]
|
||||
c := []int{}
|
||||
d := []int{}
|
||||
assert merge<int>(a,b) == [1,2,3,4,4,5,5,5,6,7,8]
|
||||
assert merge<int>(c,d) == []
|
||||
assert merge<int>(a,c) == a
|
||||
assert merge<int>(d,b) == b
|
||||
assert merge<int>(a, b) == [1, 2, 3, 4, 4, 5, 5, 5, 6, 7, 8]
|
||||
assert merge<int>(c, d) == []
|
||||
assert merge<int>(a, c) == a
|
||||
assert merge<int>(d, b) == b
|
||||
}
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
// Cipher block chaining (CBC) mode.
|
||||
|
||||
// CBC provides confidentiality by xoring (chaining) each plaintext block
|
||||
// with the previous ciphertext block before applying the block cipher.
|
||||
|
||||
// See NIST SP 800-38A, pp 10-11
|
||||
|
||||
// NOTE this will be moved to crypto.cipher interface (joe-c)
|
||||
|
||||
module aes
|
||||
|
||||
import crypto.cipher
|
||||
|
@ -27,10 +22,10 @@ mut:
|
|||
// internal
|
||||
fn new_aes_cbc(b AesCipher, iv []byte) AesCbc {
|
||||
return AesCbc{
|
||||
b: b,
|
||||
block_size: b.block_size(),
|
||||
iv: iv.clone(),
|
||||
tmp: []byte{len:(b.block_size()),}
|
||||
b: b
|
||||
block_size: b.block_size()
|
||||
iv: iv.clone()
|
||||
tmp: []byte{len: (b.block_size())}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,12 +39,14 @@ pub fn new_cbc(b AesCipher, iv []byte) AesCbc {
|
|||
return new_aes_cbc(b, iv)
|
||||
}
|
||||
|
||||
pub fn (x &AesCbc) block_size() int { return x.block_size }
|
||||
pub fn (x &AesCbc) block_size() int {
|
||||
return x.block_size
|
||||
}
|
||||
|
||||
pub fn (x &AesCbc) encrypt_blocks(mut dst_ []byte, src_ []byte) {
|
||||
mut dst := *dst_
|
||||
mut src := src_
|
||||
if src.len%x.block_size != 0 {
|
||||
if src.len % x.block_size != 0 {
|
||||
panic('crypto.cipher: input not full blocks')
|
||||
}
|
||||
if dst.len < src.len {
|
||||
|
@ -58,14 +55,11 @@ pub fn (x &AesCbc) encrypt_blocks(mut dst_ []byte, src_ []byte) {
|
|||
if subtle.inexact_overlap(dst[..src.len], src_) {
|
||||
panic('crypto.cipher: invalid buffer overlap')
|
||||
}
|
||||
|
||||
mut iv := x.iv
|
||||
|
||||
for src.len > 0 {
|
||||
// Write the xor to dst, then encrypt in place.
|
||||
cipher.xor_bytes(mut dst[..x.block_size], src[..x.block_size], iv)
|
||||
x.b.encrypt(mut dst[..x.block_size], mut dst[..x.block_size])
|
||||
|
||||
// Move to the next block with this block as the next iv.
|
||||
iv = dst[..x.block_size]
|
||||
if x.block_size >= src.len {
|
||||
|
@ -75,13 +69,12 @@ pub fn (x &AesCbc) encrypt_blocks(mut dst_ []byte, src_ []byte) {
|
|||
}
|
||||
dst = dst[x.block_size..]
|
||||
}
|
||||
|
||||
// Save the iv for the next crypt_blocks call.
|
||||
copy(x.iv, iv)
|
||||
}
|
||||
|
||||
pub fn (mut x AesCbc) decrypt_blocks(mut dst []byte, src []byte) {
|
||||
if src.len%x.block_size != 0 {
|
||||
if src.len % x.block_size != 0 {
|
||||
panic('crypto.cipher: input not full blocks')
|
||||
}
|
||||
if dst.len < src.len {
|
||||
|
@ -93,33 +86,27 @@ pub fn (mut x AesCbc) decrypt_blocks(mut dst []byte, src []byte) {
|
|||
if src.len == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// For each block, we need to xor the decrypted data with the previous block's ciphertext (the iv).
|
||||
// To avoid making a copy each time, we loop over the blocks BACKWARDS.
|
||||
mut end := src.len
|
||||
mut start := end - x.block_size
|
||||
mut prev := start - x.block_size
|
||||
|
||||
// Copy the last block of ciphertext in preparation as the new iv.
|
||||
copy(x.tmp, src.slice(start, end))
|
||||
|
||||
// Loop over all but the first block.
|
||||
for start > 0 {
|
||||
mut src_chunk := src.slice(start, end)
|
||||
x.b.decrypt(mut (*dst).slice(start, end), mut src_chunk)
|
||||
cipher.xor_bytes(mut (*dst).slice(start, end), (*dst).slice(start, end), src.slice(prev, start))
|
||||
|
||||
cipher.xor_bytes(mut (*dst).slice(start, end), (*dst).slice(start, end), src.slice(prev,
|
||||
start))
|
||||
end = start
|
||||
start = prev
|
||||
prev -= x.block_size
|
||||
}
|
||||
|
||||
// The first block is special because it uses the saved iv.
|
||||
mut src_chunk := src.slice(start, end)
|
||||
x.b.decrypt(mut (*dst).slice(start, end), mut src_chunk)
|
||||
cipher.xor_bytes(mut (*dst).slice(start, end), (*dst).slice(start, end), x.iv)
|
||||
|
||||
|
||||
// Set the new iv to the first block we copied earlier.
|
||||
x.iv = x.tmp
|
||||
x.tmp = x.iv
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import crypto.aes
|
||||
|
||||
fn test_crypto_aes() {
|
||||
|
@ -17,12 +16,12 @@ fn test_crypto_aes() {
|
|||
iv := ciphertext[..aes.block_size]
|
||||
ciphertext = ciphertext[aes.block_size..]
|
||||
// CBC mode always works in whole blocks.
|
||||
if ciphertext.len%aes.block_size != 0 {
|
||||
if ciphertext.len % aes.block_size != 0 {
|
||||
panic('ciphertext is not a multiple of the block size')
|
||||
}
|
||||
mode := aes.new_cbc(block, iv)
|
||||
cipher_clone := ciphertext.clone()
|
||||
mode.encrypt_blocks(mut ciphertext, cipher_clone)
|
||||
|
||||
assert ciphertext.hex() == 'c210459b514668ddc44674885e4979215265a6c44431a248421254ef357a8c2a308a8bddf5623af9df91737562041cf1'
|
||||
assert ciphertext.hex() ==
|
||||
'c210459b514668ddc44674885e4979215265a6c44431a248421254ef357a8c2a308a8bddf5623af9df91737562041cf1'
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module aes
|
||||
|
||||
// new_cipher_generic creates and returns a new cipher.Block
|
||||
|
@ -9,8 +8,8 @@ module aes
|
|||
fn new_cipher_generic(key []byte) AesCipher {
|
||||
n := key.len + 28
|
||||
mut c := AesCipher{
|
||||
enc: []u32{len:(n)}
|
||||
dec: []u32{len:(n)}
|
||||
enc: []u32{len: (n)}
|
||||
dec: []u32{len: (n)}
|
||||
}
|
||||
expand_key_generic(key, mut c.enc, mut c.dec)
|
||||
return c
|
||||
|
|
|
@ -1,35 +1,28 @@
|
|||
import eventbus
|
||||
|
||||
struct EventData {
|
||||
data string
|
||||
data string
|
||||
}
|
||||
|
||||
fn test_eventbus(){
|
||||
fn test_eventbus() {
|
||||
ev_data := &EventData{'hello'}
|
||||
mut eb := eventbus.new()
|
||||
eb.subscriber.subscribe_once("on_test", on_test)
|
||||
|
||||
assert eb.has_subscriber("on_test")
|
||||
assert eb.subscriber.is_subscribed("on_test")
|
||||
|
||||
eb.publish("on_test", eb, ev_data)
|
||||
|
||||
assert !eb.has_subscriber("on_test")
|
||||
assert !eb.subscriber.is_subscribed("on_test")
|
||||
|
||||
eb.subscriber.subscribe("on_test", on_test)
|
||||
|
||||
assert eb.has_subscriber("on_test")
|
||||
assert eb.subscriber.is_subscribed("on_test")
|
||||
|
||||
eb.subscriber.subscribe_once('on_test', on_test)
|
||||
assert eb.has_subscriber('on_test')
|
||||
assert eb.subscriber.is_subscribed('on_test')
|
||||
eb.publish('on_test', eb, ev_data)
|
||||
assert !eb.has_subscriber('on_test')
|
||||
assert !eb.subscriber.is_subscribed('on_test')
|
||||
eb.subscriber.subscribe('on_test', on_test)
|
||||
assert eb.has_subscriber('on_test')
|
||||
assert eb.subscriber.is_subscribed('on_test')
|
||||
eb.clear_all()
|
||||
|
||||
assert !eb.has_subscriber("on_test")
|
||||
assert !eb.subscriber.is_subscribed("on_test")
|
||||
assert !eb.has_subscriber('on_test')
|
||||
assert !eb.subscriber.is_subscribed('on_test')
|
||||
}
|
||||
|
||||
fn on_test(receiver voidptr, ev &EventData, sender voidptr) {
|
||||
assert receiver == 0
|
||||
assert sender != 0
|
||||
assert ev.data == "hello"
|
||||
assert ev.data == 'hello'
|
||||
}
|
||||
|
|
|
@ -3,5 +3,5 @@ fn main() {
|
|||
s := 'test string\n'
|
||||
sys_write(1, s.str, u64(s.len))
|
||||
a := s[0]
|
||||
println("Hello freestanding!")
|
||||
println('Hello freestanding!')
|
||||
}
|
||||
|
|
|
@ -3,24 +3,37 @@ module szip
|
|||
#flag -I @VROOT/thirdparty/zip
|
||||
#include "zip.c"
|
||||
#include "zip.h"
|
||||
|
||||
struct C.zip_t {}
|
||||
struct C.zip_t {
|
||||
}
|
||||
|
||||
type Zip = C.zip_t
|
||||
|
||||
fn C.zip_open(byteptr, int, byte) &Zip
|
||||
|
||||
fn C.zip_close(&Zip)
|
||||
|
||||
fn C.zip_entry_open(&Zip, byteptr) int
|
||||
|
||||
fn C.zip_entry_close(&Zip) int
|
||||
|
||||
fn C.zip_entry_name(&Zip) byteptr
|
||||
|
||||
fn C.zip_entry_index(&Zip) int
|
||||
|
||||
fn C.zip_entry_isdir(&Zip) int
|
||||
|
||||
fn C.zip_entry_size(&Zip) u64
|
||||
|
||||
fn C.zip_entry_crc32(&Zip) u32
|
||||
|
||||
fn C.zip_entry_write(&Zip, voidptr, int) int
|
||||
|
||||
fn C.zip_entry_fwrite(&Zip, byteptr) int
|
||||
|
||||
fn C.zip_entry_read(&Zip, byteptr, int) int
|
||||
|
||||
fn C.zip_entry_fread(&Zip, byteptr) int
|
||||
|
||||
fn C.zip_total_entries(&Zip) int
|
||||
|
||||
// Ref - miniz.h
|
||||
|
|
|
@ -64,7 +64,8 @@ fn test_variadic_only_with_no_vargs() {
|
|||
fn_variadic_only_with_no_vargs()
|
||||
}
|
||||
|
||||
struct VaTestStruct {}
|
||||
struct VaTestStruct {
|
||||
}
|
||||
|
||||
fn (a VaTestStruct) variadic_method(name string, groups ...VaTestGroup) {
|
||||
assert groups.len == 2
|
||||
|
|
|
@ -2,7 +2,6 @@ module assets
|
|||
|
||||
// this module provides an AssetManager for combining
|
||||
// and caching javascript & css.
|
||||
|
||||
import os
|
||||
import time
|
||||
import crypto.md5
|
||||
|
@ -101,7 +100,9 @@ fn (am AssetManager) combine(asset_type string, to_file bool) string {
|
|||
return out
|
||||
}
|
||||
if !os.is_dir(am.cache_dir) {
|
||||
os.mkdir(am.cache_dir) or { panic(err) }
|
||||
os.mkdir(am.cache_dir) or {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
mut file := os.create(out_file) or {
|
||||
panic(err)
|
||||
|
@ -157,7 +158,9 @@ fn (mut am AssetManager) add(asset_type, file string) bool {
|
|||
}
|
||||
asset := Asset{
|
||||
file_path: file
|
||||
last_modified: time.Time{unix: u64(os.file_last_mod_unix(file))}
|
||||
last_modified: time.Time{
|
||||
unix: u64(os.file_last_mod_unix(file))
|
||||
}
|
||||
}
|
||||
if asset_type == 'css' {
|
||||
am.css << asset
|
||||
|
@ -183,11 +186,7 @@ fn (am AssetManager) get_assets(asset_type string) []Asset {
|
|||
if asset_type != 'css' && asset_type != 'js' {
|
||||
panic('$unknown_asset_type_error ($asset_type).')
|
||||
}
|
||||
assets := if asset_type == 'css' {
|
||||
am.css
|
||||
} else {
|
||||
am.js
|
||||
}
|
||||
assets := if asset_type == 'css' { am.css } else { am.js }
|
||||
return assets
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue