summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock50
-rw-r--r--Cargo.toml2
-rw-r--r--jt.md53
-rw-r--r--src/bin/jt2.rs51
-rw-r--r--src/error.rs20
-rw-r--r--src/journal.rs108
-rw-r--r--src/lib.rs1
-rw-r--r--src/opt.rs13
-rw-r--r--subplot/jt.py29
-rw-r--r--subplot/jt.yaml6
10 files changed, 287 insertions, 46 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f7f4a1b..035e089 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -36,6 +36,12 @@ dependencies = [
]
[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -48,6 +54,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time",
+ "winapi",
+]
+
+[[package]]
name = "clap"
version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -145,9 +164,11 @@ name = "jt2"
version = "0.1.0"
dependencies = [
"anyhow",
+ "chrono",
"directories-next",
"log",
"pretty_env_logger",
+ "regex",
"serde",
"serde_yaml",
"structopt",
@@ -188,6 +209,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
name = "pretty_env_logger"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -393,6 +433,16 @@ dependencies = [
]
[[package]]
+name = "time"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
name = "unicode-segmentation"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index a8e81bf..a76755a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,3 +13,5 @@ log = "0.4"
directories-next = "2.0.0"
serde = { version = "1", features = ["derive"] }
serde_yaml = "0.8"
+regex = "1"
+chrono = "0.4"
diff --git a/jt.md b/jt.md
index cbea089..ad40b35 100644
--- a/jt.md
+++ b/jt.md
@@ -26,7 +26,7 @@ until it's finished.
$ jt --dirname ~/Journal init default "My private journal"
$ jt new --tag meta --tag journalling "My first journal entry"
... # text editor is opened so the new entry can be written
-$ jt finish
+$ jt finish 0 first-entry
~~~
@@ -157,11 +157,11 @@ and there is one draft in jrnl
and draft 0 in jrnl contains "Abracadabra"
given an executable script append.sh
-when I run jt2 --editor=./append.sh --dirname=jrnl edit
+when I run jt2 --editor=./append.sh --dirname=jrnl edit 0
then command is successful
and draft 0 in jrnl contains "Open sesame!"
-when I run jt2 --dirname=jrnl finish
+when I run jt2 --dirname=jrnl finish 0 abra
then command is successful
and there is one journal entry in jrnl, at FILE
and journal entry <FILE> contains "Abracadabra"
@@ -175,6 +175,53 @@ set -eux
echo "Open sesame!" >> "$1"
~~~
+## Create two drafts
+
+Verify that we can create two draft entries at the same time.
+
+~~~scenario
+given an installed jt
+
+when I run jt2 --dirname jrnl init default "My test journal"
+then command is successful
+then there are no drafts in jrnl
+then there are no journal entries in jrnl
+
+when I run jt2 --editor=none --dirname=jrnl new "Abracadabra"
+then command is successful
+then there is one draft in jrnl
+then draft 0 in jrnl contains "Abracadabra"
+
+when I run jt2 --editor=none --dirname=jrnl new "Simsalabim"
+then command is successful
+then there are two drafts in jrnl
+then draft 0 in jrnl contains "Abracadabra"
+then draft 1 in jrnl contains "Simsalabim"
+
+given an executable script append.sh
+when I run jt2 --editor=./append.sh --dirname=jrnl edit 0
+then draft 0 in jrnl contains "Open sesame!"
+when I run jt2 --editor=./append.sh --dirname=jrnl edit 1
+then draft 1 in jrnl contains "Open sesame!"
+
+when I run jt2 --dirname=jrnl finish 0 abra
+then command is successful
+then there is one journal entry in jrnl, at FILE
+then journal entry <FILE> contains "Abracadabra"
+then journal entry <FILE> contains "Open sesame!"
+then there is one draft in jrnl
+
+when I run jt2 --dirname=jrnl finish 1 sim
+then command is successful
+then there are two journal entries in jrnl, at FILE1 and FILE2
+then journal entry <FILE1> contains "Abracadabra"
+then journal entry <FILE2> contains "Simsalabim"
+then there are no drafts in jrnl
+~~~
+
+
+
+
# Colophon
diff --git a/src/bin/jt2.rs b/src/bin/jt2.rs
index cf55aef..3d85229 100644
--- a/src/bin/jt2.rs
+++ b/src/bin/jt2.rs
@@ -1,11 +1,9 @@
use jt2::config::Configuration;
use jt2::error::JournalError;
+use jt2::journal::Journal;
use jt2::opt::{Opt, SubCommand};
-use log::debug;
-use std::fs;
use std::path::Path;
-use std::process::Command;
use structopt::StructOpt;
fn main() -> anyhow::Result<()> {
@@ -20,57 +18,40 @@ fn main() -> anyhow::Result<()> {
} => init(&config.dirname, &journalname, &description)?,
SubCommand::IsJournal => is_journal(&config.dirname)?,
SubCommand::New { title } => new_draft(&title, &config.dirname, &config.editor)?,
- SubCommand::Edit => edit_draft(&config.dirname, &config.editor)?,
- SubCommand::Finish => finish_draft(&config.dirname)?,
+ SubCommand::Edit { draft } => edit_draft(&config.dirname, &config.editor, &draft)?,
+ SubCommand::Finish { draft, basename } => finish_draft(&config.dirname, &draft, &basename)?,
}
Ok(())
}
fn init(dirname: &Path, _journalname: &str, _description: &str) -> anyhow::Result<()> {
- std::fs::create_dir(dirname)
- .map_err(|err| JournalError::CreateDirectory(dirname.to_path_buf(), err))?;
+ Journal::init(dirname)?;
Ok(())
}
fn is_journal(dirname: &Path) -> anyhow::Result<()> {
- let meta = fs::symlink_metadata(dirname)?;
- if !meta.is_dir() {
+ if !Journal::is_journal(dirname) {
return Err(JournalError::NotAJournal(dirname.display().to_string()).into());
}
Ok(())
}
-fn new_draft(title: &str, dirname: &Path, _editor: &str) -> anyhow::Result<()> {
- let drafts = dirname.join("drafts");
- if !drafts.exists() {
- std::fs::create_dir(&drafts)?;
- }
- let draft_filename = drafts.join("0.md");
- std::fs::write(draft_filename, title)?;
+fn new_draft(title: &str, dirname: &Path, editor: &str) -> anyhow::Result<()> {
+ let journal = Journal::new(dirname)?;
+ journal.new_draft(title, editor)?;
Ok(())
}
-fn edit_draft(dirname: &Path, editor: &str) -> anyhow::Result<()> {
- debug!("edit_draft: dirname={:?}", dirname);
- debug!("edit_draft: editor={:?}", editor);
- let drafts = dirname.join("drafts");
- let draft_filename = drafts.join("0.md");
- debug!("edit_draft: draft_filename={:?}", draft_filename);
- Command::new(editor).arg(draft_filename).status()?;
- debug!("edit_draft: editor finished");
+fn edit_draft(dirname: &Path, editor: &str, draft: &str) -> anyhow::Result<()> {
+ let journal = Journal::new(dirname)?;
+ let filename = journal.pick_draft(draft)?;
+ journal.edit_draft(editor, &filename)?;
Ok(())
}
-fn finish_draft(dirname: &Path) -> anyhow::Result<()> {
- let drafts = dirname.join("drafts");
- let draft = drafts.join("0.md");
-
- let entries = dirname.join("entries");
- if !entries.exists() {
- std::fs::create_dir(&entries)?;
- }
- let entry = entries.join("0.md");
-
- std::fs::rename(draft, entry)?;
+fn finish_draft(dirname: &Path, draft: &str, basename: &str) -> anyhow::Result<()> {
+ let journal = Journal::new(dirname)?;
+ let filename = journal.pick_draft(draft)?;
+ journal.finish_draft(&filename, basename)?;
Ok(())
}
diff --git a/src/error.rs b/src/error.rs
index 00dae56..232eaec 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -25,4 +25,24 @@ pub enum JournalError {
/// Failed to create the directory for the journal.
#[error("failed to create journal directory {0}")]
CreateDirectory(PathBuf, #[source] std::io::Error),
+
+ /// To many drafts.
+ #[error("there are already {0} drafts in {1}, can't create more")]
+ TooManyDrafts(usize, PathBuf),
+
+ /// User chose a draft that doesn't exist.
+ #[error("No draft {0} in {1}")]
+ NoSuchDraft(String, PathBuf),
+
+ /// Failed to read draft.
+ #[error("failed to drafts {0}: {1}")]
+ ReadDraft(PathBuf, #[source] std::io::Error),
+
+ /// Draft is not UTF8.
+ #[error("draft {0} is not UTF8: {1}")]
+ DraftNotUUtf8(PathBuf, #[source] std::string::FromUtf8Error),
+
+ /// Failed to get metadata for specific file in drafts folder.
+ #[error("failed to stat draft in {0}: {1}")]
+ StatDraft(PathBuf, #[source] std::io::Error),
}
diff --git a/src/journal.rs b/src/journal.rs
new file mode 100644
index 0000000..911f9a3
--- /dev/null
+++ b/src/journal.rs
@@ -0,0 +1,108 @@
+use crate::error::JournalError;
+use chrono::Local;
+use log::debug;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+
+const MAX_DRAFT_COUNT: usize = 1000;
+
+pub struct Journal {
+ dirname: PathBuf,
+}
+
+impl Journal {
+ pub fn is_journal(path: &Path) -> bool {
+ if let Ok(meta) = std::fs::symlink_metadata(path) {
+ meta.is_dir()
+ } else {
+ false
+ }
+ }
+
+ pub fn init(path: &Path) -> Result<Self, JournalError> {
+ std::fs::create_dir(path)
+ .map_err(|err| JournalError::CreateDirectory(path.to_path_buf(), err))?;
+ Ok(Self {
+ dirname: path.to_path_buf(),
+ })
+ }
+
+ pub fn new(path: &Path) -> Result<Self, JournalError> {
+ let dirname = path.to_path_buf();
+ if dirname.exists() {
+ Ok(Self { dirname })
+ } else {
+ Err(JournalError::NotAJournal(dirname.display().to_string()))
+ }
+ }
+
+ fn dirname(&self) -> &Path {
+ &self.dirname
+ }
+
+ fn drafts(&self) -> PathBuf {
+ self.dirname().join("drafts")
+ }
+
+ fn entries(&self) -> PathBuf {
+ self.dirname().join("entries")
+ }
+
+ pub fn new_draft(&self, title: &str, _editor: &str) -> anyhow::Result<()> {
+ let drafts = self.drafts();
+ if !drafts.exists() {
+ std::fs::create_dir(&drafts)?;
+ }
+
+ let pathname = self.pick_file_id(&drafts)?;
+ let text = format!(r#"[[!meta title="{}"]]"#, title);
+ std::fs::write(pathname, format!("{}\n\n", text))?;
+ Ok(())
+ }
+
+ fn pick_file_id(&self, dirname: &Path) -> Result<PathBuf, JournalError> {
+ for i in 0..MAX_DRAFT_COUNT {
+ let basename = format!("{}.md", i);
+ let pathname = dirname.join(basename);
+ if !pathname.exists() {
+ return Ok(pathname);
+ }
+ }
+ return Err(JournalError::TooManyDrafts(
+ MAX_DRAFT_COUNT,
+ dirname.to_path_buf(),
+ ));
+ }
+
+ pub fn pick_draft(&self, id: &str) -> Result<PathBuf, JournalError> {
+ let drafts = self.drafts();
+ let filename = drafts.join(format!("{}.md", id));
+ if filename.exists() {
+ return Ok(filename);
+ } else {
+ return Err(JournalError::NoSuchDraft(id.to_string(), self.drafts()));
+ }
+ }
+
+ pub fn edit_draft(&self, editor: &str, filename: &Path) -> anyhow::Result<()> {
+ debug!("edit_draft: editor={:?}", editor);
+ debug!("edit_draft: filename={:?}", filename);
+ Command::new(editor).arg(filename).status()?;
+ debug!("edit_draft: editor finished");
+ Ok(())
+ }
+
+ pub fn finish_draft(&self, filename: &Path, basename: &str) -> anyhow::Result<()> {
+ let entries = self.entries();
+ if !entries.exists() {
+ std::fs::create_dir(&entries)?;
+ }
+
+ let subdir = entries.join(Local::today().format("%Y/%m/%d").to_string());
+ std::fs::create_dir_all(&subdir)?;
+
+ let entry = subdir.join(basename);
+ std::fs::rename(filename, entry)?;
+ Ok(())
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 523317b..e988c12 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,4 @@
pub mod config;
pub mod error;
+pub mod journal;
pub mod opt;
diff --git a/src/opt.rs b/src/opt.rs
index 12c3d26..df83659 100644
--- a/src/opt.rs
+++ b/src/opt.rs
@@ -63,8 +63,17 @@ pub enum SubCommand {
},
/// Invoke editor on journal entry draft.
- Edit,
+ Edit {
+ /// Draft id.
+ draft: String,
+ },
/// Finish a journal entry draft.
- Finish,
+ Finish {
+ /// Draft id.
+ draft: String,
+
+ /// Set base name of published file.
+ basename: String,
+ },
}
diff --git a/subplot/jt.py b/subplot/jt.py
index 31bceb7..312a22a 100644
--- a/subplot/jt.py
+++ b/subplot/jt.py
@@ -65,17 +65,22 @@ def output_contains(ctx, pattern=None):
def journal_has_no_drafts(ctx, dirname=None):
- assert_eq = globals()["assert_eq"]
- logging.debug(f"checking {dirname} has no drafts")
- drafts = os.path.join(dirname, "drafts")
- assert_eq(_find_files(drafts), [])
+ _journal_has_n_drafts(ctx, 0, dirname=dirname)
def journal_has_one_draft(ctx, dirname=None):
+ _journal_has_n_drafts(ctx, 1, dirname=dirname)
+
+
+def journal_has_two_drafts(ctx, dirname=None):
+ _journal_has_n_drafts(ctx, 2, dirname=dirname)
+
+
+def _journal_has_n_drafts(ctx, n, dirname=None):
assert_eq = globals()["assert_eq"]
- logging.debug(f"checking {dirname} has one draft")
+ logging.debug(f"checking {dirname} has {n} drafts")
drafts = os.path.join(dirname, "drafts")
- assert_eq(len(_find_files(drafts)), 1)
+ assert_eq(len(_find_files(drafts)), n)
def journal_has_no_entries(ctx, dirname=None):
@@ -98,6 +103,18 @@ def journal_has_one_entry(ctx, dirname=None, variable=None):
ctx["variables"] = variables
+def journal_has_two_entries(ctx, dirname=None, variable1=None, variable2=None):
+ assert_eq = globals()["assert_eq"]
+ logging.debug(f"checking {dirname} has two entries")
+ entries = os.path.join(dirname, "entries")
+ files = list(sorted(_find_files(entries)))
+ assert_eq(len(files), 2)
+ variables = ctx.get("variables", {})
+ variables[variable1] = files[0]
+ variables[variable2] = files[1]
+ ctx["variables"] = variables
+
+
def _find_files(root):
if not os.path.exists(root):
return []
diff --git a/subplot/jt.yaml b/subplot/jt.yaml
index 9c0983c..187f436 100644
--- a/subplot/jt.yaml
+++ b/subplot/jt.yaml
@@ -37,6 +37,9 @@
- then: there is one draft in {dirname}
function: journal_has_one_draft
+- then: there are two drafts in {dirname}
+ function: journal_has_two_drafts
+
- then: draft {draftno} in {dirname} contains "{pattern:text}"
function: draft_contains_string
@@ -46,5 +49,8 @@
- then: there is one journal entry in {dirname}, at {variable}
function: journal_has_one_entry
+- then: there are two journal entries in {dirname}, at {variable1} and {variable2}
+ function: journal_has_two_entries
+
- then: journal entry <{variable}> contains "{pattern:text}"
function: file_contains