website: Flesh out docs split.

website: Copy files during build.

website: Allow for mixed env builds.

website: Reduce build size.

website: Expose build.

website: Add build memory debugging.

WIP: Disable broken links check to compare memory usage.

website: Update deps.

website: Clean up API paths.

website: Flesh out 3.8 fixes.

Format.

website: Update ignore paths.

Website: Clean up integrations build.

website: Fix paths.

website: Optimize remark.

website: Update deps.

website: Format.

website: Remove linking.

website: Fix paths.

wip: Attempt API only build.

Prep.

Migrate render to runtime. Tidy sidebar.

Clean up templates.

docs: Move directory. WIP

docs: Flesh out split.

website: Fix issue where routes have collisions.
This commit is contained in:
Teffen Ellis
2025-06-17 21:02:38 +02:00
parent b10c795a26
commit 582812b3ec
704 changed files with 5179 additions and 4670 deletions

1
docs/scripts/docsmg/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

474
docs/scripts/docsmg/Cargo.lock generated Normal file
View File

@ -0,0 +1,474 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
[[package]]
name = "anstyle-parse"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
]
[[package]]
name = "anyhow"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "backtrace"
version = "0.3.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "cc"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaff6f8ce506b9773fa786672d63fc7a191ffea1be33f72bbd4aeacefca9ffc8"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70"
[[package]]
name = "colorchoice"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]]
name = "colored"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
dependencies = [
"lazy_static",
"windows-sys 0.48.0",
]
[[package]]
name = "docsmg"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"colored",
"dotenv",
"regex",
"tokio",
]
[[package]]
name = "dotenv"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "gimli"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
dependencies = [
"adler",
]
[[package]]
name = "object"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce"
dependencies = [
"memchr",
]
[[package]]
name = "pin-project-lite"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[package]]
name = "proc-macro2"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tokio"
version = "1.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
dependencies = [
"backtrace",
"pin-project-lite",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

View File

@ -0,0 +1,14 @@
[package]
name = "docsmg"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.86"
clap = { version = "4.5.9", features = ["derive", "env"] }
colored = "2.1.0"
dotenv = "0.15.0"
regex = "1.10.6"
tokio = "1.38.0"

View File

@ -0,0 +1,68 @@
# Docsmg
This CLI tool is used to generate a mapping file (`migratefile`) that is then used by the tool to migrate `.md`, `.mdx`, and images file from their current structure into a new structure.
Use this migration tool to:
- generate the mapping file with the current structure
- read the completed (manual process to define target structure) and create the directories and move the files.
- modify the internal, cross-reference links to point to new location
- write to the `netlify.toml` file to add redirect entries for all migrated files.
## Steps to install
1. Verify that you have the latest version of rust installed
- Install [rust](rustup.rs) or update rust to the latest version with `rustup update`
- If installing rust from scratch, you may need to run `. $HOME/.cargo/env`
2. Install the cli tool with `cargo install --git https://github.com/goauthentik/authentik --bin docsmg`
3. In the `/docs` directory, create a file named `docsmg.env` with the entry of `MIGRATE_PATH=./docs`.
## Steps to use
1. Generate a migratefile with `docsmg generate >> migratefile`
2. Find the files you want to move in `migratefile` and insert the path you want to move them to after the arrow; ex `path/to/move/from/file.md -> path/to/move/to/file.md` Note: make sure to put spaces on either side of the arrow or that line won't be recognized
3. Once you have entered all the paths you want to move, migrate the files with `docsmg migrate`
4. To revert the migration, use `docsmg unmigrate`; Note: DO NOT edit the migrate file in between steps 3 and 4
5. Repeat steps 2-4 until you are satisfied with the result
### Create the mapping file (`migratefile`)
1. Navigate to the `authentik/docs` dir.
2. Generate a migratefile with `docsmg generate | sort >> migratefile`.
You can also just run `docsmg generate | sort` to see the output in the terminal, before writing it to a file.
:::info The new `migratefile` will be created in the `/docs` dir.
:::
3. Edit the `migratefile` to add the target directory paths in the new structure for each entry.
For each file listed in `migratefile` insert the path you want to move them to.
EXAMPLE: `path/to/move/from/file.md -> path/to/move/to/file.md`
Note: make sure to put spaces on either side of the arrow or that line won't be recognized.
### Migrate the docs
1. After you have entered all the paths you want to move, migrate the files with the command `docsmg migrate`.
:::info
After you have run `migrate`, you cannot run it again or you will get a `panic` error... because the files have already been moved.
:::
2. To revert the migration, use `docsmg unmigrate`.
Note: DO NOT edit the `migratefile` file before running `unmigrate`.
3. Continue modifying the `migratefile` file and then using the `docsmg migrate` command until you are satisfied with the result.
### Update the `sidebar.js file`
Because the structure is completely changed, you will need to modify/reconstruct the navigation bar.
## Test the results
To test the internal links, navigate up a level to `authentik` and then run `make docs-watch`.
## Troubleshooting
- If the `docsmg generate` command pulls _all_ of the files in the repo (even non-docs files), then check that:
- the `docsmg.env` exists
- that it is in `/docs`
- the content is `MIGRATE_PATH=./docs`

5
docs/scripts/docsmg/install.sh Executable file
View File

@ -0,0 +1,5 @@
cargo install --git https://github.com/goauthentik/authentik
sudo wget https://raw.githubusercontent.com/goauthentik/authentik/main/docs/topics/scripts/docsmg/m.bash -O /bin/map
sudo wget https://raw.githubusercontent.com/goauthentik/authentik/main/docs/topics/scripts/docsmg/mcomplete.bash -O /bin/mapcomplete
sudo chmod 755 /bin/map
echo "source mapcomplete" >> ~/.zshrc

2
docs/scripts/docsmg/m.bash Executable file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env bash
docsmg move $1 $2 | tee -a migratefile

View File

@ -0,0 +1,21 @@
_completions () {
if [[ -z "${MIGRATE_PATH}" ]];
then MIGRATE_PATH="./";
else MIGRATE_PATH="${MIGRATE_PATH}"; fi
if [[ -z "${TMP_STRUCTURE_PATH}" ]];
then TMP_STRUCTURE_PATH="./tmp/";
else TMP_STRUCTURE_PATH="${TMP_STRUCTURE_PATH}"; fi
if [[ $1 = $3 ]];
then LSPATH="$MIGRATE_PATH";
else LSPATH="$TMP_STRUCTURE_PATH"; fi
for i in $(compgen -f -- "$LSPATH$2" | cut -d "/" -f 2-); do
if [[ -d "$LSPATH$i" ]];
then COMPREPLY+=("$i/");
else COMPREPLY+=("$i"); fi
done
}
complete -o nospace -o filenames -F _completions map

View File

@ -0,0 +1,31 @@
use std::path::PathBuf;
use crate::{migratefile::read_migrate_file_left_side, recurse_directory};
pub fn generate(migratefile: Option<PathBuf>, migrate_path: PathBuf) {
// if there is a migrate file, read it and get the paths from the left side
let paths: Vec<PathBuf> = match migratefile {
Some(i) => {
let contents = read_migrate_file_left_side(i);
if let Ok(contents) = contents {
contents
} else {
vec![]
}
}
None => {
vec![]
}
};
// get rid of paths already in the specified migrate file
let paths: Vec<PathBuf> = recurse_directory(migrate_path.clone())
.iter()
.filter_map(|x| x.strip_prefix(migrate_path.clone()).ok())
.filter(|x| !paths.contains(&x.to_path_buf()))
.map(|x| x.to_path_buf())
.collect();
for path in paths {
println!("{} ->", path.display());
}
}

View File

@ -0,0 +1,24 @@
use std::{ffi::OsStr, fs::{read_to_string, write}, path::PathBuf};
use crate::recurse_directory;
pub fn add_extra_dot_dot_to_expression_mdx(migrate_path: PathBuf) {
let binding = recurse_directory(migrate_path);
let files = binding.iter().filter(|x| if let Some(i) = x.file_name() {
if Some("expression.mdx") == i.to_str() || Some("expressions.md") == i.to_str() {
true
} else {
false
}
} else {
false
});
for file in files {
let content = match read_to_string(file) {
Ok(i) => i,
_ => continue,
};
let _ = write(file, content.replace("../expressions", "../../expressions"));
}
}

View File

@ -0,0 +1,34 @@
use std::{fs::read_to_string, path::PathBuf};
use regex::{Captures, Regex};
use crate::recurse_directory;
pub fn shorten_all_external_links(migrate_path: PathBuf) {
let files = recurse_directory(migrate_path.clone());
for file in files {
let file = migrate_path.join(file);
let absolute_file = file.clone().canonicalize().unwrap();
let contents = if let Ok(x) = read_to_string(file) {
x
} else {
continue;
};
let re = Regex::new(r"\[(?<name>.*)\]\((?<link>.*)\)").unwrap();
let captures: Vec<Captures> = re.captures_iter(&contents).collect();
for capture in captures {
let link = &capture["link"];
let link = PathBuf::from(link);
let absolute_link = absolute_file
.clone()
.parent()
.unwrap()
.join(link)
.canonicalize()
.unwrap();
shorten_link_relative_to(absolute_link.clone(), absolute_file.clone());
}
}
}
fn shorten_link_relative_to(link_to_shorten: PathBuf, relative_to: PathBuf) {}

View File

@ -0,0 +1,86 @@
use std::{fs, path::PathBuf};
use clap::{Parser, Subcommand};
mod generate;
mod links;
mod migrate;
mod migratefile;
mod r#move;
mod hackyfixes;
#[derive(Parser)]
struct Cli {
#[arg(long, env, default_value = "./")]
migrate_path: PathBuf,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Move {
old_path: PathBuf,
new_path: PathBuf,
},
Migrate {
#[arg(long, name = "FILE", default_value = "./migratefile")]
migratefile: PathBuf,
#[arg(short, long)]
quiet: bool,
},
Unmigrate {
#[arg(long, name = "FILE", default_value = "./migratefile")]
migratefile: PathBuf,
#[arg(short, long)]
quiet: bool,
},
Generate {
#[arg(long, name = "FILE")]
migratefile: Option<PathBuf>,
},
}
fn main() {
let _ = dotenv::from_filename("./docsmg.env");
let cli = Cli::parse();
match cli.command {
Commands::Move { old_path, new_path } => r#move::r#move(old_path, new_path),
Commands::Migrate { migratefile, quiet } => {
migrate::migrate(quiet, migratefile, cli.migrate_path)
}
Commands::Unmigrate { migratefile, quiet } => {
migrate::unmigrate(quiet, migratefile, cli.migrate_path)
}
Commands::Generate { migratefile } => generate::generate(migratefile, cli.migrate_path),
}
}
fn recurse_directory(path: PathBuf) -> Vec<PathBuf> {
let paths = fs::read_dir(path).expect("path to exist");
let mut final_paths = vec![];
for path in paths {
match path {
Ok(path) => {
if !path.path().is_file() && !path.path().is_dir() {
continue;
} // dont go any further if not a file or directory
let is_dir = path.path().is_dir();
let path = path.path();
if is_dir {
let mut paths = recurse_directory(path);
final_paths.append(&mut paths);
} else {
final_paths.push(path);
}
}
_ => {}
}
}
final_paths
}

View File

@ -0,0 +1,441 @@
use std::{
collections::{HashMap, HashSet, VecDeque}, env::consts::OS, ffi::OsStr, fs::{create_dir_all, read_to_string, remove_file, write, File}, path::{Component, PathBuf}, process::Command
};
use colored::Colorize;
use regex::{Captures, Regex};
use crate::{hackyfixes::add_extra_dot_dot_to_expression_mdx, migratefile::read_migrate_file, recurse_directory, Cli};
pub fn migrate(quiet: bool, migratefile: PathBuf, migrate_path: PathBuf) {
if !quiet {
println!("Reading migrate file");
}
let files = read_migrate_file(migratefile);
let files = match files {
Ok(i) => {
if !quiet {
println!("{}", "Success".green());
}
i
}
Err(_) => {
println!("{}: Could not read migrate file", "Error".red());
return;
}
};
replace_links(migrate_path.clone(), files.clone());
let successful_moves = move_files(quiet, migrate_path.clone(), files);
add_redirects(successful_moves.clone(), migrate_path.clone());
//shorten_all_external_links(migrate_path);
add_extra_dot_dot_to_expression_mdx(migrate_path.clone());
let _ = Command::new("sh")
.arg("-c")
.arg("find . -empty -type d -delete")
.current_dir(migrate_path)
.output();
}
pub fn unmigrate(quiet: bool, migratefile: PathBuf, migrate_path: PathBuf) {
if !quiet {
println!("Reading migrate file");
}
let files = read_migrate_file(migratefile);
let files = match files {
Ok(i) => {
if !quiet {
println!("{}", "Success".green());
}
i
}
Err(_) => {
println!("{}: Could not read migrate file", "Error".red());
return;
}
};
let files: Vec<(PathBuf, PathBuf)> = files.iter().map(|x| (x.1.clone(), x.0.clone())).collect(); //switch files to reverse a migration
replace_links(migrate_path.clone(), files.clone());
let successful_moves = move_files(quiet, migrate_path.clone(), files);
let successful_moves: Vec<(PathBuf, PathBuf)> = successful_moves
.iter()
.map(|x| (x.1.clone(), x.0.clone()))
.collect(); //switch files to reverse a migration
remove_redirects(successful_moves, migrate_path.clone());
//shorten_all_external_links(migrate_path);
}
fn move_files(
quiet: bool,
migrate_path: PathBuf,
files: Vec<(PathBuf, PathBuf)>,
) -> Vec<(PathBuf, PathBuf)> {
let mut successful_moves = vec![];
for file in files {
if !quiet {
print!("{} -> {} : ", file.0.display(), file.1.display());
}
let rename: anyhow::Result<()> = (|| {
let old_file = migrate_path.join(&file.0);
let new_file = migrate_path.join(&file.1);
std::fs::create_dir_all(&new_file.parent().expect("files to have a parent"))?;
std::fs::rename(&old_file, &new_file)?;
Ok(())
})();
match rename {
Ok(_) => {
if !quiet {
println!("{}", "Success".green());
}
successful_moves.push(file);
}
Err(_) => println!(
"{}: Could not move file {}",
"Error".red(),
file.0.display()
),
};
}
successful_moves
}
fn replace_links(migrate_path: PathBuf, moves: Vec<(PathBuf, PathBuf)>) {
let files = recurse_directory(migrate_path.clone());
let mut moved = HashSet::new();
let mut absolute_moves = vec![];
for r#move in &moves {
let r#move = (
migrate_path.join(r#move.0.clone()),
migrate_path.join(r#move.1.clone()),
);
let absolute_move_0 = r#move
.0
.canonicalize()
.expect(&format!("{}", r#move.0.display()));
let _ = create_dir_all(r#move.1.parent().unwrap());
let tmp_file = File::create_new(&r#move.1);
let absolute_move_1 = r#move.1.clone().canonicalize().expect(&format!(
"{} {:?}",
r#move.1.display(),
tmp_file
));
// delete file if it didn't already exist
if let Ok(_) = tmp_file {
let _ = remove_file(&r#move.1);
};
absolute_moves.push((absolute_move_0, absolute_move_1));
}
let absolute_moves = absolute_moves
.iter()
.map(|x| x.clone())
.collect::<HashMap<PathBuf, PathBuf>>();
for file in files {
let absolute_file = file.canonicalize().unwrap();
println!("{}", absolute_file.display());
let mut contents = match read_to_string(file.clone()) {
Ok(i) => i,
Err(_) => continue,
};
// replace old absolute file with the new absolute file
let old_absolute_file = absolute_file.clone();
let absolute_file = match absolute_moves.get(&absolute_file) {
Some(file) => {
println!(" new file: {}", file.display());
moved.insert(absolute_file);
file.clone()
}
None => absolute_file.clone(),
};
// get all links in file and remove web links and link to self
let re = Regex::new(r"\[(?<name>[\w \-\*'`]*)\]\((?<link>[\w\-\\/\\.#]*)\)").unwrap();
let tmp_contents = contents.clone();
let captures: Vec<Captures> = re
.captures_iter(&tmp_contents)
.filter(|x| {
let link = &x["link"];
!["http", "#", "/"]
.iter()
.fold(false, |acc, x| acc || link.starts_with(x))
})
.collect();
println!(" captures: {}\n", captures.len());
for capture in captures {
let mut capture_log = String::new();
let link = capture["link"].to_owned();
let link_path;
let link_postfix_index = link.find('#');
let link_postfix = match link_postfix_index {
Some(i) => {
let link_postfix = link[i..].to_owned();
link_path = link[..i].to_owned();
Some(link_postfix)
}
None => {
link_path = link.clone();
None
},
};
let absolute_link = old_absolute_file.parent().unwrap().join(link_path.clone());
//let _ = create_dir_all(absolute_link.parent().unwrap());
//let tmp_file = File::create_new(&absolute_link);
let absolute_link = match absolute_link
.canonicalize()
.or(absolute_link.with_extension("md").canonicalize())
.or(absolute_link.with_extension("mdx").canonicalize())
{
Ok(link) => link,
_ => {
println!(
" {}: {} -> {}",
"failed".red(),
absolute_file.to_string_lossy().to_string().red(),
absolute_link.to_string_lossy().to_string().red()
);
continue;
}
};
let absolute_link = if absolute_link.is_file() {
absolute_link
} else if absolute_link.join("index.md").is_file() {
absolute_link.join("index.md")
} else if absolute_link.join("index.mdx").is_file() {
absolute_link.join("index.mdx")
} else {
println!(
" {}: {} -> {}",
"failed".red(),
absolute_file.to_string_lossy().to_string().red(),
absolute_link.to_string_lossy().to_string().red()
);
continue;
};
// delete file if it didn't already exist
//if let Ok(_) = tmp_file {
// let _ = remove_file(&absolute_link);
//};
capture_log.push_str(&format!(" oldalink: {}\n", absolute_link.display()));
// replace old absolute link with the new absolute link
let absolute_link = match absolute_moves.get(&absolute_link) {
Some(link) => link.clone(),
None => absolute_link.clone(),
};
capture_log.push_str(&format!(" newalink: {}\n", absolute_link.display()));
// create tmp absolutes and make them into components
let tmp_absolute_file = absolute_file.clone();
let mut tmp_absolute_file = tmp_absolute_file.components().collect::<VecDeque<_>>();
let tmp_absolute_link = absolute_link.clone();
let mut tmp_absolute_link = tmp_absolute_link.components().collect::<VecDeque<_>>();
// remove the shared path components
loop {
if tmp_absolute_file.front() != tmp_absolute_link.front()
|| tmp_absolute_file.front() == None
{
break;
}
tmp_absolute_file.pop_front();
tmp_absolute_link.pop_front();
}
capture_log.push_str(&format!(
" shrtfile: {}\n",
tmp_absolute_file.iter().collect::<PathBuf>().display()
));
capture_log.push_str(&format!(
" shrtlink: {}\n",
tmp_absolute_link.iter().collect::<PathBuf>().display()
));
if tmp_absolute_file.len() <= 0 {
println!(
" {}: {} -> {}",
"failed".red(),
absolute_file.to_string_lossy().to_string().red(),
absolute_link.to_string_lossy().to_string().red()
);
continue;
}
let escapes = (0..tmp_absolute_file.len() - 1)
.map(|_| Component::Normal("..".as_ref()))
.collect::<PathBuf>();
let new_link = escapes.join(tmp_absolute_link.iter().collect::<PathBuf>());
// add a . to the beginning if it doesn't already start with . or ..
let new_link = match new_link
.components()
.collect::<Vec<_>>()
.first()
.iter()
.collect::<PathBuf>()
.to_str()
{
Some(".") => new_link,
Some("..") => new_link,
_ => PathBuf::from(".").join(new_link),
};
let mut new_link = new_link.to_string_lossy().to_string();
match link_postfix {
Some(i) => new_link.push_str(&i),
None => {}
}
capture_log.push_str(&format!(" old link: {}\n", link));
capture_log.push_str(&format!(" new link: {}\n", new_link));
print!("{}", capture_log);
//println!("{} {} {}", absolute_file.display(), absolute_link.display(), new_link.display());
let tmp_contents = contents.replace(&format!("({})", link), &format!("({})", new_link));
if tmp_contents == contents {
println!("{}", " nothing replaced".yellow());
} else {
contents = tmp_contents;
};
println!("");
}
write(file, contents).unwrap();
}
}
fn fix_internal_links_in_file(migrate_path: PathBuf, move_from: PathBuf, move_to: PathBuf) {
let move_from = migrate_path.join(move_from);
let move_to = migrate_path.join(move_to);
let contents = read_to_string(&move_from);
let mut contents = match contents {
Ok(ok) => ok,
Err(_) => return,
};
let re = Regex::new(r"\[(?<name>.*)\]\((?<link>.*)\)").unwrap();
let captures: Vec<Captures> = re.captures_iter(&contents).collect();
let mut changes = vec![];
for capture in captures {
//let name = &capture["name"];
let link = &capture["link"];
if link.starts_with('#') || link.starts_with("http") {
continue;
}
let link = PathBuf::from(link);
//println!("{} {}", move_from.display(), link.display());
let absolute_link = move_from
.parent()
.unwrap()
.canonicalize()
.unwrap()
.join(&link);
if move_to.components().collect::<Vec<_>>().len() > 1 {
let _ = create_dir_all(move_to.parent().unwrap());
}
let tmp_file = File::create_new(move_to.clone());
//println!("{} {} {} {}", name, link.display(), absolute_link.display(), make_path_relative(absolute_link.clone(), move_to.canonicalize().unwrap().clone()).display());
let new_link = make_path_relative(
absolute_link.clone(),
move_to.canonicalize().unwrap().clone(),
);
if let Ok(_) = tmp_file {
remove_file(move_to.clone()).unwrap()
};
changes.push((link.clone(), new_link.clone()));
}
for i in changes {
contents = contents.replace(
&format!("({})", i.0.display()),
&format!("({})", i.1.display()),
);
}
write(move_from, contents).unwrap();
}
fn make_path_relative(path: PathBuf, relative_to: PathBuf) -> PathBuf {
let mut subdirs = 0;
let path_components = path.components().collect::<Vec<_>>();
let relative_to_components = relative_to.components().collect::<Vec<_>>();
loop {
if path_components.len() <= subdirs {
break;
} else if path_components[subdirs] != relative_to_components[subdirs] {
break;
}
subdirs += 1;
}
let new_path = &path_components[subdirs..].iter().collect::<PathBuf>();
let backouts = (0..relative_to_components.len() - subdirs - 1)
.map(|_| PathBuf::from(".."))
.reduce(|acc, e| acc.join(e))
.unwrap_or(PathBuf::from(""));
//println!("{}, {}", relative_to_components.len() - subdirs - 1, backouts.display());
let new_path = backouts.join(new_path);
let new_path = if new_path
.to_string_lossy()
.to_string()
.chars()
.next()
.unwrap()
!= '.'
{
PathBuf::from(".").join(new_path)
} else {
new_path
};
let new_path = if new_path.file_name() == Some(OsStr::new("index.md"))
|| new_path.file_name() == Some(OsStr::new("index.mdx"))
{
new_path.parent().unwrap().to_path_buf()
} else {
new_path
};
new_path
}
fn add_redirects(successful_moves: Vec<(PathBuf, PathBuf)>, migrate_path: PathBuf) {
let redirects = generate_redirects(successful_moves);
let netlify_path = migrate_path.parent().unwrap().join("netlify.toml");
let mut netlify_contents = read_to_string(netlify_path.clone()).unwrap();
for redirect in redirects {
netlify_contents.push_str(&redirect);
}
std::fs::write(netlify_path, netlify_contents).unwrap();
}
fn remove_redirects(successful_moves: Vec<(PathBuf, PathBuf)>, migrate_path: PathBuf) {
let redirects = generate_redirects(successful_moves);
let netlify_path = migrate_path.parent().unwrap().join("netlify.toml");
let mut netlify_contents = read_to_string(netlify_path.clone()).unwrap();
for redirect in redirects {
netlify_contents = netlify_contents.replace(&redirect, "");
}
std::fs::write(netlify_path, netlify_contents).unwrap();
}
fn generate_redirects(successful_moves: Vec<(PathBuf, PathBuf)>) -> Vec<String> {
successful_moves
.iter()
.map(|x| {
format!(
"
[[redirects]]
from = \"{}\"
to = \"{}\"
status = 301
force = true
",
x.0.display(),
x.1.display()
)
})
.collect()
}

View File

@ -0,0 +1,36 @@
use std::{fs::read_to_string, path::PathBuf};
pub fn read_migrate_file(file: PathBuf) -> anyhow::Result<Vec<(PathBuf, PathBuf)>> {
let contents = read_to_string(file)?;
let lines: Vec<String> = contents
.split('\n')
.map(|x| x.to_owned())
.filter(|x| x != "")
.collect();
let migrations = lines
.iter()
.filter_map(|x| x.split_once(" -> "))
.filter(|x| !(x.0 == x.1))
.map(|x| {
(
x.0.parse().expect("a valid path"),
x.1.parse().expect("a valid path"),
)
})
.collect::<Vec<_>>();
Ok(migrations)
}
pub fn read_migrate_file_left_side(file: PathBuf) -> anyhow::Result<Vec<PathBuf>> {
let contents = read_to_string(file)?;
let lines: Vec<String> = contents
.split('\n')
.map(|x| x.to_owned())
.filter(|x| x != "")
.collect();
let migrations = lines
.iter()
.map(|x| x.split(" -> ").collect::<Vec<&str>>()[0].into())
.collect::<Vec<_>>();
Ok(migrations)
}

View File

@ -0,0 +1,23 @@
use std::path::PathBuf;
use crate::recurse_directory;
pub fn r#move(old_path: PathBuf, new_path: PathBuf) {
let is_dir = old_path.is_dir();
if is_dir {
let paths = recurse_directory(old_path.clone());
for path in paths {
let raw_path = path
.strip_prefix(old_path.clone())
.expect("path to be within old path");
let new_path = new_path.join(raw_path);
println!("{} -> {}", path.display(), new_path.display());
}
} else {
println!(
"{} -> {}",
old_path.to_string_lossy(),
new_path.to_string_lossy()
);
}
}