Compare commits
No commits in common. "main" and "v1.2.3" have entirely different histories.
8 changed files with 145 additions and 141 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -173,7 +173,7 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "day-reporter"
|
name = "day-reporter"
|
||||||
version = "2.0.0"
|
version = "1.2.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "day-reporter"
|
name = "day-reporter"
|
||||||
version = "2.0.0"
|
version = "1.2.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
|
||||||
18
README.md
18
README.md
|
|
@ -4,9 +4,6 @@ A small program written in rust that digests my Things tasks into a format for S
|
||||||
Tasks are pulled out of Things using AppleScript over a JavaScript OSA Bridge. Tasks are then filtered
|
Tasks are pulled out of Things using AppleScript over a JavaScript OSA Bridge. Tasks are then filtered
|
||||||
and formatted based on the selected format schema.
|
and formatted based on the selected format schema.
|
||||||
|
|
||||||
## Options
|
|
||||||
See `day-reporter -h` for a current list of options
|
|
||||||
|
|
||||||
## Basic Task format
|
## Basic Task format
|
||||||
A task is returned as a markdown list element containing its title and optionally a list of information parsed from
|
A task is returned as a markdown list element containing its title and optionally a list of information parsed from
|
||||||
related tag sections in the notes portion of the task.
|
related tag sections in the notes portion of the task.
|
||||||
|
|
@ -15,16 +12,6 @@ Tag specific additional bullets are included using a triple backtick (like GitHu
|
||||||
specifying the coding style as you would (for example `typescript`) you specify the relevant tag: `MyTag`. Running the
|
specifying the coding style as you would (for example `typescript`) you specify the relevant tag: `MyTag`. Running the
|
||||||
program with a given `--tags` argument will cause related tag blocks to be included as sub-bullets.
|
program with a given `--tags` argument will cause related tag blocks to be included as sub-bullets.
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
<pre>
|
|
||||||
```MyTag
|
|
||||||
Some notes
|
|
||||||
```
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
will result in "Some notes" being included with the task
|
|
||||||
|
|
||||||
## Name Sanitization
|
## Name Sanitization
|
||||||
Name tags found on projects and tasks are automatically sanitized by replacing vowel characters with lookalikes. This
|
Name tags found on projects and tasks are automatically sanitized by replacing vowel characters with lookalikes. This
|
||||||
behavior can be disabled with the `--no-sanitize` argument.
|
behavior can be disabled with the `--no-sanitize` argument.
|
||||||
|
|
@ -36,3 +23,8 @@ and project specific matched tag block comments in the project notes.
|
||||||
## Area Formatting
|
## Area Formatting
|
||||||
Areas collect up projects, but instead of indenting them further, Area's are returned as section titles above the
|
Areas collect up projects, but instead of indenting them further, Area's are returned as section titles above the
|
||||||
generated output. If only one area contains all of the reported tasks, then the area name is omitted entirely.
|
generated output. If only one area contains all of the reported tasks, then the area name is omitted entirely.
|
||||||
|
|
||||||
|
## Cycle Message Formatting
|
||||||
|
The `--mode cycle` argument will output slightly different content. All tasks are collected at a project level, and
|
||||||
|
only the project name is given. Cycle messages look back in time at the last 6 weeks and therefore have too many values
|
||||||
|
to meaningfully output at full resolution.
|
||||||
|
|
|
||||||
106
src/main.rs
106
src/main.rs
|
|
@ -5,42 +5,60 @@ mod names;
|
||||||
|
|
||||||
use reporter::{MarkdownReporter, Reporter, Resolution, ReportOptions};
|
use reporter::{MarkdownReporter, Reporter, Resolution, ReportOptions};
|
||||||
|
|
||||||
use things::task::Task;
|
use things::task::{Task, Status};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
|
|
||||||
#[derive(ValueEnum, Copy, Clone, Eq, PartialEq)]
|
#[derive(ValueEnum, Copy, Clone, Eq, PartialEq)]
|
||||||
enum ListType {
|
enum ReportTypes {
|
||||||
/// Generate a report from the Things today list
|
/// report projected work for the day and a morning message
|
||||||
Today,
|
Morning,
|
||||||
/// Generate a report from the Things logbook
|
/// report what major tasks were completed in the last cycle.
|
||||||
Logbook,
|
Cycle,
|
||||||
|
/// report what was actually done today and a signoff message
|
||||||
|
Signoff,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ListType {
|
impl ReportTypes {
|
||||||
fn format_tasks(&self, tasks: Vec<Task>, tags: &Vec<String>, sanitize_names: bool, resolution: Resolution) -> String {
|
fn format_tasks(&self, tasks: Vec<Task>, tags: &Vec<String>, sanitize_names: bool) -> String {
|
||||||
match self {
|
match self {
|
||||||
ListType::Today => {
|
ReportTypes::Morning => {
|
||||||
MarkdownReporter.report(tasks, &ReportOptions {
|
let task_report = MarkdownReporter.report(tasks, &ReportOptions {
|
||||||
resolution,
|
resolution: Resolution::FullTask,
|
||||||
tags: tags.to_vec(),
|
tags: tags.to_vec(),
|
||||||
sanitize_names,
|
sanitize_names,
|
||||||
})
|
});
|
||||||
|
format!("{}\n\n{}", emoji::pick(3).join(" "), task_report)
|
||||||
},
|
},
|
||||||
ListType::Logbook => {
|
ReportTypes::Signoff => {
|
||||||
MarkdownReporter.report(tasks, &ReportOptions {
|
let task_report = MarkdownReporter.report(tasks, &ReportOptions {
|
||||||
resolution,
|
resolution: Resolution::FullTask,
|
||||||
tags: tags.to_vec(),
|
tags: tags.to_vec(),
|
||||||
sanitize_names,
|
sanitize_names,
|
||||||
})
|
});
|
||||||
|
format!("Stopping now\n\n{}", task_report)
|
||||||
|
},
|
||||||
|
ReportTypes::Cycle => {
|
||||||
|
let further_filtered = tasks.into_iter().filter(|t| {
|
||||||
|
if let Some(p) = &t.project {
|
||||||
|
return p.status == Status::Completed;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}).collect::<Vec<Task>>();
|
||||||
|
let task_report = MarkdownReporter.report(further_filtered, &ReportOptions {
|
||||||
|
resolution: Resolution::Project,
|
||||||
|
tags: tags.to_vec(),
|
||||||
|
sanitize_names,
|
||||||
|
});
|
||||||
|
format!("*Cycle Report*\n\n{}", task_report)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ListType {
|
impl Default for ReportTypes {
|
||||||
fn default() -> ListType {
|
fn default() -> ReportTypes {
|
||||||
ListType::Today
|
ReportTypes::Morning
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,65 +70,33 @@ struct CliArgs {
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
tags: Vec<String>,
|
tags: Vec<String>,
|
||||||
|
|
||||||
/// A list of tags to specifically omit from the results. Only todo list items WITHOUT these
|
|
||||||
/// tags will be included
|
|
||||||
#[arg(short, long)]
|
|
||||||
omit: Vec<String>,
|
|
||||||
|
|
||||||
/// Select the type of report to generate
|
/// Select the type of report to generate
|
||||||
#[arg(short, long, default_value_t = ListType::default())]
|
#[arg(short, long, default_value_t = ReportTypes::default())]
|
||||||
#[clap(value_enum)]
|
#[clap(value_enum)]
|
||||||
list: ListType,
|
report: ReportTypes,
|
||||||
|
|
||||||
/// By default, any @<name> style tags will be sanitized in the output to avoid @-mentions in
|
/// By default, any @<name> style tags will be sanitized in the output to avoid @-mentions in
|
||||||
/// Slack. This is done by replacing vowel characters with unicode lookalikes. If this
|
/// Slack. This is done by replacing vowel characters with unicode lookalikes. If this
|
||||||
/// flag is set then the names will be passed through unsanitized.
|
/// flag is set then the names will be passed through unsanitized.
|
||||||
#[arg(long, default_value_t = false)]
|
#[arg(long, default_value_t = false)]
|
||||||
no_sanitize: bool,
|
no_sanitize: bool,
|
||||||
|
|
||||||
/// An ISO date string for when to filter tasks from.
|
|
||||||
/// Defaults to midnight this morning if unset. Not used for the today list
|
|
||||||
#[arg(long)]
|
|
||||||
from: Option<String>,
|
|
||||||
|
|
||||||
/// An ISO date string for when to filter tasks until.
|
|
||||||
/// Defaults to 1 second before midnight tonight if unset. Not used for the today list
|
|
||||||
#[arg(long)]
|
|
||||||
to: Option<String>,
|
|
||||||
|
|
||||||
/// An optional message to include at the beginning of the report. If omitted, 3 random emojis
|
|
||||||
/// will be included instead
|
|
||||||
#[arg(short, long)]
|
|
||||||
message: Option<String>,
|
|
||||||
|
|
||||||
/// Choose a resolution for the report. This will determine how much detail is included in the
|
|
||||||
/// output. Default is "FullTask"
|
|
||||||
#[arg(short, long, default_value_t = Resolution::default())]
|
|
||||||
#[clap(value_enum)]
|
|
||||||
resolution: Resolution,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let args = CliArgs::parse();
|
let args = CliArgs::parse();
|
||||||
let now = chrono::Local::now();
|
let tasks = match args.report {
|
||||||
let from = args.from.unwrap_or_else(|| now.date().and_hms(0, 0, 0).to_rfc3339());
|
ReportTypes::Morning => Task::today(),
|
||||||
let to = args.to.unwrap_or_else(|| (now + chrono::Duration::days(1)).date().and_hms(0, 0, 0).to_rfc3339());
|
ReportTypes::Signoff => Task::logbook_today(),
|
||||||
|
ReportTypes::Cycle => Task::logbook_this_cycle(),
|
||||||
let message = args.message.unwrap_or_else(|| emoji::pick(3).join(" "));
|
|
||||||
|
|
||||||
let tasks = match args.list {
|
|
||||||
ListType::Today => Task::today(&from, &to),
|
|
||||||
ListType::Logbook => Task::logbook(&from, &to),
|
|
||||||
}?;
|
}?;
|
||||||
let mut reported: Vec<Task> = tasks.into_iter().filter(|task| {
|
let mut reported: Vec<Task> = tasks.into_iter().filter(|task| {
|
||||||
// Filter down to tasks with all selected tags and without any of the omitted tags
|
args.tags.iter().all(|tag| task.has_tag(tag))
|
||||||
args.tags.iter().all(|tag| task.has_tag(tag)) && !args.omit.iter().any(|tag| task.has_tag(tag))
|
|
||||||
}).collect();
|
}).collect();
|
||||||
reported.sort_by(|a, b| {
|
reported.sort_by(|a, b| {
|
||||||
a.completion_date.cmp(&b.completion_date)
|
a.completion_date.cmp(&b.completion_date)
|
||||||
});
|
});
|
||||||
let report = args.list.format_tasks(reported, &args.tags, !args.no_sanitize, args.resolution);
|
let report = args.report.format_tasks(reported, &args.tags, !args.no_sanitize);
|
||||||
println!("{message}\n\n{report}");
|
println!("{report}");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
use clap::ValueEnum;
|
use crate::things::task::Task;
|
||||||
|
|
||||||
use crate::things::task::{Task, Status};
|
|
||||||
use crate::names::sanitize_names;
|
use crate::names::sanitize_names;
|
||||||
|
|
||||||
/// Given a notes field and a list of possible tags for sections, return the content of triple tick
|
/// Given a notes field and a list of possible tags for sections, return the content of triple tick
|
||||||
|
|
@ -128,18 +126,11 @@ impl ThingsTree {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(ValueEnum, Copy, Clone, Eq, PartialEq)]
|
|
||||||
pub enum Resolution {
|
pub enum Resolution {
|
||||||
FullTask,
|
FullTask,
|
||||||
Project,
|
Project,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Resolution {
|
|
||||||
fn default() -> Resolution {
|
|
||||||
Resolution::FullTask
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ReportOptions {
|
pub struct ReportOptions {
|
||||||
pub resolution: Resolution,
|
pub resolution: Resolution,
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
|
|
@ -185,17 +176,11 @@ impl Reporter for MarkdownReporter {
|
||||||
.map(|l| format!("\n{}- {}", String::from(" ").repeat(depth + 4), l))
|
.map(|l| format!("\n{}- {}", String::from(" ").repeat(depth + 4), l))
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join("");
|
.join("");
|
||||||
let title = if task.status == Status::Canceled {
|
let mut output = format!("\n{}- {}{}", String::from(" ").repeat(depth), task.title, relevant_notes);
|
||||||
format!("~{}~", task.title)
|
|
||||||
} else {
|
|
||||||
task.title.to_string()
|
|
||||||
};
|
|
||||||
let mut output = format!("\n{}- {}{}", String::from(" ").repeat(depth), title, relevant_notes);
|
|
||||||
if options.sanitize_names {
|
if options.sanitize_names {
|
||||||
output = sanitize_names(&output, &task.tags);
|
output = sanitize_names(&output, &task.tags);
|
||||||
}
|
}
|
||||||
|
return output;
|
||||||
output
|
|
||||||
}
|
}
|
||||||
fn report_project(&mut self, project: &ProjectTree, depth: usize, options: &ReportOptions) -> String {
|
fn report_project(&mut self, project: &ProjectTree, depth: usize, options: &ReportOptions) -> String {
|
||||||
let resolution = &options.resolution;
|
let resolution = &options.resolution;
|
||||||
|
|
@ -213,7 +198,7 @@ impl Reporter for MarkdownReporter {
|
||||||
.map(|t| self.report_task(t, depth + 4, options))
|
.map(|t| self.report_task(t, depth + 4, options))
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join("");
|
.join("");
|
||||||
format!("{}{}{}{}", String::from(" ").repeat(depth), project.title, relevant_notes, tasks)
|
format!("{}{}{}{}", String::from(" ").repeat(depth), relevant_notes, project.title, tasks)
|
||||||
},
|
},
|
||||||
Resolution::Project => {
|
Resolution::Project => {
|
||||||
format!("{}- {}{}", String::from(" ").repeat(depth), project.title, relevant_notes)
|
format!("{}- {}{}", String::from(" ").repeat(depth), project.title, relevant_notes)
|
||||||
|
|
@ -224,7 +209,7 @@ impl Reporter for MarkdownReporter {
|
||||||
output = sanitize_names(&output, &project.tags);
|
output = sanitize_names(&output, &project.tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
output
|
return output;
|
||||||
}
|
}
|
||||||
fn report_single_area(&mut self, area: &AreaTree, options: &ReportOptions) -> String {
|
fn report_single_area(&mut self, area: &AreaTree, options: &ReportOptions) -> String {
|
||||||
let project_reports = area.projects
|
let project_reports = area.projects
|
||||||
|
|
|
||||||
|
|
@ -2,36 +2,41 @@ var things = Application("Things");
|
||||||
var logbook = things.lists.byName("Logbook").toDos();
|
var logbook = things.lists.byName("Logbook").toDos();
|
||||||
var objs = [];
|
var objs = [];
|
||||||
|
|
||||||
var from = new Date($params.from);
|
var from = new Date();
|
||||||
var to = new Date($params.to);
|
from.setHours(0);
|
||||||
|
from.setMinutes(0);
|
||||||
|
from.setSeconds(0);
|
||||||
|
var to = new Date();
|
||||||
|
to.setTime(from.getTime());
|
||||||
|
to.setHours(23);
|
||||||
|
to.setMinutes(59);
|
||||||
|
to.setSeconds(59);
|
||||||
|
|
||||||
for (const todo of logbook) {
|
logbook.filter(task => {
|
||||||
if(todo.completionDate() >= from && todo.completionDate() < to) {
|
return task.completionDate() >= from && task.completionDate() < to;
|
||||||
var proj = todo.project();
|
}).forEach(todo => {
|
||||||
var tags = [];
|
var proj = todo.project();
|
||||||
if (proj) {
|
var tags = [];
|
||||||
tags.push(...proj.tagNames().split(', '));
|
if (proj) {
|
||||||
}
|
tags.push(...proj.tagNames().split(', '));
|
||||||
var area = todo.area() || proj && proj.area();
|
|
||||||
objs.push({
|
|
||||||
id: todo.id(),
|
|
||||||
title: todo.name(),
|
|
||||||
notes: todo.notes() || null,
|
|
||||||
status: todo.status(),
|
|
||||||
completion_date: todo.completionDate(),
|
|
||||||
project: proj && {
|
|
||||||
id: proj.id(),
|
|
||||||
title: proj.name(),
|
|
||||||
status: proj.status(),
|
|
||||||
notes: proj.notes(),
|
|
||||||
tags: proj.tagNames().split(', '),
|
|
||||||
},
|
|
||||||
area: area && { id: area.id(), title: area.name() },
|
|
||||||
tags: [...tags, ...todo.tagNames().split(', ')].filter(t => t),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
var area = todo.area() || proj && proj.area();
|
||||||
|
objs.push({
|
||||||
|
id: todo.id(),
|
||||||
|
title: todo.name(),
|
||||||
|
notes: todo.notes() || null,
|
||||||
|
status: todo.status(),
|
||||||
|
completion_date: todo.completionDate(),
|
||||||
|
project: proj && {
|
||||||
|
id: proj.id(),
|
||||||
|
title: proj.name(),
|
||||||
|
status: proj.status(),
|
||||||
|
notes: proj.notes(),
|
||||||
|
tags: proj.tagNames().split(', '),
|
||||||
|
},
|
||||||
|
area: area && { id: area.id(), title: area.name() },
|
||||||
|
tags: [...tags, ...todo.tagNames().split(', ')].filter(t => t),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return JSON.stringify(objs, undefined, 2);
|
return JSON.stringify(objs, undefined, 2);
|
||||||
|
|
|
||||||
42
src/things/logbook_cycle.js
Normal file
42
src/things/logbook_cycle.js
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
var things = Application("Things");
|
||||||
|
var logbook = things.lists.byName("Logbook").toDos();
|
||||||
|
var objs = [];
|
||||||
|
|
||||||
|
// From 6 weeks ago
|
||||||
|
var from = new Date(new Date().getTime() - (6 * 7 * 24 * 60 * 60 * 1000));
|
||||||
|
from.setHours(0);
|
||||||
|
from.setMinutes(0);
|
||||||
|
from.setSeconds(0);
|
||||||
|
var to = new Date();
|
||||||
|
to.setHours(23);
|
||||||
|
to.setMinutes(59);
|
||||||
|
to.setSeconds(59);
|
||||||
|
|
||||||
|
logbook.filter(task => {
|
||||||
|
return task.completionDate() >= from && task.completionDate() < to;
|
||||||
|
}).forEach(todo => {
|
||||||
|
var proj = todo.project();
|
||||||
|
var tags = [];
|
||||||
|
if (proj) {
|
||||||
|
tags.push(...proj.tagNames().split(', '));
|
||||||
|
}
|
||||||
|
var area = todo.area() || proj && proj.area();
|
||||||
|
objs.push({
|
||||||
|
id: todo.id(),
|
||||||
|
title: todo.name(),
|
||||||
|
notes: todo.notes() || null,
|
||||||
|
status: todo.status(),
|
||||||
|
completion_date: todo.completionDate(),
|
||||||
|
project: proj && {
|
||||||
|
id: proj.id(),
|
||||||
|
title: proj.name(),
|
||||||
|
status: proj.status(),
|
||||||
|
notes: proj.notes(),
|
||||||
|
tags: proj.tagNames().split(', '),
|
||||||
|
},
|
||||||
|
area: area && { id: area.id(), title: area.name() },
|
||||||
|
tags: [...tags, ...todo.tagNames().split(', ')].filter(t => t),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return JSON.stringify(objs, undefined, 2);
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use std::str::from_utf8;
|
use std::str::from_utf8;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use osascript;
|
use osascript;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
|
@ -13,8 +13,6 @@ pub enum Status {
|
||||||
Incomplete,
|
Incomplete,
|
||||||
#[serde(rename = "open")]
|
#[serde(rename = "open")]
|
||||||
Open,
|
Open,
|
||||||
#[serde(rename = "canceled")]
|
|
||||||
Canceled,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
|
|
@ -44,31 +42,27 @@ pub struct Task {
|
||||||
pub area: Option<Area>,
|
pub area: Option<Area>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
|
||||||
pub struct TaskParams {
|
|
||||||
// ISO string for the start date
|
|
||||||
from: String,
|
|
||||||
// ISO string for the end date
|
|
||||||
to: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Task {
|
impl Task {
|
||||||
/// A Helper for loading Tasks from json returned by an osascript
|
/// A Helper for loading Tasks from json returned by an osascript
|
||||||
fn from_script(script_bytes: &[u8], params: TaskParams) -> Result<Vec<Task>> {
|
fn from_script(script_bytes: &[u8]) -> Result<Vec<Task>> {
|
||||||
let script = osascript::JavaScript::new(from_utf8(script_bytes)?);
|
let script = osascript::JavaScript::new(from_utf8(script_bytes)?);
|
||||||
let raw_json: String = script.execute_with_params(params)?;
|
let raw_json: String = script.execute()?;
|
||||||
let tasks: Vec<Task> = serde_json::from_str(&raw_json)?;
|
let tasks: Vec<Task> = serde_json::from_str(&raw_json)?;
|
||||||
Ok(tasks)
|
Ok(tasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all tasks in the today list from Things
|
/// Get all tasks in the today list from Things
|
||||||
pub fn today(from: &String, to: &String) -> Result<Vec<Task>> {
|
pub fn today() -> Result<Vec<Task>> {
|
||||||
Task::from_script(include_bytes!("today.js"), TaskParams { from: from.to_string(), to: to.to_string() })
|
Task::from_script(include_bytes!("today.js"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all tasks in the logbook list from Things
|
/// Get all tasks in the logbook list from Things
|
||||||
pub fn logbook(from: &String, to: &String) -> Result<Vec<Task>> {
|
pub fn logbook_today() -> Result<Vec<Task>> {
|
||||||
Task::from_script(include_bytes!("logbook.js"), TaskParams { from: from.to_string(), to: to.to_string() })
|
Task::from_script(include_bytes!("logbook.js"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn logbook_this_cycle() -> Result<Vec<Task>> {
|
||||||
|
Task::from_script(include_bytes!("logbook_cycle.js"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_tag(&self, tag: &str) -> bool {
|
pub fn has_tag(&self, tag: &str) -> bool {
|
||||||
|
|
|
||||||
Reference in a new issue