mirror of
https://codeberg.org/postscriptum/snac2nex.git
synced 2026-03-31 13:15:27 +00:00
init separated format controller, detect content pattern by format_filename extension or trailing slash availability
This commit is contained in:
parent
776e929b01
commit
678f0c6d57
4 changed files with 174 additions and 78 deletions
|
|
@ -50,8 +50,10 @@ snac2nex -s /path/to/snac/storage -t /path/to/nex -u user1 -u user2
|
|||
--format-filename <FORMAT_FILENAME>
|
||||
Post filenames format
|
||||
|
||||
* escaped with `%%`
|
||||
* be careful when removing seconds, as it may cause overwrite collisions
|
||||
* append trailing slash to parse documents as the directory
|
||||
* append `.gmi` or `.gemtext` extension to parse documents as the Gemtext
|
||||
* escape with `%%` when using in `systemd` context
|
||||
* be careful when removing the seconds part, as it may cause overwrite collisions!
|
||||
|
||||
[default: %H-%M-%S]
|
||||
|
||||
|
|
|
|||
|
|
@ -35,8 +35,10 @@ pub struct Config {
|
|||
|
||||
/// Post filenames format
|
||||
///
|
||||
/// * escaped with `%%`
|
||||
/// * be careful when removing seconds, as it may cause overwrite collisions
|
||||
/// * append trailing slash to parse documents as the directory
|
||||
/// * append `.gmi` or `.gemtext` extension to parse documents as the Gemtext
|
||||
/// * escape with `%%` when using in `systemd` context
|
||||
/// * be careful when removing the seconds part, as it may cause overwrite collisions!
|
||||
#[arg(long, default_value_t = String::from("%H-%M-%S"))]
|
||||
pub format_filename: String,
|
||||
|
||||
|
|
|
|||
130
src/nex.rs
130
src/nex.rs
|
|
@ -1,10 +1,12 @@
|
|||
mod attachment;
|
||||
mod format;
|
||||
pub mod response;
|
||||
pub mod source;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use attachment::Attachment;
|
||||
use chrono::{DateTime, Utc};
|
||||
use format::Format;
|
||||
use response::{Clean, Sync, change::Change};
|
||||
use source::Source;
|
||||
use std::{
|
||||
|
|
@ -17,10 +19,9 @@ use std::{
|
|||
pub struct Nex {
|
||||
attachment: Attachment,
|
||||
filename: String,
|
||||
format: Format,
|
||||
is_cleanup: bool,
|
||||
is_debug: bool,
|
||||
pattern: String,
|
||||
time_format: String,
|
||||
users: HashMap<String, PathBuf>,
|
||||
}
|
||||
|
||||
|
|
@ -34,10 +35,6 @@ impl Nex {
|
|||
(is_cleanup, is_debug): (bool, bool),
|
||||
user_names: &Vec<String>,
|
||||
) -> Result<Self> {
|
||||
use std::path::MAIN_SEPARATOR;
|
||||
if filename.contains(MAIN_SEPARATOR) {
|
||||
bail!("Filename pattern should not contain `{MAIN_SEPARATOR}` characters!")
|
||||
}
|
||||
// init data export location
|
||||
let target = PathBuf::from_str(&target_dir)?.canonicalize()?;
|
||||
if !target.is_dir() {
|
||||
|
|
@ -51,14 +48,15 @@ impl Nex {
|
|||
fs::create_dir_all(&p)?;
|
||||
users.insert(u.clone(), p);
|
||||
}
|
||||
// init document format
|
||||
let format = Format::init(&filename, pattern, time_format);
|
||||
|
||||
Ok(Self {
|
||||
attachment: Attachment::init(attachment_mode)?,
|
||||
filename,
|
||||
format,
|
||||
is_cleanup,
|
||||
is_debug,
|
||||
pattern,
|
||||
time_format,
|
||||
users,
|
||||
})
|
||||
}
|
||||
|
|
@ -106,7 +104,9 @@ impl Nex {
|
|||
fs::create_dir_all(&p)?;
|
||||
|
||||
// init shared post ID once
|
||||
let id = published.format(&self.filename).to_string();
|
||||
let id = published
|
||||
.format(self.filename.trim_matches('/'))
|
||||
.to_string();
|
||||
|
||||
// init post filepath
|
||||
let mut f = PathBuf::from(&p);
|
||||
|
|
@ -146,77 +146,59 @@ impl Nex {
|
|||
});
|
||||
}
|
||||
|
||||
// format content pattern
|
||||
let c = self
|
||||
.pattern
|
||||
.replace(
|
||||
"{content}",
|
||||
&if self.pattern.contains("{tags}") {
|
||||
content.replace("#", "")
|
||||
} else {
|
||||
content
|
||||
},
|
||||
)
|
||||
.replace(
|
||||
"{attachments}",
|
||||
&attachments
|
||||
.map(|a| {
|
||||
let mut b = Vec::with_capacity(a.len());
|
||||
b.push("\n".to_string());
|
||||
for (n, (name, media_type, source)) in a.into_iter().enumerate() {
|
||||
let mut t = Vec::with_capacity(3);
|
||||
t.push(format!(
|
||||
"=> {}",
|
||||
match source {
|
||||
Source::Url(url) => url,
|
||||
Source::File(from) => {
|
||||
let to = attachment::filepath(&d, &from, n);
|
||||
let uri = format!(
|
||||
"{}/{}",
|
||||
d.file_name().unwrap().to_string_lossy(),
|
||||
to.file_name().unwrap().to_string_lossy()
|
||||
);
|
||||
self.attachment.sync(&from, &to).unwrap();
|
||||
if let Some(ref mut i) = index {
|
||||
i.push(to);
|
||||
}
|
||||
uri
|
||||
}
|
||||
}
|
||||
));
|
||||
if !name.is_empty() {
|
||||
t.push(name)
|
||||
}
|
||||
if !media_type.is_empty() {
|
||||
t.push(format!("({media_type})"))
|
||||
}
|
||||
b.push(t.join(" "))
|
||||
}
|
||||
b.join("\n")
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.replace(
|
||||
"{tags}",
|
||||
&tags
|
||||
.map(|t| format!("\n\n{}", t.join(", ")))
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.replace("{link}", &format!("\n\n=> {link}"))
|
||||
.replace(
|
||||
"{updated}",
|
||||
&updated
|
||||
.map(|t| format!("\n\n✏ {}", t.format(&self.time_format)))
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
|
||||
// save post file, set its change status
|
||||
let change = if fs::exists(&f)? {
|
||||
Change::Updated
|
||||
} else {
|
||||
Change::Created
|
||||
};
|
||||
fs::write(&f, c)?;
|
||||
fs::write(
|
||||
&f,
|
||||
self.format.to_string(
|
||||
updated,
|
||||
content,
|
||||
link,
|
||||
tags,
|
||||
attachments.map(|a| {
|
||||
let mut b = Vec::with_capacity(a.len());
|
||||
for (n, (name, media_type, source)) in a.into_iter().enumerate() {
|
||||
b.push((
|
||||
match source {
|
||||
Source::Url(url) => url,
|
||||
Source::File(from) => {
|
||||
let to = attachment::filepath(&d, &from, n);
|
||||
let uri = format!(
|
||||
"{}/{}",
|
||||
d.file_name().unwrap().to_string_lossy(),
|
||||
to.file_name().unwrap().to_string_lossy()
|
||||
);
|
||||
self.attachment.sync(&from, &to).unwrap();
|
||||
if let Some(ref mut i) = index {
|
||||
i.push(to);
|
||||
}
|
||||
uri
|
||||
}
|
||||
},
|
||||
{
|
||||
let mut alt = Vec::with_capacity(2);
|
||||
if !name.is_empty() {
|
||||
alt.push(name)
|
||||
}
|
||||
if !media_type.is_empty() {
|
||||
alt.push(format!("({media_type})"))
|
||||
}
|
||||
if alt.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(alt.join(" "))
|
||||
}
|
||||
},
|
||||
))
|
||||
}
|
||||
b
|
||||
}),
|
||||
),
|
||||
)?;
|
||||
|
||||
// move all paths processed to cleanup ignore
|
||||
if let Some(ref mut i) = index {
|
||||
|
|
|
|||
110
src/nex/format.rs
Normal file
110
src/nex/format.rs
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
|
||||
pub enum Format {
|
||||
/// Format as [Gemtext](https://geminiprotocol.net/docs/gemtext.gmi)
|
||||
/// * detected in case, when the `format_filename` option has `.gmi` or `.gemtext` suffix in pattern
|
||||
Gemtext {
|
||||
pattern: String,
|
||||
time_format: String,
|
||||
},
|
||||
/// It is useful to enable clickable links
|
||||
/// * detected in case, when the `format_filename` option has trailing slash in pattern
|
||||
/// * the server should support appending a trailing slash in this case
|
||||
Dir {
|
||||
pattern: String,
|
||||
time_format: String,
|
||||
},
|
||||
/// Ignore markdown
|
||||
Plain {
|
||||
pattern: String,
|
||||
time_format: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Format {
|
||||
pub fn init(filename: &str, pattern: String, time_format: String) -> Self {
|
||||
if filename.ends_with('/') {
|
||||
Format::Dir {
|
||||
pattern,
|
||||
time_format,
|
||||
}
|
||||
} else if filename.ends_with(".gmi") | filename.ends_with(".gemtext") {
|
||||
Format::Gemtext {
|
||||
pattern,
|
||||
time_format,
|
||||
}
|
||||
} else {
|
||||
Format::Plain {
|
||||
pattern,
|
||||
time_format,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_string(
|
||||
&self,
|
||||
updated: Option<DateTime<Utc>>,
|
||||
content: String,
|
||||
link: String,
|
||||
tags: Option<Vec<String>>,
|
||||
attachments: Option<Vec<(String, Option<String>)>>,
|
||||
) -> String {
|
||||
match self {
|
||||
Self::Dir {
|
||||
pattern,
|
||||
time_format,
|
||||
}
|
||||
| Self::Gemtext {
|
||||
pattern,
|
||||
time_format,
|
||||
}
|
||||
| Self::Plain {
|
||||
pattern,
|
||||
time_format,
|
||||
} => pattern
|
||||
.replace(
|
||||
"{content}",
|
||||
&if pattern.contains("{tags}") {
|
||||
content.replace("#", "")
|
||||
} else {
|
||||
content
|
||||
},
|
||||
)
|
||||
.replace(
|
||||
"{attachments}",
|
||||
&attachments
|
||||
.map(|a| {
|
||||
let mut b = Vec::with_capacity(a.len());
|
||||
b.push("\n".to_string());
|
||||
for (link, alt) in a {
|
||||
let mut t = Vec::with_capacity(2);
|
||||
t.push(if matches!(self, Self::Dir { .. }) {
|
||||
format!("=> ../{link}")
|
||||
} else {
|
||||
format!("=> {link}")
|
||||
});
|
||||
if let Some(text) = alt {
|
||||
t.push(text)
|
||||
}
|
||||
b.push(t.join(" "))
|
||||
}
|
||||
b.join("\n")
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.replace(
|
||||
"{tags}",
|
||||
&tags
|
||||
.map(|t| format!("\n\n{}", t.join(", ")))
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.replace("{link}", &format!("\n\n=> {link}"))
|
||||
.replace(
|
||||
"{updated}",
|
||||
&updated
|
||||
.map(|t| format!("\n\n✏ {}", t.format(time_format)))
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
} // @TODO implement separated templates
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue