tangled
alpha
login
or
join now
ngp.computer
/
tsk
A file-based task manager
0
fork
atom
overview
issues
pulls
pipelines
ADD: completion and edit commands
ngp.computer
1 year ago
6f57b846
9c01bd41
+65
-16
2 changed files
expand all
collapse all
unified
split
src
main.rs
workspace.rs
+34
-9
src/main.rs
···
2
2
mod stack;
3
3
mod util;
4
4
mod workspace;
5
5
+
use clap_complete::{generate, Shell};
6
6
+
use std::io;
5
7
use std::path::PathBuf;
6
8
use std::{env::current_dir, io::Read};
7
7
-
use clap_complete::Shell;
8
9
use workspace::Workspace;
9
10
10
11
//use smol;
11
12
//use iocraft::prelude::*;
12
12
-
use clap::{Args, Parser, Subcommand};
13
13
+
use clap::{Args, CommandFactory, Parser, Subcommand};
13
14
use edit::edit as open_editor;
14
15
15
16
fn default_dir() -> PathBuf {
···
59
60
60
61
Edit {
61
62
#[arg(short = 't')]
62
62
-
task_id: u32
63
63
+
task_id: u32,
63
64
},
64
65
65
66
Completion {
66
67
#[arg(short = 's')]
67
67
-
shell: Shell
68
68
+
shell: Shell,
69
69
+
},
70
70
+
71
71
+
/*
72
72
+
Drop {
73
73
+
#[arg(short = 't')]
74
74
+
task_id: Option<u32>,
68
75
}
76
76
+
*/
69
77
}
70
78
71
79
#[derive(Args)]
···
88
96
command_push(cli.dir.unwrap_or(default_dir()), edit, body, title)
89
97
}
90
98
Commands::List { all, count } => command_list(cli.dir.unwrap_or(default_dir()), all, count),
91
91
-
Commands::Swap => command_swap(cli.dir.unwrap_or(default_dir()))
99
99
+
Commands::Swap => command_swap(cli.dir.unwrap_or(default_dir())),
100
100
+
Commands::Edit { task_id } => command_edit(cli.dir.unwrap_or(default_dir()), task_id),
101
101
+
Commands::Completion { shell } => command_completion(shell),
92
102
}
93
103
}
94
104
···
98
108
99
109
fn command_push(dir: PathBuf, edit: bool, body: Option<String>, title: Title) {
100
110
let workspace = Workspace::from_path(dir).expect("Unable to find .tsk dir");
101
101
-
let title = if let Some(title) = title.title {
111
111
+
let mut title = if let Some(title) = title.title {
102
112
title
103
113
} else if let Some(title) = title.title_simple {
104
114
let joined = title.join(" ");
···
116
126
.expect("Failed to read stdin");
117
127
}
118
128
if edit {
119
119
-
body = open_editor(format!("{title}\n\n{body}")).expect("Failed to edit file");
129
129
+
let new_content = open_editor(format!("{title}\n\n{body}")).expect("Failed to edit file");
130
130
+
if let Some(content) = new_content.split_once("\n") {
131
131
+
title = content.0.to_string();
132
132
+
body = content.1.to_string();
133
133
+
}
120
134
}
121
135
let task = workspace
122
136
.new_task(title, body)
···
147
161
workspace.swap_top().expect("swap to work");
148
162
}
149
163
150
150
-
fn command_edit(dir: PathBuf) {
164
164
+
fn command_edit(dir: PathBuf, id: u32) {
151
165
let workspace = Workspace::from_path(dir).expect("Unable to find .tsk dir");
152
152
-
let task = workspace.
166
166
+
let mut task = workspace.task(id.into()).expect("To read task from disk");
167
167
+
let new_content =
168
168
+
open_editor(format!("{}\n\n{}", task.title.trim(), task.body.trim())).expect("Failed to edit file");
169
169
+
if let Some((title, body)) = new_content.split_once("\n") {
170
170
+
task.title = title.to_string();
171
171
+
task.body = body.to_string();
172
172
+
task.save().expect("Failed to save task");
173
173
+
}
174
174
+
}
175
175
+
176
176
+
fn command_completion(shell: Shell) {
177
177
+
generate(shell, &mut Cli::command(), "tsk", &mut io::stdout())
153
178
}
+31
-7
src/workspace.rs
···
6
6
use crate::util;
7
7
use std::fmt::Display;
8
8
use std::fs::File;
9
9
-
use std::io::{Read, Seek};
9
9
+
use std::io::{BufRead as _, BufReader, Read, Seek, SeekFrom};
10
10
use std::path::PathBuf;
11
11
use std::str::FromStr;
12
12
use std::{fs::OpenOptions, io::Write};
···
30
30
impl Display for Id {
31
31
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32
32
write!(f, "tsk-{}", self.0)
33
33
+
}
34
34
+
}
35
35
+
36
36
+
impl From<u32> for Id {
37
37
+
fn from(value: u32) -> Self {
38
38
+
Id(value)
33
39
}
34
40
}
35
41
···
82
88
// reset the files contents
83
89
file.set_len(0)?;
84
90
// TODO: figure out if this is necessary
85
85
-
file.seek(std::io::SeekFrom::Start(0))?;
91
91
+
file.seek(SeekFrom::Start(0))?;
86
92
// store the *next* if
87
93
file.write_all(format!("{}\n", id + 1).as_bytes())?;
88
94
Ok(Id(id))
···
105
111
}
106
112
107
113
pub fn task(&self, id: Id) -> Result<Task> {
108
108
-
let mut file = util::flopen(
114
114
+
let file = util::flopen(
109
115
self.path.join("tasks").join(format!("tsk-{}.tsk", id.0)),
110
116
FlockArg::LockExclusive,
111
117
)?;
112
112
-
118
118
+
let mut title = String::new();
119
119
+
let mut body = String::new();
120
120
+
let mut reader = BufReader::new(&*file);
121
121
+
reader.read_line(&mut title)?;
122
122
+
reader.read_to_string(&mut body)?;
123
123
+
drop(reader);
124
124
+
Ok(Task {
125
125
+
id,
126
126
+
title,
127
127
+
body,
128
128
+
file,
129
129
+
})
113
130
}
114
131
115
132
pub fn read_stack(&self, count: Option<usize>) -> Result<TaskStack> {
···
138
155
pub file: Flock<File>,
139
156
}
140
157
141
141
-
#[cfg(test)]
142
142
-
mod test {
143
143
-
fn test_next_id() {}
158
158
+
impl Task {
159
159
+
/// Consumes a task and saves it to disk.
160
160
+
pub fn save(mut self) -> Result<()> {
161
161
+
self.file.set_len(0)?;
162
162
+
self.file.seek(SeekFrom::Start(0))?;
163
163
+
self.file.write_all(self.title.trim().as_bytes())?;
164
164
+
self.file.write_all(b"\n\n")?;
165
165
+
self.file.write_all(self.body.trim().as_bytes())?;
166
166
+
Ok(())
167
167
+
}
144
168
}