mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
tools, examples: add --only-watch=*.v
option to v watch
(#18974)
This commit is contained in:
parent
7d6fd9dade
commit
d25e213aa8
@ -95,6 +95,7 @@ mut:
|
|||||||
ignore_exts []string // extensions of files that will be ignored, even if they change (useful for sqlite.db files for example)
|
ignore_exts []string // extensions of files that will be ignored, even if they change (useful for sqlite.db files for example)
|
||||||
cmd_before_run string // a command to run before each re-run
|
cmd_before_run string // a command to run before each re-run
|
||||||
cmd_after_run string // a command to run after each re-run
|
cmd_after_run string // a command to run after each re-run
|
||||||
|
only_watch []string // If not empty, *all* files that trigger updates, should match *at least one* of these s.match_glob() patterns. This is also triggered for vweb apps, to monitor for just *.v,*.js,*.css,*.html in vweb projects.
|
||||||
}
|
}
|
||||||
|
|
||||||
[if debug_vwatch ?]
|
[if debug_vwatch ?]
|
||||||
@ -106,6 +107,33 @@ fn (context &Context) str() string {
|
|||||||
return 'Context{ pid: ${context.pid}, is_worker: ${context.is_worker}, check_period_ms: ${context.check_period_ms}, vexe: ${context.vexe}, opts: ${context.opts}, is_exiting: ${context.is_exiting}, vfiles: ${context.vfiles}'
|
return 'Context{ pid: ${context.pid}, is_worker: ${context.is_worker}, check_period_ms: ${context.check_period_ms}, vexe: ${context.vexe}, opts: ${context.opts}, is_exiting: ${context.is_exiting}, vfiles: ${context.vfiles}'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn (mut context Context) is_ext_ignored(pf string, pf_ext string) bool {
|
||||||
|
for ipattern in context.ignore_exts {
|
||||||
|
if pf_ext.match_glob(ipattern) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pf_ext in ['', '.so', '.a'] {
|
||||||
|
// on unix, the executables saved by compilers, usually do not have extensions at all, and shared libs are .so
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if pf_ext in ['.exe', '.dll', '.def'] {
|
||||||
|
// on windows, files with these extensions will be generated by the compiler
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// ignore common backup files saved by editors like emacs/jed/vim:
|
||||||
|
if pf_ext == '.bak' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if pf.starts_with('.#') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if pf.ends_with('~') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
fn (mut context Context) get_stats_for_affected_vfiles() []VFileStat {
|
fn (mut context Context) get_stats_for_affected_vfiles() []VFileStat {
|
||||||
if context.affected_paths.len == 0 {
|
if context.affected_paths.len == 0 {
|
||||||
mut apaths := map[string]bool{}
|
mut apaths := map[string]bool{}
|
||||||
@ -121,34 +149,63 @@ fn (mut context Context) get_stats_for_affected_vfiles() []VFileStat {
|
|||||||
vfiles := os.execute(cmd)
|
vfiles := os.execute(cmd)
|
||||||
if vfiles.exit_code == 0 {
|
if vfiles.exit_code == 0 {
|
||||||
paths_trimmed := vfiles.output.trim_space()
|
paths_trimmed := vfiles.output.trim_space()
|
||||||
reported_used_files := paths_trimmed.split('\n')
|
reported_used_files := paths_trimmed.split_any('\n')
|
||||||
$if trace_reported_used_files ? {
|
$if trace_reported_used_files ? {
|
||||||
context.elog('reported_used_files: ${reported_used_files}')
|
context.elog('reported_used_files: ${reported_used_files}')
|
||||||
}
|
}
|
||||||
paths << reported_used_files
|
paths << reported_used_files
|
||||||
}
|
}
|
||||||
|
mut is_vweb_found := false
|
||||||
for vf in paths {
|
for vf in paths {
|
||||||
apaths[os.real_path(os.dir(vf))] = true
|
apaths[os.real_path(os.dir(vf))] = true
|
||||||
|
if vf.contains('vweb.v') {
|
||||||
|
is_vweb_found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_vweb_found {
|
||||||
|
if !os.args.any(it.starts_with('--only-watch')) {
|
||||||
|
// vweb is often used with SQLite .db or .sqlite3 files right next to the executable/source,
|
||||||
|
// that are updated by the vweb app, causing a restart of the app, which in turn causes the
|
||||||
|
// browser to reload the current page, that probably triggered the update in the first place.
|
||||||
|
// Note that the problem is not specific to SQLite, any database that stores its files in the
|
||||||
|
// current (project) folder, will also cause this.
|
||||||
|
println('`v watch` detected that you are compiling a vweb project.')
|
||||||
|
println(' Because of that, the `--only-watch=*.v,*.html,*.css,*.js` flag was also implied.')
|
||||||
|
println(' In result, `v watch` will ignore changes to other files.')
|
||||||
|
println(' Add your own --only-watch filter, if you wish to override that choice.')
|
||||||
|
println('')
|
||||||
|
context.only_watch = '*.v,*.html,*.css,*.js'.split_any(',')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
context.affected_paths = apaths.keys()
|
context.affected_paths = apaths.keys()
|
||||||
// context.elog('vfiles paths to be scanned: $context.affected_paths')
|
// context.elog('vfiles paths to be scanned: $context.affected_paths')
|
||||||
}
|
}
|
||||||
// scan all files in the found folders
|
// scan all files in the found folders:
|
||||||
mut newstats := []VFileStat{}
|
mut newstats := []VFileStat{}
|
||||||
for path in context.affected_paths {
|
for path in context.affected_paths {
|
||||||
mut files := os.ls(path) or { []string{} }
|
mut files := os.ls(path) or { []string{} }
|
||||||
for pf in files {
|
next_file: for pf in files {
|
||||||
|
pf_path := os.join_path_single(path, pf)
|
||||||
|
if context.only_watch.len > 0 {
|
||||||
|
// in the whitelist mode, first only allow files, which match at least one of the patterns in context.only_watch:
|
||||||
|
mut matched_pattern_idx := -1
|
||||||
|
for ow_pattern_idx, ow_pattern in context.only_watch {
|
||||||
|
if pf_path.match_glob(ow_pattern) {
|
||||||
|
matched_pattern_idx = ow_pattern_idx
|
||||||
|
context.elog('> ${@METHOD} matched --only-watch pattern: ${ow_pattern}, for file: ${pf_path}')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matched_pattern_idx == -1 {
|
||||||
|
context.elog('> ${@METHOD} --only-watch ignored file: ${pf_path}')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// by default allow everything, except very specific extensions (backup files, executables etc):
|
||||||
pf_ext := os.file_ext(pf).to_lower()
|
pf_ext := os.file_ext(pf).to_lower()
|
||||||
if pf_ext in ['', '.bak', '.exe', '.dll', '.so', '.def'] {
|
if context.is_ext_ignored(pf, pf_ext) {
|
||||||
continue
|
context.elog('> ${@METHOD} ignored extension: ${pf_ext}, for file: ${pf_path}')
|
||||||
}
|
|
||||||
if pf_ext in context.ignore_exts {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if pf.starts_with('.#') {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if pf.ends_with('~') {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
f := os.join_path(path, pf)
|
f := os.join_path(path, pf)
|
||||||
@ -323,19 +380,21 @@ fn main() {
|
|||||||
// Options after `run` should be ignored, since they are intended for the user program, not for the watcher.
|
// Options after `run` should be ignored, since they are intended for the user program, not for the watcher.
|
||||||
// For example, `v watch run x.v -a -b -k', should pass all of -a -b -k to the compiled and run program.
|
// For example, `v watch run x.v -a -b -k', should pass all of -a -b -k to the compiled and run program.
|
||||||
only_watch_options, has_run := all_before('run', all_args_after_watch_cmd)
|
only_watch_options, has_run := all_before('run', all_args_after_watch_cmd)
|
||||||
|
|
||||||
mut fp := flag.new_flag_parser(only_watch_options)
|
mut fp := flag.new_flag_parser(only_watch_options)
|
||||||
fp.application('v watch')
|
fp.application('v watch')
|
||||||
fp.version('0.0.2')
|
fp.version('0.0.2')
|
||||||
fp.description('Collect all .v files needed for a compilation, then re-run the compilation when any of the source changes.')
|
fp.description('Collect all .v files needed for a compilation, then re-run the compilation when any of the source changes.')
|
||||||
fp.arguments_description('[--silent] [--clear] [--ignore .db] [--add /path/to/a/file.v] [run] program.v')
|
fp.arguments_description('[--silent] [--clear] [--ignore .db] [--add /path/to/a/file.v] [run] program.v')
|
||||||
fp.allow_unknown_args()
|
fp.allow_unknown_args()
|
||||||
fp.limit_free_args_to_at_least(1)!
|
|
||||||
context.is_worker = fp.bool('vwatchworker', 0, false, 'Internal flag. Used to distinguish vwatch manager and worker processes.')
|
context.is_worker = fp.bool('vwatchworker', 0, false, 'Internal flag. Used to distinguish vwatch manager and worker processes.')
|
||||||
context.silent = fp.bool('silent', `s`, false, 'Be more silent; do not print the watch timestamp before each re-run.')
|
context.silent = fp.bool('silent', `s`, false, 'Be more silent; do not print the watch timestamp before each re-run.')
|
||||||
context.clear_terminal = fp.bool('clear', `c`, false, 'Clears the terminal before each re-run.')
|
context.clear_terminal = fp.bool('clear', `c`, false, 'Clears the terminal before each re-run.')
|
||||||
context.keep_running = fp.bool('keep', `k`, false, 'Keep the program running. Restart it automatically, if it exits by itself. Useful for gg/ui apps.')
|
context.keep_running = fp.bool('keep', `k`, false, 'Keep the program running. Restart it automatically, if it exits by itself. Useful for gg/ui apps.')
|
||||||
context.add_files = fp.string('add', `a`, '', 'Add more files to be watched. Useful with `v watch -add=/tmp/feature.v run cmd/v /tmp/feature.v`, if you change *both* the compiler, and the feature.v file.').split(',')
|
context.add_files = fp.string('add', `a`, '', 'Add more files to be watched. Useful with `v watch --add=/tmp/feature.v run cmd/v /tmp/feature.v`, if you change *both* the compiler, and the feature.v file.').split_any(',')
|
||||||
context.ignore_exts = fp.string('ignore', `i`, '', 'Ignore files having these extensions. Useful with `v watch -ignore=.db run server.v`, if your server writes to an sqlite.db file in the same folder.').split(',')
|
context.ignore_exts = fp.string('ignore', `i`, '', 'Ignore files having these extensions. Useful with `v watch --ignore=.db run server.v`, if your server writes to an sqlite.db file in the same folder.').split_any(',')
|
||||||
|
context.only_watch = fp.string('only-watch', `o`, '', 'Watch only files matching these globe patterns. Example for a markdown renderer project: `v watch --only-watch=*.v,*.md run .`').split_any(',')
|
||||||
show_help := fp.bool('help', `h`, false, 'Show this help screen.')
|
show_help := fp.bool('help', `h`, false, 'Show this help screen.')
|
||||||
context.cmd_before_run = fp.string('before', 0, '', 'A command to execute *before* each re-run.')
|
context.cmd_before_run = fp.string('before', 0, '', 'A command to execute *before* each re-run.')
|
||||||
context.cmd_after_run = fp.string('after', 0, '', 'A command to execute *after* each re-run.')
|
context.cmd_after_run = fp.string('after', 0, '', 'A command to execute *after* each re-run.')
|
||||||
@ -351,6 +410,7 @@ fn main() {
|
|||||||
context.opts << all_args_before_watch_cmd
|
context.opts << all_args_before_watch_cmd
|
||||||
context.opts << remaining_options
|
context.opts << remaining_options
|
||||||
if has_run {
|
if has_run {
|
||||||
|
context.opts << 'run'
|
||||||
context.opts << all_after('run', all_args_after_watch_cmd)
|
context.opts << all_after('run', all_args_after_watch_cmd)
|
||||||
}
|
}
|
||||||
context.elog('>>> context.pid: ${context.pid}')
|
context.elog('>>> context.pid: ${context.pid}')
|
||||||
@ -360,6 +420,7 @@ fn main() {
|
|||||||
context.elog('>>> context.clear_terminal: ${context.clear_terminal}')
|
context.elog('>>> context.clear_terminal: ${context.clear_terminal}')
|
||||||
context.elog('>>> context.add_files: ${context.add_files}')
|
context.elog('>>> context.add_files: ${context.add_files}')
|
||||||
context.elog('>>> context.ignore_exts: ${context.ignore_exts}')
|
context.elog('>>> context.ignore_exts: ${context.ignore_exts}')
|
||||||
|
context.elog('>>> context.only_watch: ${context.only_watch}')
|
||||||
if context.is_worker {
|
if context.is_worker {
|
||||||
context.worker_main()
|
context.worker_main()
|
||||||
} else {
|
} else {
|
||||||
@ -407,7 +468,7 @@ fn all_before(needle string, all []string) ([]string, bool) {
|
|||||||
if needle_pos == -1 {
|
if needle_pos == -1 {
|
||||||
return all, false
|
return all, false
|
||||||
}
|
}
|
||||||
return all#[..needle_pos + 1], true
|
return all#[..needle_pos], true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn all_after(needle string, all []string) []string {
|
fn all_after(needle string, all []string) []string {
|
||||||
|
14
examples/vwatch/cli_clock/main.v
Normal file
14
examples/vwatch/cli_clock/main.v
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import time
|
||||||
|
// This example demonstrates how to use `v watch` for simple CLI apps.
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println('Run with: `v watch run examples/vwatch/cli_clock`,')
|
||||||
|
println('then modify timer.v in your editor.')
|
||||||
|
println('The application will be restarted,')
|
||||||
|
println('as soon as you save your changes.')
|
||||||
|
println('')
|
||||||
|
for {
|
||||||
|
println('The time is now: ${time.now()}')
|
||||||
|
time.sleep(1000 * time.millisecond)
|
||||||
|
}
|
||||||
|
}
|
4
examples/vwatch/web_server/.gitignore
vendored
Normal file
4
examples/vwatch/web_server/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
|
!main.v
|
||||||
|
|
65
examples/vwatch/web_server/main.v
Normal file
65
examples/vwatch/web_server/main.v
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
module main
|
||||||
|
|
||||||
|
// This example demonstrates how to use `v watch` for vweb apps, that use sqlite .db files.
|
||||||
|
//
|
||||||
|
// Note 1: while developing services, it is useful to also add the `--keep` option of `v watch`,
|
||||||
|
// which will restart the app right away, even when it exits on its own.
|
||||||
|
//
|
||||||
|
// Note 2: vweb supports a special live reload mode, where it will make the browser to check for server
|
||||||
|
// restarts, and it will trigger a refresh of the current page, right after that is detected.
|
||||||
|
//
|
||||||
|
// The above means, that to get the most optimal prototyping experience for vweb apps, use:
|
||||||
|
// `v -d vweb_livereload watch --only-watch=*.v,*.html,*.css,*.js --keep run .`
|
||||||
|
import os
|
||||||
|
import vweb
|
||||||
|
import db.sqlite
|
||||||
|
|
||||||
|
fn mydb() !sqlite.DB {
|
||||||
|
return sqlite.connect(os.resource_abs_path('app.db'))
|
||||||
|
}
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
mut:
|
||||||
|
counter int
|
||||||
|
}
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
vweb.Context
|
||||||
|
mut:
|
||||||
|
state shared State
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut app App) index() vweb.Result {
|
||||||
|
mut c := 0
|
||||||
|
lock app.state {
|
||||||
|
app.state.counter++
|
||||||
|
c = app.state.counter
|
||||||
|
}
|
||||||
|
visits := app.update_db() or { 0 }
|
||||||
|
return app.html('<!doctype html><html><body>
|
||||||
|
<br/>Current request counter, after the server restart: ${c}.
|
||||||
|
<br/>Total stored visits: ${visits}
|
||||||
|
</body></html>')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut app App) update_db() !int {
|
||||||
|
mut db := mydb()!
|
||||||
|
db.exec('INSERT INTO visits (created_at) VALUES ("")')
|
||||||
|
visits := db.q_int('SELECT count(*) FROM visits')
|
||||||
|
db.close()!
|
||||||
|
return visits
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println('App demonstrating the use of `vweb` & `db.sqlite` together.')
|
||||||
|
println('For best prototyping experience, run with:')
|
||||||
|
println('`v -d vweb_livereload watch --keep run examples/vwatch/web_server/`')
|
||||||
|
println('')
|
||||||
|
mut db := mydb()!
|
||||||
|
db.exec('CREATE TABLE visits (id integer primary key AUTOINCREMENT, created_at timestamp default current_timestamp);')
|
||||||
|
db.exec('CREATE TRIGGER INSERT_visits AFTER INSERT ON visits BEGIN
|
||||||
|
UPDATE visits SET created_at = datetime("now", "localtime") WHERE rowid = new.rowid ;
|
||||||
|
END')
|
||||||
|
db.close()!
|
||||||
|
vweb.run(&App{}, 19123)
|
||||||
|
}
|
@ -13,16 +13,22 @@ Options:
|
|||||||
|
|
||||||
-c, --clear Clears the terminal before each re-run.
|
-c, --clear Clears the terminal before each re-run.
|
||||||
|
|
||||||
-a, --add <string> Add more files to be watched.
|
-a, --add <string> Add more files to be watched (separated by ,).
|
||||||
Useful with `v watch -add=feature.v run cmd/v feature.v`,
|
Useful with `v watch -add=feature.v run cmd/v feature.v`,
|
||||||
when you want to change *both* the V compiler,
|
when you want to change *both* the V compiler,
|
||||||
and the `feature.v` file.
|
and the `feature.v` file.
|
||||||
|
|
||||||
-i, --ignore <string> Ignore files having these extensions.
|
-i, --ignore <string> Ignore files having these extensions (separated by ,)
|
||||||
Useful with `v watch -ignore=.db run vwebserver.v`,
|
Useful with `v watch -ignore=.db run vwebserver.v`,
|
||||||
if your `vwebserver` writes to an sqlite.db file in
|
if your `vwebserver` writes to an sqlite.db file in
|
||||||
the same folder.
|
the same folder.
|
||||||
|
|
||||||
|
-o, --only-watch <string>
|
||||||
|
Watch only files matching these glob patterns
|
||||||
|
The patterns are separated by `,`. Example for
|
||||||
|
a markdown renderer project:
|
||||||
|
v watch --only-watch=*.v,*.md run .
|
||||||
|
|
||||||
--before <string> A command to execute *before* each re-run.
|
--before <string> A command to execute *before* each re-run.
|
||||||
Example: --before 'v wipe-cache'
|
Example: --before 'v wipe-cache'
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user