Skip to content

Commit

Permalink
(feat) add support for ascii tables
Browse files Browse the repository at this point in the history
```task-table-ascii``` renders an ascii table in a very similar format
as taskwarrior's CLI table.
  • Loading branch information
Ashton Eby committed Mar 7, 2024
1 parent 2a7d944 commit 3990e6f
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 65 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ You can also provide [command line overrides](https://taskwarrior.org/docs/confi
command: task rc.context:home rc.report.list.filter:"status:pending" list
```
````
## ASCII task tables
To include an ASCII table of tasks, use a `task-table-ascii` code block. The text inside is parsed the same as a `task-table` code block, except that the output is rendered as monospace ASCII instead of as an HTML table. You will likely need to include an override for the defaultWidth so lines are not wrapped; 80 appears to work for most situations. For example, `command: task rc.defaultwidth:80 project:home list`

## Developer Guide
### Releasing new releases
Expand Down
98 changes: 37 additions & 61 deletions main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Plugin, parseYaml, PluginSettingTab, Setting, App } from 'obsidian';
import { Plugin, parseYaml, PluginSettingTab, Setting, App, MarkdownRenderer, MarkdownPostProcessorContext } from 'obsidian'
import { execSync } from 'child_process'

interface Settings {
taskBinaryPath: string;
taskBinaryPath: string
}

const DEFAULT_SETTINGS: Settings = {
Expand All @@ -14,6 +14,7 @@ export class EgoRockSettingsTab extends PluginSettingTab {

constructor(app: App, plugin: EgoRock) {
super(app, plugin)
this.app = app
this.plugin = plugin
}

Expand All @@ -27,7 +28,7 @@ export class EgoRockSettingsTab extends PluginSettingTab {
.setDesc('The path to the taskwarrior binary. If task is on the system PATH, "task" should work. Otherwise, provide an absolute path. WSL systems can invoke taskwarrior running in WSL from windows with the path "wsl task".')
.addText((text) =>
text
.setPlaceholder("task")
.setPlaceholder('task')
.setValue(this.plugin.settings.taskBinaryPath)
.onChange(async (value) => {
this.plugin.settings.taskBinaryPath = value
Expand All @@ -38,21 +39,29 @@ export class EgoRockSettingsTab extends PluginSettingTab {
}

export default class EgoRock extends Plugin {
settings: Settings;
settings: Settings

async onload() {
await this.loadSettings();
await this.loadSettings()
this.addSettingTab(new EgoRockSettingsTab(this.app, this))

this.registerMarkdownCodeBlockProcessor('task-table', (source, element, context) => {
this.doCommandReturnString(parseYaml(source).command, element)
this.doCommand(parseYaml(source).command, false, this.buildHTMLTable, [element, context])
})

this.registerMarkdownCodeBlockProcessor('task-table-ascii', (source, element, context) => {
this.doCommand(parseYaml(source).command, true, this.buildASCIITable, [element, context])
})
}

onunload() {
}

buildTable(tableDescription: any, el: any) {
buildASCIITable(rawTable: string, el: any, context: MarkdownPostProcessorContext) {
MarkdownRenderer.render(this.app, '```\n' + rawTable + '\n```', el, context.sourcePath, this)
}

buildHTMLTable(tableDescription: any, el: any) {
const [columns, rows] = tableDescription
const tableEl = el.createEl('table')
const headerEl = tableEl.createEl('thead').createEl('tr')
Expand Down Expand Up @@ -112,38 +121,32 @@ export default class EgoRock extends Plugin {
return [indices, rows]
}

doCommandReturnString(commandString: string, el: any) {
commandString = commandString.replace(/^task /, '')
buildCommand(commandString: string) {
const reports = this.getReportNames()
const report = commandString.split(' ').slice(-1)[0]
const report = commandString.replace(/^task /, '').split(' ').slice(-1)[0]
const taskwarriorBin = this.settings.taskBinaryPath
if (reports.includes(report)) {
const newCommand = `${taskwarriorBin.trim()} rc.detection:off rc.defaultwidth:1000 ${commandString}`
const asciiTable = execSync(newCommand).toString().split('\n')
.filter((line) => {
if (line.match(/^[ -]*$/)) return false
if (line.match(/^\d+ tasks*$/)) return false
if (line.match(/^\d+ tasks, \d+ shown*$/)) return false
return true
})
return this.buildTable(this.buildTableDescription(asciiTable), el)
if (!commandString.contains('rc.defaultwidth:')) commandString = `rc.defaultwidth:1000 ${commandString}`
if (!commandString.contains('rc.detection:')) commandString = `rc.detection:off ${commandString}`
return `${taskwarriorBin.trim()} ${commandString}`
} else {
throw new Error(`Taskwarrior command must be a report, was: ${report}.`)
}
}

doCommand(commandString: string) {
const reports = this.getReportNames()
const report = commandString.split(' ')[0]
const taskwarriorBin = this.settings.taskBinaryPath
if (reports.includes(report)) {
const newCommand = `${taskwarriorBin.trim()} ${this.buildCommandForReport(report)}`
return JSON.parse(execSync(newCommand).toString())
} else if (report === 'export') {
return JSON.parse(execSync(`${taskwarriorBin.trim()} ${commandString}`).toString())
} else {
throw new Error(`Taskwarrior command must either be a report or export, was: ${report}.`)
}
filterOutputToTable(output: Buffer) {
return output.toString().split('\n')
.filter((line) => {
if (line.match(/^[ -]*$/)) return false
if (line.match(/^\d+ tasks*$/)) return false
if (line.match(/^\d+ tasks, \d+ shown*$/)) return false
return true
})
}

doCommand(commandString: string, raw: boolean, processor: any, processorArgs: any) {
const asciiTable = this.filterOutputToTable(execSync(this.buildCommand(commandString)))
return processor.call(this, raw ? asciiTable.join('\n') : this.buildTableDescription(asciiTable), ...processorArgs)
}

getReport(report: string) {
Expand All @@ -159,52 +162,25 @@ export default class EgoRock extends Plugin {
}, {} as Record<string, string>)
}

// TODO: merge with user-provided info
buildCommandForReport(report: string) {
return this.getReports().reduce((result, line) => {
const regex = RegExp(`report\.${report}.([^ ]*) +(.+)`)
const matches = regex.exec(line)
if (matches) {
return `${result} rc.${matches[1]}="${matches[2]}"`
} else {
return result
}
}, 'export')
}

getReports() {
const taskwarriorBin = this.settings.taskBinaryPath
const lines = execSync(`${taskwarriorBin} task show report`).toString().split('\n').filter(line => line.match(/^report\..+/))
return lines
return execSync(`${taskwarriorBin} show report`).toString().split('\n').filter(line => line.match(/^report\..+/))
}

getReportNames() {
return this.getReports().reduce((result, line) => {
// TODO: could this be report.regex instead of regex.regex? see getReportSettings
const matches = /^[^\.]+\.([^\.]+).*/.exec(line)
if (matches && matches[1] && !result.includes(matches[1]))
return [ ...result, matches[1] ]
return result
}, [] as String[])
}

// we could instead use export, but that won't work with reports
// we could read the report, _then_ export with the report's filter provided
// but for now, let's take the easy / naive way out
parseReport(reportString: string) {
const lines = reportString.split('\n').filter(line => !!line)
const columns = lines[0].split(' ').filter(line => !!line)
return {
columns: columns,
rows: lines.slice(1)
}
}

async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData())
}

async saveSettings() {
await this.saveData(this.settings);
await this.saveData(this.settings)
}
}
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "ego-rock",
"name": "Ego Rock",
"version": "2.0.0",
"version": "2.0.2",
"minAppVersion": "0.15.0",
"description": "A basic taskwarrior UI for listing and modifying tasks.",
"author": "Ashton Eby",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ego-rock",
"version": "2.0.0",
"version": "2.0.2",
"description": "A plugin for Obsidian that allows viewing taskwarrior tasks.",
"main": "main.js",
"scripts": {
Expand Down
5 changes: 3 additions & 2 deletions versions.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"1.0.1": "0.15.0",
"1.0.2": "0.15.0",
"2.0.0": "0.15.0",
"2.0.1": "0.15.0"
}
"2.0.1": "0.15.0",
"2.0.2": "0.15.0"
}

0 comments on commit 3990e6f

Please sign in to comment.