Compare commits

..

15 commits
v1.0.0 ... main

Author SHA1 Message Date
Hugh Rundle 1ac9d03231
Merge pull request #7 from hughrun/patch
fix toml IndexMap compile error
2024-04-20 14:18:31 +10:00
Hugh Rundle 74f2e0756f
fix toml IndexMap compile error
Update deps, see https://github.com/toml-rs/toml/issues/539
2024-04-20 13:46:35 +10:00
Hugh Rundle a6cdf52f7d
Merge pull request #6 from hughrun/patch
improve error handling
fixes #4
2024-04-20 13:09:48 +10:00
Hugh Rundle fc2b43a659
improve error handling
Fixes #4
2024-04-20 13:07:49 +10:00
Hugh Rundle d40b38adc3
Merge pull request #5 from hughrun/patch
update deps
2024-04-20 11:47:57 +10:00
Hugh Rundle 87e2a39a56
update deps
Update chrono and change all deps to tilde versions.
2024-04-20 11:46:11 +10:00
Hugh Rundle e5d7031705 update README with Mojave install instructions 2023-04-12 21:32:09 +10:00
Hugh Rundle cb932edacf fix installation
- simplify script
- add -L flag to install instructions so they ...actually work.
2023-04-02 11:29:36 +10:00
Hugh Rundle c2bb66b514 add to readme 2023-04-02 08:55:41 +10:00
Hugh Rundle 71b92d5900 update README formatting 2023-04-02 08:54:29 +10:00
Hugh Rundle d92a35a027 update README 2023-04-02 08:53:53 +10:00
Hugh Rundle a035b43d9a update README 2023-04-02 08:52:18 +10:00
Hugh Rundle c066383e8a Check remote for latest post
Fixes #2
2023-04-01 20:28:32 +11:00
Hugh Rundle 3f1bd0aded fix sync and improve UI
- sync default should be down, not up!
- add colour to help info
- add message to publish so it doesn't look like it's hanging
- fix spelling errors

fixes #1
fixes #3
2023-04-01 15:42:53 +11:00
Hugh Rundle f946e8a800 update install script
update README
2023-02-26 20:49:56 +11:00
5 changed files with 243 additions and 99 deletions

162
Cargo.lock generated
View file

@ -11,6 +11,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
@ -75,17 +81,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.23"
version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-integer",
"num-traits",
"time",
"wasm-bindgen",
"winapi",
"windows-targets",
]
[[package]]
@ -174,6 +179,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "expanduser"
version = "1.2.2"
@ -193,14 +204,14 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "iana-time-zone"
@ -228,11 +239,11 @@ dependencies = [
[[package]]
name = "indexmap"
version = "1.9.2"
version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [
"autocfg",
"equivalent",
"hashbrown",
]
@ -281,25 +292,6 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "nom8"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
dependencies = [
"memchr",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
@ -417,16 +409,16 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "0.6.1"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
dependencies = [
"serde",
]
[[package]]
name = "soyuz-cli"
version = "1.0.0"
version = "1.0.2"
dependencies = [
"chrono",
"expanduser",
@ -475,22 +467,11 @@ dependencies = [
"syn",
]
[[package]]
name = "time"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]]
name = "toml"
version = "0.7.2"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6"
checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
dependencies = [
"serde",
"serde_spanned",
@ -500,24 +481,24 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.6.1"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.3"
version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6a7712b49e1775fb9a7b998de6635b299237f48b404dde71704f2e0e7f37e5"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
"indexmap",
"nom8",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
@ -538,12 +519,6 @@ version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasm-bindgen"
version = "0.2.84"
@ -628,3 +603,76 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-targets"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]]
name = "winnow"
version = "0.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
dependencies = [
"memchr",
]

View file

@ -1,13 +1,13 @@
[package]
name = "soyuz-cli"
version = "1.0.0"
version = "1.0.2"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chrono = "0.4.23"
expanduser = "1.2.2"
regex = "1.7.1"
serde = { version = "1.0.152", features = ["derive"] }
toml = { version = "0.7.2", features = ["parse"] }
chrono = "~0.4.38"
expanduser = "~1.2.2"
regex = "~1.7.1"
serde = { version = "~1.0.152", features = ["derive"] }
toml = { version = "~0.7.3", features = ["parse"] }

View file

@ -1,23 +1,47 @@
# soyuz-cli
This is the sister application to `soyuz-web`.
This is the sister application to [`soyuz-web`](https://github.com/hughrun/soyuz-web).
`soyuz-cli` can be used alone or on combination with `soyuz-web` to publish and syncronise a gemlog between your local machine and your gemini server.
It is essentially a wrapper around `rsync`. Additionally it will maintain an archive of posts for each year, and the latest 5 posts on your gemlog homepage.
It is mostly a wrapper around `rsync`. Additionally it will maintain an archive of posts for each year, and the latest 5 posts on your gemlog homepage.
## Assumptions
1. You are using MacOS locally and a unix-like OS on the server (it probably works on n*x locally but I haven't tested it)
2. If you have text _under_ the list of latest posts on your homepage, you have at least 5 posts listed already (I could have put in some logic to deal with this but I am lazy)
3. You have `rsync` installed on your local machine
3. You have `rsync` and `ssh` installed on your local machine
4. You have read and write permission for your gemlog files on the server, with the same username as your local machine (otherwise file permissions get weird)
5. You have permission to install files at `/usr/local/bin` on your local machine
6. You publish posts one at a time, so each post will be published prior to the next one being drafted
## Installation
The easiest way to install `soyuz-cli` is using the install script:
The easiest way to install `soyuz-cli` is using the install script. Note that these scripts **will overwrite any previously installed version**.
If you are using a recent version of MacOS, run this command in `Terminal`:
```sh
curl https://hugh.run/install-soyuz | bash
curl -kL https://hugh.run/install-soyuz | bash
```
To install for MacOS Mojave, run this command instead:
```sh
curl -kL https://hugh.run/install-soyuz-mojave | bash
```
Alternatively, you can build from source if you have rust and cargo installed.
## Set up
Once installed, run `soyuz settings` to create a new settings file. The settings file has the following values:
* `local_dir` - the directory on your local machine where your Gemini files are saved
* `remote_dir` - the SSH remote directory path to your files on the server. This includes the server name or ip address, so should look something like `gemini-server:/srv/gemini/example.com` or `username@123.456.789:/srv/gemini/example.com`.
* `editor` - the command to open the text editor you want to use. Defaults to `nano`.
* `index_heading` - the heading text above the latest posts listing on your homepage. If unsure, leave this on the default.
## Commands
Run `soyuz help` for a list of commands and what they do.

View file

@ -1,10 +1,13 @@
#!/bin/sh
# This script installs soyuz-cli
# Download latest binary and save in /usr/local/bin
curl -L https://github.com/hughrun/soyuz-cli/releases/download/latest/MacOS > /usr/local/bin/soyuz
# Download latest binary
wget -q --show-progress https://github.com/hughrun/soyuz-cli/releases/latest/download/MacOS
# mv to /usr/local/bin and rename
mv MacOS /usr/local/bin/soyuz
# make it executable
chmod +x /usr/local/bin/yawp_test
chmod +x /usr/local/bin/soyuz
cat 1>&2 << 'EOM'

View file

@ -9,7 +9,7 @@ use std::path::PathBuf;
use regex::Regex;
use serde::Deserialize;
use std::fs;
use std::process::Command;
use std::process::{Command, Stdio};
use toml;
#[derive(Debug, Deserialize)]
@ -59,31 +59,32 @@ fn create_or_update_homepage(string: String) -> Result<(), std::io::Error> {
}
fn help() {
println!("Союз (Soyuz) - v{}", option_env!("CARGO_PKG_VERSION").unwrap());
println!("A command line program for publishing Gemini posts.\n");
println!(" soyuz ( help | settings | write | publish | sync [ --overwrite | --delete ] )\n");
println!(" help");
println!("\x1B[1;37;41mСоюз (Soyuz) - v{}\x1B[0m", option_env!("CARGO_PKG_VERSION").unwrap());
println!("A command line program for publishing Gemini posts\n\x1B[0m");
println!(" \x1B[1;31mhelp | settings | write | publish | sync [ up | down [ --overwrite | --delete ]]\n\x1B[0m");
println!(" \x1B[1;31mhelp\x1B[0m");
println!(" - shows this help screen\n");
println!(" settings");
println!(" \x1B[1;31msettings\x1B[0m");
println!(" - create or edit the settings file\n");
println!(" write");
println!(" \x1B[1;31mwrite\x1B[0m");
println!(" - create or edit today's gempost using the editor specified in the settings file\n");
println!(" publish");
println!(" - update the homepage and year archive lists of posts, and publish to server.");
println!(" Syncs new or changed files from the server before updating indexes and syncing up.\n");
println!(" sync");
println!(" - synchronise files between local machine and server.");
println!(" \x1B[1;31mpublish\x1B[0m");
println!(" - update the homepage and year archive lists of posts, and publish to server");
println!(" Syncs new or changed files from the server before updating indexes and syncing up\n");
println!(" If you wish to make manual homepage changes you should use 'sync down' first\n");
println!(" \x1B[1;31msync\x1B[0m");
println!(" - synchronise files between local machine and server");
println!(" This is a wrapper around rsync, the default being 'rsync -rtOq'");
println!(" Without arguments sync defaults to 'sync up' which is the equivalent of 'publish'\n");
println!(" sync up");
println!(" - syncronise new or changed files from local machine to server.\n");
println!(" sync down");
println!(" - syncronise new or changed files from server to local machine.\n");
println!(" sync (down | up) --overwrite");
println!(" - sync, and overwrite all files at the destination regardless of last edit date.");
println!(" Without this flag, sync will ignore files at the destination edited more recently than the source.\n");
println!(" sync (down | up) --delete");
println!(" - sync, and delete any files at the destination that do not exist at the source.\n");
println!(" \x1B[1;31msync up\x1B[0m");
println!(" - synchronise new or changed files from local machine to server\n");
println!(" \x1B[1;31msync down\x1B[0m");
println!(" - synchronise new or changed files from server to local machine\n");
println!(" \x1B[1;31msync (down | up) --overwrite\x1B[0m");
println!(" - sync, and overwrite all files at the destination regardless of last edit date");
println!(" Without this flag, sync will ignore files at the destination edited more recently than the source\n");
println!(" \x1B[1;31msync (down | up) --delete\x1B[0m");
println!(" - sync, and delete any files at the destination that do not exist at the source\n");
}
fn open_file(filepath: std::path::PathBuf) {
@ -100,6 +101,7 @@ fn open_file(filepath: std::path::PathBuf) {
}
fn publish() -> Result<(), Box<dyn std::error::Error>> {
println!("Publishing, please wait...");
let config = read_config()?;
// sync down from server (only files that are newer on the server than local)
let mut sync = Command::new("rsync")
@ -138,6 +140,11 @@ fn publish() -> Result<(), Box<dyn std::error::Error>> {
let post = fs::read_to_string(format!("{}/{}", dirpath, &filename))?;
let post_lines: Vec<&str> = post.lines().collect();
let title = post_lines[0].strip_prefix("# ");
if title.is_none() {
println!("ABORTING: Your latest post does not have a title.");
println!("Use a '# Heading' on the first line.");
return Ok(())
}
let entry_string = format!("=> {} {} ({})", filename, sliced, title.unwrap());
let home_entry_string = format!("=> /{}/{} {} ({})", &year, filename, sliced, title.unwrap());
if let Ok(index) = fs::read_to_string(&indexfilepath) {
@ -227,7 +234,7 @@ fn settings() -> Result<(), Box<dyn std::error::Error>> {
fn sync(args: &Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
let config = read_config()?;
let rsync_args = match args.len() {
2 => ["-rtOq", "--update", &config.remote_dir, &config.local_dir],
2 => ["-rtOq", "--update", &config.local_dir, &config.remote_dir],
3 | 4 => {
match args[2].as_str() {
"down" => {
@ -277,19 +284,75 @@ fn sync(args: &Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
}
}
fn write() -> Result<(), Box<dyn std::error::Error>> {
fn write(args: &Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
let config = read_config()?;
let dt = Local::now();
let year = &dt.format("%Y").to_string();
let local_dir: std::path::PathBuf = expanduser(config.local_dir)?;
let local_dir: std::path::PathBuf = expanduser(&config.local_dir)?;
fs::create_dir_all(format!("{}{}", &local_dir.display(), &year))?;
let filepath = format!("{}{}/{}.gmi", local_dir.display(), year, dt.format("%Y-%m-%d"));
match args.len() {
2 => {
let remote_dir: std::path::PathBuf = expanduser(&config.remote_dir)?;
let spath = format!("{}", remote_dir.display());
let remote_vec = spath.split(':').collect::<Vec<_>>();
let server_name = remote_vec[0];
let server_path = remote_vec[1];
let remote_filepath = format!("{}{}/{}.gmi", server_path, year, dt.format("%Y-%m-%d"));
let cmd = format!("[[ -f {} ]] && echo 'true' || echo 'false';", &remote_filepath);
println!("Checking server for latest post...");
let check = Command::new("ssh")
.args(["-q", &server_name, &cmd])
.stdout(Stdio::piped())
.spawn()
.expect("reading from server failed");
let output = check
.wait_with_output()
.expect("something fucked up");
let out = std::str::from_utf8(&output.stdout);
let out2 = out.clone()?.trim();
match out?.trim() {
"true" => {
// user has already published today
println!("\x1B[1;31mYou have already published today!\x1B[0m");
println!("To edit a post published with soyuz-web, run 'soyuz sync down' first.");
println!("To edit a published post already saved locally, use 'soyuz write --edit'");
Ok(())
},
"false" => {
// open a new file
Ok(open_file(filepath.into()))
},
_ => {
println!("Something went wrong checking your gemini server");
println!("Error: {:?}", out2);
Ok(())
}
}
},
3 => {
match args[2].as_str() {
"--edit" => Ok(open_file(filepath.into())),
_ => {
println!("Unknown command. Try 'soyuz help'.");
Ok(())
}
}
},
_ => {
println!("Unknown command. Try 'soyuz help'.");
Ok(())
}
}
}
fn match_single_arg(args: &Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
match args[1].as_str() {
"write" => write(),
"write" => write(args),
"settings" => settings(),
"publish" => publish(),
"sync" => sync(args),
@ -302,7 +365,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>>{
match &args.len() {
1 => Ok(help()),
2 => match_single_arg(&args),
3 | 4 => sync(&args),
3 | 4 => {
match args[1].as_str() {
"sync" => sync(&args),
"write" => write(&args),
&_ => Ok(help())
}
},
_ => Ok(help())
}
}