first commit

This commit is contained in:
Hugh Rundle 2023-02-26 17:56:57 +11:00
commit 7d5812e192
6 changed files with 991 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

630
Cargo.lock generated Normal file
View file

@ -0,0 +1,630 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "arrayref"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "blake2b_simd"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
dependencies = [
"arrayref",
"arrayvec",
"constant_time_eq",
]
[[package]]
name = "bumpalo"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
dependencies = [
"iana-time-zone",
"js-sys",
"num-integer",
"num-traits",
"time",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "crossbeam-utils"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
dependencies = [
"cfg-if",
]
[[package]]
name = "cxx"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90d59d9acd2a682b4e40605a242f6670eaa58c5957471cbf85e8aa6a0b97a5e8"
dependencies = [
"cc",
"cxxbridge-flags",
"cxxbridge-macro",
"link-cplusplus",
]
[[package]]
name = "cxx-build"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebfa40bda659dd5c864e65f4c9a2b0aff19bea56b017b9b77c73d3766a453a38"
dependencies = [
"cc",
"codespan-reporting",
"once_cell",
"proc-macro2",
"quote",
"scratch",
"syn",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "457ce6757c5c70dc6ecdbda6925b958aae7f959bda7d8fb9bde889e34a09dc03"
[[package]]
name = "cxxbridge-macro"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebf883b7aacd7b2aeb2a7b338648ee19f57c140d4ee8e52c68979c6b2f7f2263"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "dirs"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "expanduser"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14e0b79235da57db6b6c2beed9af6e5de867d63a973ae3e91910ddc33ba40bc0"
dependencies = [
"dirs",
"lazy_static",
"pwd",
]
[[package]]
name = "getrandom"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "iana-time-zone"
version = "0.1.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
dependencies = [
"cxx",
"cxx-build",
]
[[package]]
name = "indexmap"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "js-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "link-cplusplus"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
dependencies = [
"cc",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]]
name = "proc-macro2"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pwd"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72c71c0c79b9701efe4e1e4b563b2016dd4ee789eb99badcb09d61ac4b92e4a2"
dependencies = [
"libc",
"thiserror",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_users"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
dependencies = [
"getrandom",
"redox_syscall",
"rust-argon2",
]
[[package]]
name = "regex"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "rust-argon2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
dependencies = [
"base64",
"blake2b_simd",
"constant_time_eq",
"crossbeam-utils",
]
[[package]]
name = "scratch"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
[[package]]
name = "serde"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_spanned"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
dependencies = [
"serde",
]
[[package]]
name = "soyuz-cli"
version = "1.0.0"
dependencies = [
"chrono",
"expanduser",
"regex",
"serde",
"toml",
]
[[package]]
name = "syn"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
"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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6a7712b49e1775fb9a7b998de6635b299237f48b404dde71704f2e0e7f37e5"
dependencies = [
"indexmap",
"nom8",
"serde",
"serde_spanned",
"toml_datetime",
]
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "wasi"
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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

13
Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "soyuz-cli"
version = "1.0.0"
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"] }

23
README.md Normal file
View file

@ -0,0 +1,23 @@
# soyuz-cli
This is the sister application to `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.
## 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
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
## Installation
The easiest way to install `soyuz-cli` is using the install script:
```sh
curl https://hugh.run/install-soyuz | bash
```

16
install.sh Executable file
View file

@ -0,0 +1,16 @@
#!/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
# make it executable
chmod +x /usr/local/bin/yawp_test
cat 1>&2 << 'EOM'
🚀 soyuz-cli is now installed!
🔧 Get set up with 'soyuz settings'
For help try 'soyuz help'
EOM

308
src/main.rs Normal file
View file

@ -0,0 +1,308 @@
#![feature(file_create_new)]
#![feature(fs_try_exists)]
#![allow(dead_code)]
use chrono::prelude::*;
use std::env;
use expanduser::expanduser;
use std::path::PathBuf;
use regex::Regex;
use serde::Deserialize;
use std::fs;
use std::process::Command;
use toml;
#[derive(Debug, Deserialize)]
struct Config {
local_dir: String,
remote_dir: String,
editor: String,
index_heading: String
}
fn create_or_update_homepage(string: String) -> Result<(), std::io::Error> {
let config = read_config().expect("cannot read config");
let filepath = format!("{}{}", config.local_dir, "index.gmi");
match fs::try_exists(&filepath) {
Ok(true) => {
let homepage = fs::read_to_string(&filepath)?;
match homepage.find(&string) {
Some(_s) => {
// if it's already listed, do nothing and move on...
},
None => {
let mut lines: Vec<&str> = homepage.lines().collect();
if lines.contains(&config.index_heading.as_str()) {
let i = lines.iter().position(|x| x == &config.index_heading).unwrap(); // ERROR here if the file exists but is empty
let first_entry = format!("\n{}", string);
lines[i + 1] = &first_entry;
lines.remove(i + 6);
fs::write(filepath, lines.join("\n"))?;
} else {
// if the file exists but is empty (or can't find the header) just write out the new entry
// Note: this will overwrite the homepage if it doesn't contain the header string exactly as
// per the config
let contents = format!("{}\n\n{}", config.index_heading, string);
fs::write(&filepath, contents)?;
}
}
}
},
Ok(false) => {
// there's no homepage, create a new one
let contents = format!("{}\n\n{}", config.index_heading, string);
fs::write(&filepath, contents)?;
},
Err(err) => eprintln!("{}", err)
};
Ok(())
}
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!(" - shows this help screen\n");
println!(" settings");
println!(" - create or edit the settings file\n");
println!(" write");
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!(" 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");
}
fn open_file(filepath: std::path::PathBuf) {
let editor = match read_config() {
Ok(config) => config.editor,
Err(_err) => String::from("nano")
};
let mut open = Command::new(&editor)
.arg(&filepath)
.spawn()
.expect(format!("Failed to run {} {:?} command", editor, filepath.into_os_string()).as_str());
let _result = open.wait().expect("problem waiting for editor");
}
fn publish() -> Result<(), Box<dyn std::error::Error>> {
let config = read_config()?;
// sync down from server (only files that are newer on the server than local)
let mut sync = Command::new("rsync")
.args(["-rtOq", "--update", &config.remote_dir, &config.local_dir])
.spawn()
.expect("syncing from server failed");
let _result = sync.wait().expect("problem waiting for rsync");
// check for this year's directory
let dt = Local::now();
let year = &dt.format("%Y").to_string();
let dirpath = format!("{}{}", &config.local_dir, year);
let indexfilepath = format!("{}/{}", &dirpath, "index.gmi");
if let Ok(entries) = fs::read_dir(&dirpath) {
let mut most_recent: (i32, String) = (0,String::new());
for entry in entries {
if let Ok(entry) = entry {
// if filename format yyyy-mm-dd.gmi
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}$").expect("Error creating regex");
let file_string = entry.file_name().into_string().expect("Cannot parse filename");
let ent = match &file_string.strip_suffix(".gmi") {
Some(stripped) => stripped,
None => ""
};
if re.is_match(&ent) {
let allnums = ent.replace("-", "");
let name_as_int: i32 = allnums.parse().unwrap();
if most_recent.0 < name_as_int {
most_recent = (name_as_int, file_string);
}
}
};
};
if most_recent.0 > 0 {
let filename = most_recent.1;
let sliced = &filename.strip_suffix(".gmi").expect("file is not a gemini file!");
let post = fs::read_to_string(format!("{}/{}", dirpath, &filename))?;
let post_lines: Vec<&str> = post.lines().collect();
let title = post_lines[0].strip_prefix("# ");
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) {
// is the file listed in the archive?
match index.find(&filename) {
Some(_i) => {
// the file is already listed in the year index
// check that it's listed on the homepage too
create_or_update_homepage(home_entry_string)?;
},
None => {
let mut lines: Vec<&str> = index.lines().collect();
if lines.len() > 2 {
// there's an archive page, update it
let first_value = format!("{}\n{}", &entry_string, lines[2]);
lines[2] = &first_value;
let new_string = lines.join("\n");
fs::write(indexfilepath, &new_string)?;
} else {
// file is blank, overwrite it
let contents = format!("# {}\n\n{}", &year, &entry_string);
fs::write(indexfilepath, contents)?;
}
// update or create the homepage
create_or_update_homepage(home_entry_string)?;
}
}
} else {
// year archive file doesn't exist, just create it
let contents = format!("# {}\n\n{}", &year, &entry_string);
fs::write(indexfilepath, contents)?;
create_or_update_homepage(home_entry_string)?;
}
} else {
// dir has no files for some reason in
// in this case there's nothing to do
// except sync up any changes from other dirs
}
} else {
// create new directory then try again
// this will create an empty archive if used
// before there are any new posts for the year
fs::create_dir_all(dirpath)?;
publish()?;
}
// rsync to server (no del)
let mut sync_up = Command::new("rsync")
.args(["-rtOq", &config.local_dir, &config.remote_dir])
.spawn()
.expect("syncing to server failed");
let _sync_up_result = sync_up.wait().expect("problem waiting for rsync up");
println!("🎉 published!");
Ok(())
}
fn read_config() -> Result<Config, Box<dyn std::error::Error>> {
let config_path: std::path::PathBuf = expanduser("~/.config/soyuz/config.toml")?;
let vals = fs::read_to_string(config_path)?;
let mut config: Config = toml::from_str(&vals)?;
let local: PathBuf = expanduser(config.local_dir).unwrap();
let remote: PathBuf = expanduser(config.remote_dir).unwrap();
let l_trimmed = local.to_str().unwrap().trim_end_matches("/");
let r_trimmed = remote.to_str().unwrap().trim_end_matches("/");
config.local_dir = format!("{}/", l_trimmed);
config.remote_dir = format!("{}/", r_trimmed);
Ok(config)
}
fn settings() -> Result<(), Box<dyn std::error::Error>> {
let config_dir: std::path::PathBuf = expanduser("~/.config/soyuz")?;
let config_path: std::path::PathBuf = expanduser("~/.config/soyuz/config.toml")?;
fs::create_dir_all(config_dir)?;
let _file = match fs::File::create_new(&config_path) {
Ok(_file) => {
fs::write(&config_path, "local_dir = \nremote_dir = \neditor = \"nano\"\nindex_heading =\"## Latest notes\"\n")
},
Err(_err) => Ok(()) // do nothing, this means the file exists already
};
Ok(open_file(config_path))
}
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],
3 | 4 => {
match args[2].as_str() {
"down" => {
if args.len() == 3 {
["-rtOq", "--update", &config.remote_dir, &config.local_dir]
} else {
match args[3].as_str() {
"--overwrite" => {
["-rtO", "--quiet", &config.remote_dir, &config.local_dir]
},
"--delete" => {
["-rtOq", "--delete", &config.remote_dir, &config.local_dir]
},
_ => ["", "", "", ""],
}
}
},
"up" => {
if args.len() == 3 {
["-rtOq", "--update", &config.local_dir, &config.remote_dir]
} else {
match args[3].as_str() {
"--overwrite" => {
["-rtO", "--quiet", &config.local_dir, &config.remote_dir]
},
"--delete" => {
["-rtOq", "--delete", &config.local_dir, &config.remote_dir]
},
_ => ["", "", "", ""],
}
}
},
_ => ["","","",""],
}
},
_ => ["","","",""],
};
if rsync_args[0] == "" {
Ok(help())
} else {
let mut sync = Command::new("rsync")
.args(rsync_args)
.spawn()
.expect("syncing failed");
let _sync_res = sync.wait().expect("problem waiting for rsync");
Ok(println!("Sync complete."))
}
}
fn write() -> 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)?;
fs::create_dir_all(format!("{}{}", &local_dir.display(), &year))?;
let filepath = format!("{}{}/{}.gmi", local_dir.display(), year, dt.format("%Y-%m-%d"));
Ok(open_file(filepath.into()))
}
fn match_single_arg(args: &Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
match args[1].as_str() {
"write" => write(),
"settings" => settings(),
"publish" => publish(),
"sync" => sync(args),
_ => Ok(help())
}
}
fn main() -> Result<(), Box<dyn std::error::Error>>{
let args: Vec<String> = env::args().collect();
match &args.len() {
1 => Ok(help()),
2 => match_single_arg(&args),
3 | 4 => sync(&args),
_ => Ok(help())
}
}