├── .gitignore ├── nwatchdog.nimble ├── LICENSE.md ├── nwatch.json.example ├── nwatchdog.nim ├── nwatch.nim └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache/ 2 | nimblecache/ 3 | htmldocs/ 4 | .*~ 5 | -------------------------------------------------------------------------------- /nwatchdog.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.2.15" 4 | author = "Amru Rosyada" 5 | description = "Simple watchdog (watch file changes modified, deleted, created) in nim lang." 6 | license = "BSD" 7 | installExt = @["nim"] 8 | bin = @["nwatch"] 9 | 10 | # Dependencies 11 | 12 | requires "nim >= 2.0.0" 13 | requires "regex >= 0.21.0" 14 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2020 Amru Rosyada 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /nwatch.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "define": { 3 | "srcDir": "src", 4 | "binDir": "bin", 5 | "main": "app.nim", 6 | "mainExec": "app", 7 | "nimFilePattern": "[\\w\\W]*\\.[(nim)]+$" 8 | }, 9 | "interval": 1000, 10 | "instanceId": "app-example-1", 11 | "task": { 12 | "buildAndRun": { 13 | "path": "", 14 | "pattern": "", 15 | "onCreated": [], 16 | "onModified": [ 17 | "", 18 | "" 19 | ], 20 | "onDeleted": [] 21 | }, 22 | "build": { 23 | "path": "", 24 | "pattern": "", 25 | "command": { 26 | "default": [ 27 | { 28 | "hostOS": ["all"], 29 | "list": [ 30 | {"exec": "nim --outDir: c ::", "ignoreFail": false} 31 | ] 32 | } 33 | ] 34 | }, 35 | "onCreated": [], 36 | "onModified": [""], 37 | "onDeleted": [] 38 | }, 39 | "run": { 40 | "path": "", 41 | "pattern": "", 42 | "command": { 43 | "default": [ 44 | { 45 | "hostOS": ["all"], 46 | "list": [ 47 | {"exec": "::"} 48 | ] 49 | } 50 | ] 51 | }, 52 | "onCreated": [], 53 | "onModified": [""], 54 | "onDeleted": [] 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /nwatchdog.nim: -------------------------------------------------------------------------------- 1 | import 2 | os, 3 | asyncdispatch, 4 | times, 5 | regex, 6 | strutils, 7 | sequtils, 8 | streams, 9 | base64, 10 | random, 11 | strformat 12 | export asyncdispatch 13 | 14 | 15 | type 16 | NWatchEvent* = enum 17 | Created, 18 | Modified, 19 | Deleted 20 | 21 | NWatchDogParam*[T] = tuple[ 22 | id: string, 23 | dir: string, 24 | pattern: string, 25 | onEvent: proc ( 26 | file: string, 27 | nwEvent: NWatchEvent, 28 | param: T = nil) {.gcsafe async.}, 29 | param: T] 30 | 31 | NWatchDog*[T] = ref object of RootObj 32 | ## NWatchDog object 33 | ## NWatchDog(interval: 5000) 34 | ## will check each 5000 milisecond or 5 second 35 | interval*: int 36 | toWatch*: seq[NWatchDogParam[T]] 37 | ## workdir is directory for saving the temporary snapshot file structure 38 | ## this usefull when working with multiple nwatchdog instances 39 | ## this is optional 40 | workdir*: string 41 | ## instanceid is instanceid for the tamporary snapshot name to make sure not conflict with other nwatchdog instance id 42 | ## this is optional 43 | instanceid*: string 44 | 45 | 46 | var watchFile {.threadvar.}: string 47 | watchFile = getCurrentDir().joinPath(".watch") 48 | var watchCmpFile {.threadvar.}: string 49 | watchCmpFile = getCurrentDir().joinPath(".watch.cmp") 50 | 51 | 52 | proc add*[T]( 53 | self: NWatchDog, 54 | dir: string, 55 | pattern: string, 56 | onEvent: proc ( 57 | file: string, 58 | nwEvent: NWatchEvent, 59 | param: T = nil) {.gcsafe async.}, 60 | param: T = nil) = 61 | ## register new directory to watch when file changed 62 | let id = now().utc().format("YYYY-MM-dd HH:mm:ss:fffffffff") 63 | let dirToWatch = dir.expandTilde 64 | self.toWatch.add(( 65 | (&"{dirToWatch}-{id}").encode, 66 | dirToWatch, 67 | pattern, 68 | onEvent, 69 | param)) 70 | 71 | 72 | proc delete*[T](self: NWatchDog, dir: string) = 73 | ## delete directory from watch 74 | self.toWatch = self.toWatch.filter( 75 | proc (x: NWatchDogParam[T]): bool = 76 | x.dir != dir) 77 | 78 | 79 | proc watchFormat( 80 | file, 81 | createTime, 82 | modifTime, 83 | accessTime, 84 | id: string): string = 85 | 86 | ## set watch format file 87 | ## used by internal watch system 88 | return file & "||" & createTime & "||" & modifTime & "||" & accessTime & "||" & id 89 | 90 | 91 | proc createSnapshot( 92 | self: NWatchDog, 93 | Snapshot: string) = 94 | 95 | ## create snapshot of file in the directory 96 | ## used by internal watch system 97 | let fr = newFileStream(Snapshot, fmWrite) 98 | for (id, dir, pattern, evt, p) in self.toWatch: 99 | for f in dir.walkDirRec: 100 | try: 101 | if f.match(re2 pattern): 102 | fr.writeLine( 103 | f.watchFormat( 104 | $f.getCreationTime().toUnix, 105 | $f.getLastModificationTime().toUnix, 106 | $f.getLastAccessTime().toUnix, 107 | id 108 | )) 109 | except Exception as ex: 110 | echo ex.msg 111 | fr.close 112 | 113 | 114 | proc executeEvent[T]( 115 | self: NWatchDog[T], 116 | event: tuple[ 117 | file: string, 118 | event: NWatchEvent, 119 | id: string]) {.gcsafe async.} = 120 | 121 | var watchEvent: NWatchDogParam[T] 122 | for evt in self.toWatch: 123 | if evt.id == event.id: 124 | watchEvent = evt 125 | break 126 | 127 | await watchEvent.onEvent(event.file, event.event, watchEvent.param) 128 | 129 | 130 | proc watch*(self: NWatchDog) {.gcsafe async.} = 131 | ## watch with NWatchDog instance configuration 132 | 133 | if self.workdir != "" and not self.workdir.dirExists: 134 | ## check workdir if overriden and not exist 135 | self.workdir.createDir 136 | 137 | ## override watch logger location if workdir exists 138 | if self.workdir.dirExists: 139 | watchFile = self.workdir.joinPath(".watch") 140 | watchCmpFile = self.workdir.joinPath(".watch.cmp") 141 | 142 | ## override watch logger name if instanceid set 143 | if self.instanceid != "": 144 | watchFile = watchFile & "." & self.instanceid 145 | watchCmpFile = watchCmpFile & "." & self.instanceid 146 | 147 | ## start watch the registered directory 148 | if self.interval == 0: 149 | self.interval = 5000 150 | 151 | var rnd = initRand(10) 152 | self.createSnapshot(watchFile) 153 | 154 | while true: 155 | self.createSnapshot(watchCmpFile) 156 | let snap = newFileStream(watchFile, fmRead) 157 | let snapCmp = newFileStream(watchCmpFile, fmRead) 158 | 159 | var line = "" 160 | var lineCmp = "" 161 | var doEvent = false 162 | 163 | let snapContent = snap.readAll() 164 | let snapCmpContent = snapCmp.readAll() 165 | 166 | snap.setPosition(0) 167 | snapCmp.setPosition(0) 168 | 169 | # check new and modified file 170 | while true: 171 | let snapReadStatus = snap.readLine(line) 172 | let snapCmpReadStatus = snapCmp.readLine(lineCmp) 173 | 174 | if not snapReadStatus and not snapCmpReadStatus: 175 | break 176 | 177 | if snapCmpReadStatus: 178 | var isNewFile = true 179 | let snapCmpInfo = lineCmp.split("||") 180 | for mdata in snapContent.findAll(re2 &"({snapCmpInfo[0]}\\|\\|[\\w\\W]+?)+[\n]+"): 181 | let snapInfo = snapContent[mdata.group(0)].split("||") 182 | # file modified 183 | if snapInfo[0] == snapCmpInfo[0]: 184 | isNewFile = false 185 | if snapInfo[2] != snapCmpInfo[2]: 186 | doEvent = true 187 | await self.executeEvent((snapCmpInfo[0], Modified, snapCmpInfo[4])) 188 | if isNewFile: 189 | doEvent = true 190 | await self.executeEvent((snapCmpInfo[0], Created, snapCmpInfo[4])) 191 | 192 | if snapReadStatus: 193 | let snapInfo = line.split("||") 194 | if not snapCmpContent.contains(re2 &"({snapInfo[0]}\\|\\|[\\w\\W]+?)+[\n]+"): 195 | doEvent = true 196 | await self.executeEvent((snapInfo[0], Deleted, snapInfo[4])) 197 | 198 | 199 | await rnd.rand(0..10).sleepAsync 200 | 201 | snap.close() 202 | snapCmp.close() 203 | 204 | if doEvent: 205 | moveFile(watchCmpFile, watchFile) 206 | 207 | await self.interval.sleepAsync 208 | 209 | -------------------------------------------------------------------------------- /nwatch.nim: -------------------------------------------------------------------------------- 1 | import 2 | nwatchdog, 3 | regex 4 | 5 | import 6 | std/[ 7 | json, 8 | paths, 9 | strutils, 10 | parseopt, 11 | dirs, 12 | files, 13 | strformat, 14 | appdirs, 15 | dirs, 16 | sugar 17 | ] 18 | from std/os import getAppDir 19 | from std/osproc import execCmd 20 | 21 | type 22 | NWatch*[T: JsonNode] = ref object of RootObj 23 | nWatchDog: NWatchDog[T] 24 | nWatchConfig: T 25 | 26 | 27 | proc findValue*( 28 | jnode: JsonNode, 29 | key: string 30 | ): JsonNode = ## \ 31 | ## find value of given key in the jnode 32 | 33 | let attrToken = key.find(".") 34 | if attrToken >= 0: 35 | let attr = key.substr(0, attrToken - 1) 36 | let jnode = jnode{attr} 37 | if not jnode.isNil: 38 | result = jnode.findValue(key.substr(attrToken + 1, key.high)) 39 | 40 | else: 41 | result = jnode{key} 42 | 43 | 44 | proc parseConfig*( 45 | nWatchConfig: JsonNode 46 | ): JsonNode = ## \ 47 | ## normalize json config 48 | ## replace all variable string 49 | ## with correct value 50 | 51 | var matches = newSeq[string]() 52 | var nWatchConfigStr = $nWatchConfig 53 | var nWatchConfigStrResult = $nWatchConfig 54 | var varList = findAll(nWatchConfigStr, re2"(<[\d\w\\._\\-]+>)") 55 | for m in varList: 56 | var valueToReplace = nWatchConfigStr[m.boundaries] 57 | let valueReplacement = 58 | nWatchConfig. 59 | findValue(valueToReplace.replace("<", "").replace(">", "")) 60 | 61 | if valueReplacement.isNil: 62 | raise newException( 63 | KeyError, 64 | @[ 65 | "\nFail when parse nwatch json file:\n", 66 | valueToReplace.replace("<", "").replace(">", ""), 67 | " key not found!.\n", 68 | ].join("") 69 | ) 70 | 71 | let isTaskList: bool = valueToReplace.startsWith(")").len != 0: 99 | result = result.parseConfig 100 | 101 | 102 | proc newNWatch*( 103 | path: Path, 104 | data: JsonNode = nil 105 | ): NWatch[JsonNode] = ## \ 106 | ## parse nwatch.json file 107 | 108 | try: 109 | var nWatchJson = parseFile($path) 110 | if not data.isNil: 111 | ## apply data from command line 112 | ## replace key, value from data 113 | ## the data should simple 114 | ## only string and numeric value allowed 115 | var nWatchConfigStr = $nWatchJson 116 | for k, v in data: 117 | var val = ($v).strip 118 | if v.kind == JString: val = val.substr(1, val.high - 1) 119 | nWatchConfigStr = nWatchConfigStr.replace(&"<{k}>", val) 120 | 121 | nWatchJson = nWatchConfigStr.parseJson 122 | 123 | nWatchJson = nWatchJson.parseConfig 124 | 125 | let define = nWatchJson{"define"} 126 | let interval = nWatchJson{"interval"} 127 | let instanceId = nWatchJson{"instanceId"} 128 | let task = nWatchJson{"task"} 129 | 130 | if define.isNil or interval.isNil or instanceId.isNil or task.isNil: 131 | let err = @[ 132 | "missing one of json attribute:", 133 | "\tdefine", 134 | "\tinterval", 135 | "\tinstanceId", 136 | "\ttask" 137 | ].join("\n") 138 | 139 | raise newException(ValueError, err) 140 | 141 | ## return NWatchDog[JsonNode] object 142 | ## create working dir 143 | let workDir = getCacheDir()/"nwatchdog".Path 144 | workDir.createDir 145 | 146 | result = NWatch[JsonNode]( 147 | nWatchDog: NWatchDog[JsonNode]( 148 | interval: interval.getInt, 149 | instanceid: instanceId.getStr, 150 | workdir: $workDir 151 | ), 152 | nWatchConfig: nWatchJson 153 | ) 154 | 155 | except Exception as e: 156 | echo e.msg 157 | 158 | 159 | proc help() = ## \ 160 | ## print help 161 | 162 | echo "" 163 | echo "See https://github.com/zendbit/nim_nwatchdog for more informations" 164 | echo "" 165 | echo "Usage:" 166 | echo "\tnwatch taskname [-c:nwatch.json | --config:nwatch.json]" 167 | echo "\tnwatch -t:taskname [-c:nwatch.json | --config:nwatch.json]" 168 | echo "\tnwatch -task:taskname [-c:nwatch.json | --config:nwatch.json]" 169 | echo "" 170 | echo "Direct call command without watch, pass --runTask:" 171 | echo "\tnwatch . --runTask" 172 | echo "\tnwatch build.default --runTask" 173 | echo "" 174 | echo "Use > for tasks chaining:" 175 | echo "\tnwatch \"build.default>run.default\" --runTask" 176 | echo "\tabove command will execute task.build.command.default then task.run.command.default" 177 | echo "" 178 | echo "Pass data, for early replacement on config:" 179 | echo "\t" & """nwatch taskname --data:"{\"srcDir\": \"src\"}"""" 180 | echo "\tabove command will replace with src before config being parsed" 181 | echo "" 182 | 183 | 184 | proc runTask( 185 | tasks: JsonNode, 186 | replace: seq[tuple[key: string, val: string]] = @[] 187 | ) {.gcsafe async.} = ## \ 188 | ## execute task command 189 | 190 | var taskList: seq[JsonNode] 191 | for task in tasks: 192 | if task.kind == JArray: 193 | taskList &= task.to(seq[JsonNode]) 194 | else: 195 | taskList.add(task) 196 | 197 | for task in taskList: 198 | let host = task{"hostOS"} 199 | let listCmd = task{"list"} 200 | if host.isNil or 201 | listCmd.isNil or 202 | ( 203 | hostOS notin host.to(seq[string]) and 204 | "all" notin host.to(seq[string]) 205 | ): 206 | continue 207 | 208 | var isShouldExit = false 209 | for cmd in listCmd: 210 | var cmdStr = cmd{"exec"}.getStr 211 | for (key, val) in replace: 212 | cmdStr = cmdStr.replace(key, val) 213 | 214 | echo "## Running Task" 215 | echo &"=> {cmdStr}" 216 | echo "" 217 | if cmdStr.execCmd != 0 and 218 | not cmd{"ignoreFail"}.isNil and 219 | not cmd{"ignoreFail"}.getBool: 220 | echo &"error: fail to execute\n\t{cmdStr}" 221 | isShouldExit = not isShouldExit 222 | break 223 | 224 | if isShouldExit: break 225 | 226 | 227 | proc showAvailableTasks*(self: NWatch) {.gcsafe.} = ## \ 228 | ## show available task 229 | 230 | let tasks = self.nWatchConfig{"task"} 231 | if tasks.isNil: 232 | echo "No task found!." 233 | return 234 | 235 | echo "## Available Tasks:" 236 | echo "" 237 | for k, v in tasks: 238 | let command: seq[string] = 239 | if v{"command"}.isNil: @[] 240 | else: 241 | collect(newSeq): 242 | for c, _ in v{"command"}: c 243 | echo &"=> {k} [{command.join(\", \")}]" 244 | 245 | 246 | proc showTaskInfo*( 247 | self: NWatch, 248 | task: string 249 | ) {.gcsafe.} = ## \ 250 | ## show available task 251 | 252 | var taskParts = task.split(".") 253 | if taskParts.len == 2: taskParts.insert("command", 1) 254 | 255 | let tasks = self.nWatchConfig{"task"}.findValue(taskParts.join(".")) 256 | if tasks.isNil: 257 | echo "No task found!." 258 | return 259 | 260 | echo &"## Task {task}:" 261 | echo "" 262 | echo tasks.pretty 263 | 264 | 265 | proc watchTask*( 266 | self: NWatch, 267 | task: string, 268 | isRunTask: bool = false 269 | ) {.gcsafe async.} = ## \ 270 | ## parse argument and watch task by argument 271 | ## find task from nwatch.json 272 | ## if just want to execute task without watch 273 | ## set runTask to true 274 | 275 | var taskToWatch = self.nWatchConfig{"task"}{task} 276 | if isRunTask: 277 | taskToWatch = self.nWatchConfig.findValue(task) 278 | 279 | if taskToWatch.isNil: 280 | echo &"error: {task} task not found!." 281 | return 282 | 283 | if isRunTask: 284 | await taskToWatch.runTask 285 | return 286 | 287 | echo "## Start watch event" 288 | echo &"=> {task}" 289 | echo "" 290 | self.nWatchDog.add( 291 | taskToWatch["path"].getStr, 292 | taskToWatch["pattern"].getStr, 293 | (proc ( 294 | file: string, 295 | evt: NWatchEvent, 296 | task: JsonNode) {.gcsafe async.} = 297 | let (dir, name, ext) = file.Path.splitFile 298 | let replace = @[ 299 | ("", file), 300 | ("", $name), 301 | ("", $dir), 302 | ("", $ext) 303 | ] 304 | 305 | case evt 306 | of Created: 307 | await task{"onCreated"}.runTask(replace) 308 | of Modified: 309 | await task{"onModified"}.runTask(replace) 310 | of Deleted: 311 | await task{"onDeleted"}.runTask(replace) 312 | ), 313 | taskToWatch 314 | ) 315 | 316 | await self.nWatchDog.watch 317 | 318 | 319 | when isMainModule: 320 | var 321 | nWatchConfig: string = $(getCurrentDir()/"nwatch.json".Path) 322 | nWatchTask: string 323 | isRunTask: bool 324 | data: JsonNode 325 | isShowTaskList: bool 326 | isShowTaskInfo: bool 327 | isShowHelp: bool 328 | ## parse command line 329 | for kind, key, val in getOpt(): 330 | case kind 331 | of cmdArgument: 332 | nWatchTask = key 333 | of cmdShortOption: 334 | case key 335 | of "t": 336 | nWatchTask = val 337 | of "c": 338 | nWatchConfig = val 339 | of "d": 340 | data = val.parseJson 341 | of "h": 342 | isShowHelp = true 343 | of cmdLongOption: 344 | case key 345 | of "task": 346 | nWatchTask = val 347 | of "config": 348 | nWatchConfig = val 349 | of "data": 350 | data = val.parseJson 351 | of "runTask": 352 | isRunTask = true 353 | of "taskList": 354 | isShowTaskList = true 355 | of "taskInfo": 356 | isShowTaskInfo = true 357 | of "help": 358 | isShowHelp = true 359 | of cmdEnd: discard 360 | 361 | if not nWatchConfig.Path.fileExists or isShowHelp: 362 | if not nWatchConfig.Path.fileExists: 363 | echo "error: nwatch.json not found!." 364 | help() 365 | quit(QuitFailure) 366 | 367 | let nWatch = newNwatch(nWatchConfig.Path, data) 368 | if isShowTaskList: 369 | nWatch.showAvailableTasks 370 | elif isShowTaskInfo: 371 | nWatch.showTaskInfo(nWatchTask) 372 | elif isRunTask: 373 | discard 374 | for task in nWatchTask.split(">"): 375 | let cmd = task.split(".") 376 | let taskToExec = &"task.{(cmd[0]).strip}.command.{(cmd[1]).strip}" 377 | waitFor nWatch.watchTask(taskToExec, isRunTask) 378 | else: 379 | waitFor nWatch.watchTask(nWatchTask, isRunTask) 380 | 381 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## nim.nwatchdog 2 | Simple watchdog (watch file change modified, deleted, created) in nim lang 3 | 4 | ### install 5 | ```shell 6 | nimble install nwatchdog@#head 7 | ``` 8 | ### usage 9 | ```nim 10 | ## import nwatchdog 11 | import nwatchdog 12 | 13 | ## create new watchdog 14 | ## let wd = NWatchDog[T]() 15 | ## generic T is parameter will pass to the event callback 16 | ## or just set to string if we don't really care about generic 17 | ## default interval is 5000 milisecond (5 second) 18 | let wd = NWatchDog[string](interval: 10000) 19 | 20 | ## to prevent conflict on multiple instance nwatchdog can beresolve using workdir and instanceid 21 | ## or one of them 22 | ## 23 | ## let wd1 = NWatchDog[string](interval: 10000, workdir:"/tmp", instanceid:"removejunk") 24 | ## let wd2 = NWatchDog[string](interval: 10000, workdir:"/tmp", instanceid:"removejunk2") 25 | ## 26 | 27 | ## add directory to watch, with file pattern to watch 28 | ## the pattern using pcre from stdlib 29 | ## example we want to listen directory and listen all .txt and .js 30 | ## 31 | ## event callback: 32 | ## proc (file: string, evt: NWatchEvent, param: string) 33 | ## param is string because we defined generic in string let wd = NWatchDog[string]() 34 | wd.add( 35 | "/home/zendbit/test/jscript", ## allow tilde here ~/test/jscript 36 | "[\\w\\W]*\\.[(txt)|(js)]+$", 37 | (proc (file: string, evt: NWatchEvent, param: string) {.gcsafe async.} = 38 | echo param 39 | case evt 40 | of Created: 41 | echo file & " " & $evt 42 | of Modified: 43 | echo file & " " & $evt 44 | of Deleted: 45 | echo file & " " & $evt), 46 | "this param will pass to the event callback watch js and txt") 47 | 48 | wd.add( 49 | "/home/zendbit/test/csscript", ## allow tilde here ~/test/csscript 50 | "[\\w\\W]*\\.[(css)]+$", 51 | (proc (file: string, evt: NWatchEvent, param: string) {.gcsafe async.} = 52 | echo param 53 | case evt 54 | of Created: 55 | echo file & " " & $evt 56 | of Modified: 57 | echo file & " " & $evt 58 | of Deleted: 59 | echo file & " " & $evt), 60 | "this param will pass to the event callback watch css") 61 | 62 | # watch the file changes 63 | waitFor wd.watch 64 | ``` 65 | 66 | ### using nwatch binary command 67 | NWatchDog now include nwatch command, the command will take some parameter 68 | ```bash 69 | nwatch -t:taskname -c:nwatch.json 70 | ``` 71 | 72 | - **-t** mean task to call and watch 73 | - **-c** mean nwatch configuration in json format 74 | 75 | if **-c** not given or nwatch json configuration not provided, nwatch will automatic find for **nwatch.json** in the same directory, if not found then will return error 76 | 77 | ```bash 78 | nwatch taskname 79 | ``` 80 | 81 | above command will call taskname configuration from current nwatch.json. Example nwatch json available here [https://github.com/zendbit/nim_nwatchdog/blob/master/nwatch.json.example](https://github.com/zendbit/nim_nwatchdog/blob/master/nwatch.json.example) 82 | 83 | 84 | ```json 85 | { 86 | "define": { 87 | "srcDir": "src", 88 | "binDir": "bin", 89 | "main": "app.nim", 90 | "mainExec": "app", 91 | "nimFilePattern": "[\\w\\W]*\\.[(nim)]+$" 92 | }, 93 | "interval": 1000, 94 | "instanceId": "app-example-1", 95 | "task": { 96 | "buildAndRun": { 97 | "path": "", 98 | "pattern": "", 99 | "onCreated": [], 100 | "onModified": [ 101 | "", 102 | "" 103 | ], 104 | "onDeleted": [] 105 | }, 106 | "build": { 107 | "path": "", 108 | "pattern": "", 109 | "command": { 110 | "default": [ 111 | { 112 | "hostOS": ["all"], 113 | "list": [ 114 | {"exec": "nim --outDir: c ::", "ignoreFail": false} 115 | ] 116 | } 117 | ] 118 | }, 119 | "onCreated": [], 120 | "onModified": [""], 121 | "onDeleted": [] 122 | }, 123 | "run": { 124 | "path": "", 125 | "pattern": "", 126 | "command": { 127 | "default": [ 128 | { 129 | "hostOS": ["all"], 130 | "list": [ 131 | {"exec": "::"} 132 | ] 133 | } 134 | ] 135 | }, 136 | "onCreated": [], 137 | "onModified": [""], 138 | "onDeleted": [] 139 | } 140 | } 141 | } 142 | ``` 143 | 144 | let start to try using example, we need to create some folder 145 | - create **testapp** dir 146 | - inside testapp dir create **src** dir for source directory 147 | - inside testapp dir create **bin** dir for binary executable 148 | - then create example app inside src dir, in this case we create **src/testapp.nim** 149 | 150 | or you can create using nimble command, for example create directory **testapp**, then inside **testapp** dir execute nimble init command. For this example we can select binary app template on nimble init. 151 | ``` 152 | mkdir testapp 153 | cd testapp 154 | nimble init 155 | ``` 156 | 157 | in the nimble not contains bin directory so we need to create it manually. The directory structure look like this 158 | 159 | 160 | 161 | before we start, we need to do some changes on nwatch.json define section, change **main** to **testapp.nim** and **mainExec** to **testapp** and **instanceId** match with project name for this example is **testapp** 162 | 163 | ```json 164 | "define": { 165 | "srcDir": "src", 166 | "binDir": "bin", 167 | "main": "testapp.nim", 168 | "mainExec": "testapp", 169 | "nimFilePattern": "[\\w\\W]*\\.[(nim)]+$" 170 | } 171 | ``` 172 | 173 | actually, inside define object is not mandatory, it's just like define some constants that we can reuse on the other section. 174 | 175 | if you see tag like this 176 | 177 | ``` 178 | 179 | ``` 180 | 181 | above tag will replaced with **define["srcDir"]** value 182 | 183 | now we have nwatch.json look like this 184 | 185 | ```json 186 | { 187 | "define": { 188 | "srcDir": "src", 189 | "binDir": "bin", 190 | "main": "testapp.nim", 191 | "mainExec": "testapp", 192 | "nimFilePattern": "[\\w\\W]*\\.[(nim)]+$" 193 | }, 194 | "interval": 1000, 195 | "instanceId": "testapp", 196 | "task": { 197 | "buildAndRun": { 198 | "path": "", 199 | "pattern": "", 200 | "onCreated": [], 201 | "onModified": [ 202 | "", 203 | "" 204 | ], 205 | "onDeleted": [] 206 | }, 207 | "build": { 208 | "path": "", 209 | "pattern": "", 210 | "command": { 211 | "default": [ 212 | { 213 | "hostOS": ["all"], 214 | "list": [ 215 | {"exec": "nim --outDir: c ::", "ignoreFail": false} 216 | ] 217 | } 218 | ] 219 | }, 220 | "onCreated": [], 221 | "onModified": [""], 222 | "onDeleted": [] 223 | }, 224 | "run": { 225 | "path": "", 226 | "pattern": "", 227 | "command": { 228 | "default": [ 229 | { 230 | "hostOS": ["all"], 231 | "list": [ 232 | {"exec": "::"} 233 | ] 234 | } 235 | ] 236 | }, 237 | "onCreated": [], 238 | "onModified": [""], 239 | "onDeleted": [] 240 | } 241 | } 242 | } 243 | ``` 244 | 245 | there are three task defines inside task section which is 246 | - buildAndRun 247 | - build 248 | - run 249 | 250 | ***The name is not mandatory, you can pick with others name*** 251 | 252 | now from the **testapp** folder you can call one of the task 253 | 254 | if you want to watch buildAndRun task you can do with this command 255 | 256 | ```bash 257 | nwatch buildAndRun 258 | ``` 259 | 260 | **WALAA....!!** now each time we do changes to **src/testapp.nim**, will automatically compile and run the app 261 | 262 | each task can have different pattern for listening for example we want to listen each time file .js modified and want to run some command task 263 | 264 | ```json 265 | "build": { 266 | "path": "", 267 | "pattern": "", 268 | "command": { 269 | "default": [ 270 | { 271 | "hostOS": ["all"], 272 | "list": [ 273 | {"exec": "nim --outDir: c ::", "ignoreFail": false} 274 | ] 275 | } 276 | ] 277 | }, 278 | "onCreated": [], 279 | "onModified": [""], 280 | "onDeleted": [] 281 | } 282 | ``` 283 | 284 | nwatch will automatically pick the command depend on the host os, in this case my host os is linux so the command will automatic call all command inside linux section 285 | 286 | let see this section, hostOS if ["all"] will executed cross platform, and we can filter it depend on the current hostOS 287 | ``` 288 | "hostOS": ["all"] 289 | ``` 290 | 291 | this will only executed if hostOS in linux, macosx and freebsd 292 | ``` 293 | "hostOS": ["linux", "macosx", "freebsd"] 294 | ``` 295 | 296 | and the task will executed seaquencetially depend on the order of the list of command identifier 297 | ``` 298 | "command": { 299 | "default": [ 300 | { 301 | "hostOS": ["all"], 302 | "list": [ 303 | {"exec": "nim --outDir: c ::", "ignoreFail": false} 304 | ] 305 | }, 306 | { 307 | "hostOS": ["linux", "macosx", "freebsd"], 308 | "list": [ 309 | {"exec": "rm -f ::", "ignoreFail": true} 310 | ] 311 | }, 312 | { 313 | "hostOS": ["windows"], 314 | "list": [ 315 | {"exec": "del ::", "ignoreFail": true} 316 | ] 317 | } 318 | ] 319 | } 320 | ``` 321 | above command will execute ["all"] -> ["linux", "macosx", "freebsd"] or ["windows"] depend on the hostOS 322 | 323 | ***note*** 324 | - we can use tag "" to refer to others attribute or object 325 | - we can use :: for directory separator, the nwatch will automatically replace that part into host os directory separator 326 | 327 | #### Show available task 328 | ```bash 329 | nwatch --taskList 330 | ``` 331 | 332 | #### show task information 333 | ```bash 334 | nwatch build --taskInfo 335 | ``` 336 | ```bash 337 | nwatch build.default --taskInfo 338 | ``` 339 | 340 | ### Direct call task without watch 341 | In some cases we want to call the command without watch file changes, we can pass **--runTask** to nwatch command 342 | - nwatch . --runTask 343 | ```bash 344 | nwatch build.default --runTask 345 | ``` 346 | above command will run task.build.command.default 347 | 348 | - call multiple task with > for task chaining 349 | ```bash 350 | nwatch "build.default>run.default" --runTask 351 | ``` 352 | above command will run task.build.command.default then task.run.command.default 353 | 354 | - pass data for early replacement before nwatch json config being parsed 355 | ```bash 356 | nwatch "build.default>run.default" --data:"{\"srcDir\": \"src\"}" --runTask 357 | ``` 358 | above command will pass data and will replace "" with "src" 359 | --------------------------------------------------------------------------------