@ariya/berkala
Advanced tools
Comparing version 1.1.9 to 1.2.0
166
index.js
#!/usr/bin/env node | ||
const fs = require('fs'); | ||
const os = require('os'); | ||
const child_process = require('child_process'); | ||
@@ -9,2 +11,4 @@ const manifest = require('./package.json'); | ||
const Bree = require('bree'); | ||
const which = require('which'); | ||
const say = require('say'); | ||
@@ -18,22 +22,23 @@ const CONFIG_FILENAME = 'berkala.yml'; | ||
boot: | ||
type: notify | ||
message: Berkala starts now | ||
steps: | ||
- notify: Berkala starts now | ||
# We need to stay hydrated | ||
hourly-ping: | ||
type: print | ||
stay-hydrated: | ||
interval: every 1 hour | ||
message: Drink some water! | ||
steps: | ||
- notify: Drink some water! # TODO: how much? | ||
- print: Reminder was sent | ||
lunch-reminder: | ||
type: notify | ||
lunch: | ||
interval: at 11:58am | ||
title: Important reminder | ||
message: It's lunch time very soon | ||
steps: | ||
- notify: It's lunch time very soon | ||
title: Important | ||
- say: Get ready for lunch | ||
weekend-exercise: | ||
type: notify | ||
cron: 0 9 * * 6 # every 9 morning on Saturday | ||
title: Stay healthy | ||
message: Time for some exercises! | ||
steps: | ||
- notify: Time for some exercises! | ||
title: Stay healthy | ||
`; | ||
@@ -78,29 +83,7 @@ | ||
/** | ||
* A simple job to print a message via console. | ||
*/ | ||
function printJob() { | ||
const { workerData } = require('worker_threads'); | ||
const { job } = workerData; | ||
const { message } = job; | ||
console.log(message); | ||
} | ||
/** | ||
* A job that sends desktop notification. | ||
*/ | ||
function notifyJob() { | ||
const os = require('os'); | ||
const child_process = require('child_process'); | ||
const { workerData } = require('worker_threads'); | ||
const { job } = workerData; | ||
const { message } = job; | ||
let { title } = job; | ||
if (!title) { | ||
title = 'Berkala'; | ||
} | ||
function platformNotify(title, message) { | ||
if (os.type() === 'Linux') { | ||
child_process.spawnSync('notify-send', ['-a', 'Berkala', title, message]); | ||
} else if (os.type() === 'Darwin') { | ||
const command = `display notification "${message}" with title "${title}"`; | ||
const command = `display notification "${message}" with title "Berkala" subtitle "${title}"`; | ||
child_process.spawnSync('osascript', ['-e', command]); | ||
@@ -143,10 +126,61 @@ } else if (os.type() === 'Windows_NT') { | ||
function platformSay(message) { | ||
if (os.type() === 'Linux') { | ||
const resolved = which.sync('festival', { nothrow: true }); | ||
if (resolved) { | ||
say.speak(message); | ||
} else { | ||
console.error('say: unable to locate Festival'); | ||
platformNotify(message); | ||
} | ||
} else if (os.type() === 'Windows_NT') { | ||
// TODO: check for Powershell first | ||
say.speak(message); | ||
} else if (os.type() === 'Darwin') { | ||
say.speak(message, 'Samantha'); // Siri's voice | ||
} else { | ||
// unsupported | ||
console.error('say: unsupport system', os.type()); | ||
platformNotify(message); | ||
} | ||
} | ||
function workerMessageHandler(workerData) { | ||
const workerMsg = workerData.message; | ||
const { duty } = workerMsg; | ||
if (duty === 'print') { | ||
const { message } = workerMsg; | ||
console.log(message); | ||
} else if (duty === 'notify') { | ||
const { title, message } = workerMsg; | ||
platformNotify(title, message); | ||
} else if (duty === 'say') { | ||
const { message } = workerMsg; | ||
platformSay(message); | ||
} | ||
} | ||
/** | ||
* A job that dumps the worker data, useful for debugging. | ||
* Run a task, execute the steps sequentially. | ||
*/ | ||
function debugJob() { | ||
const { workerData } = require('worker_threads'); | ||
function runTask() { | ||
const { workerData, parentPort } = require('worker_threads'); | ||
const { job } = workerData; | ||
const { steps } = job; | ||
console.log('debug job=', JSON.stringify(job)); | ||
steps.forEach((step) => { | ||
if (step.print) { | ||
parentPort.postMessage({ duty: 'print', message: step.print }); | ||
} else if (step.notify) { | ||
const title = step.title ? step.title : 'Berkala'; | ||
const message = step.notify.trim(); | ||
parentPort.postMessage({ duty: 'notify', title, message }); | ||
} else if (step.say) { | ||
parentPort.postMessage({ duty: 'say', message: step.say }); | ||
} else { | ||
console.error('Unknown step', step); | ||
} | ||
}); | ||
} | ||
@@ -158,33 +192,19 @@ | ||
function convert(name, task) { | ||
const { type, interval, cron } = task; | ||
const { interval, cron } = task; | ||
let { steps } = task; | ||
let path; | ||
let options = {}; | ||
const { message = null } = task; | ||
switch (type) { | ||
case 'debug': | ||
path = debugJob; | ||
options = task; | ||
break; | ||
case 'print': | ||
path = printJob; | ||
options = { message }; | ||
break; | ||
case 'notify': | ||
path = notifyJob; | ||
const { title = null } = task; | ||
options = { title, message }; | ||
break; | ||
default: | ||
break; | ||
if (!steps || !Array.isArray(steps) || steps.length === 0) { | ||
// Migrate legacy task format | ||
steps = []; | ||
const { type } = task; | ||
if (type === 'print') { | ||
const { message } = task; | ||
steps.push({ print: message }); | ||
} else if (type === 'notify') { | ||
const { message, title } = task; | ||
steps.push({ notify: message, title }); | ||
} | ||
} | ||
if (!path) { | ||
throw new Error('Unknown task type: ' + type); | ||
} | ||
const path = runTask; | ||
return { | ||
@@ -195,3 +215,3 @@ name, | ||
cron, | ||
...options | ||
steps | ||
}; | ||
@@ -204,3 +224,3 @@ } | ||
function setupTasks(config) { | ||
const { tasks } = config; | ||
const { tasks = [] } = config; | ||
const jobs = Object.keys(tasks).map((name) => { | ||
@@ -210,3 +230,3 @@ return convert(name, tasks[name]); | ||
const root = false; | ||
return new Bree({ root, jobs }); | ||
return new Bree({ root, jobs, workerMessageHandler }); | ||
} | ||
@@ -217,4 +237,4 @@ | ||
const config = getConfig(); | ||
const config = getConfig() || {}; | ||
const tasks = setupTasks(config); | ||
tasks.start(); |
{ | ||
"name": "@ariya/berkala", | ||
"version": "1.1.9", | ||
"version": "1.2.0", | ||
"description": "Run scheduled tasks", | ||
@@ -14,2 +14,3 @@ "main": "index.js", | ||
"format-code": "prettier --write \"**/*.js\"", | ||
"cli-tests": "npx bats tests/cli.bats", | ||
"build": "pkg --compress brotli ." | ||
@@ -35,5 +36,10 @@ }, | ||
"js-yaml": "~4.1.0", | ||
"readline-sync": "~1.4.10" | ||
"readline-sync": "~1.4.10", | ||
"say": "~0.16.0", | ||
"which": "~2.0.2" | ||
}, | ||
"devDependencies": { | ||
"bats": "~1.4.1", | ||
"bats-assert": "~2.0.0", | ||
"bats-support": "~0.3.0", | ||
"pkg": "~5.3.1", | ||
@@ -40,0 +46,0 @@ "prettier": "~2.3.2" |
@@ -18,33 +18,60 @@ # Berkala | ||
boot: | ||
type: notify | ||
message: Berkala starts now | ||
steps: | ||
- notify: Berkala starts now | ||
# We need to stay hydrated | ||
hourly-ping: | ||
type: print | ||
stay-hydrated: | ||
interval: every 1 hour | ||
message: Drink some water! | ||
steps: | ||
- notify: Drink some water! # TODO: how much? | ||
- print: Reminder was sent | ||
lunch-reminder: | ||
type: notify | ||
lunch: | ||
interval: at 11:58am | ||
title: Important reminder | ||
message: It's lunch time very soon | ||
steps: | ||
- notify: It's lunch time very soon | ||
title: Important | ||
- say: Get ready for lunch | ||
weekend-exercise: | ||
type: notify | ||
cron: 0 9 * * 6 # every 9 morning on Saturday | ||
title: Stay healthy | ||
message: Time for some exercises! | ||
steps: | ||
- notify: Time for some exercises! | ||
title: Stay healthy | ||
``` | ||
Just like any regular YAML, everything from the `#` character until the end of the line will be ignored. Use this to insert comments. | ||
The schedule for each task can be specified either with a human-friendly interval (e.g. `every 5 minutes`, `at 5pm`) or a cron expression (refer to [crontab guru](https://crontab.guru/) for more details). If neither is explicitly stated, then the task runs right away. | ||
The schedule for each task can be specified as: | ||
* [a human-friendly interval](https://breejs.github.io/later/parsers.html#text), e.g. `every 5 minutes`, `at 5pm`, or | ||
* [a cron expression](https://crontab.guru/), e.g. `0 9 * * 6` | ||
As of now, only the following types of tasks are available: | ||
If neither is explicitly stated, then the task runs right away. | ||
* `print`: displays a message to the standard output | ||
* `notify`: sends a desktop notification | ||
Each task consists of one or more steps. | ||
Just like any regular YAML, everything from the `#` character until the end of the line will be ignored. Use this to insert comments. | ||
Every step must be one of the following: | ||
<details><summary><code>print</code>: displays a message to the standard output</summary></details> | ||
<details><summary><code>notify</code>: sends a desktop notification</summary> | ||
The notification is supported on the following system: | ||
* Windows: [Windows.UI.Notifications](https://docs.microsoft.com/en-us/uwp/api/windows.ui.notifications.toastnotification) via [Powershell scripting](https://docs.microsoft.com/en-us/powershell/scripting) | ||
* macOS: `display notification` with [AppleScript](https://developer.apple.com/library/archive/documentation/AppleScript/Conceptual/AppleScriptLangGuide/reference/ASLR_cmds.html) | ||
* Linux: [notify-send](https://www.commandlinux.com/man-page/man1/notify-send.1.html), e.g. part of `libnotify-bin` package on Debian/Ubuntu | ||
</details> | ||
<details><summary><code>say</code>: converts text to audible speech</summary> | ||
The text-to-speech conversion is supported on the following system: | ||
* Windows: [System.Speech.Synthesis](https://docs.microsoft.com/en-us/dotnet/api/system.speech.synthesis) via [Powershell scripting](https://docs.microsoft.com/en-us/powershell/scripting) | ||
* macOS: `say` with [AppleScript](https://developer.apple.com/library/archive/documentation/AppleScript/Conceptual/AppleScriptLangGuide/reference/ASLR_cmds.html) | ||
* Linux: [Festival speech synthesis](https://www.cstr.ed.ac.uk/projects/festival/), e.g. `festival` and `festvox-kallpc16k` on Debian/Ubuntu | ||
</details> | ||
Found a problem or have a new idea? File [an issue](https://github.com/ariya/berkala/issues)! | ||
<details> | ||
@@ -51,0 +78,0 @@ <summary>Alternative way to run Berkala (with Node.js)</summary> |
Sorry, the diff of this file is not supported yet
17426
8
206
96
5
5
+ Addedsay@~0.16.0
+ Addedwhich@~2.0.2
+ Addedisexe@2.0.0(transitive)
+ Addedone-time@0.0.4(transitive)
+ Addedsay@0.16.0(transitive)
+ Addedwhich@2.0.2(transitive)