Last active
November 29, 2024 11:57
-
-
Save hector6872/38a7298510ae23f1b36c954a31949236 to your computer and use it in GitHub Desktop.
Script for auto-creating [[internal links]] for Obsidian π https://forum.obsidian.md/t/script-for-auto-creating-internal-links/67245
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env kscript | |
/* | |
* Copyright (c) 2024. HΓ©ctor de Isidro - hector6872 | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
import java.io.File | |
import java.time.LocalDateTime | |
import java.time.format.DateTimeFormatter | |
import java.util.regex.Pattern | |
import kotlin.system.exitProcess | |
val NAME = "Obsidian-linker" | |
val VERSION = "1.0.0" | |
val LOG_FILE_NAME = "${NAME}-${LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))}.log" | |
fun Array<String>.getNextTo(modifiers: List<String>): String? = if (args.any { modifiers.contains(it) }) { | |
modifiers.forEach { modifier -> | |
val index = args.indexOf(modifier) | |
if (index != -1) { | |
return args.getOrNull(index + 1) | |
} | |
} | |
null | |
} else null | |
fun Boolean.echo(s: String) { | |
println(s) | |
if (this) File(LOG_FILE_NAME).appendText("$s${System.lineSeparator()}") | |
} | |
if (args.isEmpty() or (!args.contains("-vault") and !args.contains("-v"))) { | |
System.err.println(""" | |
Usage: $NAME -vault <folder> -verbose -limit <limit> -no-log -REPLACE | |
!!! Make a backup copy of your vault before using -REPLACE param | |
""") | |
exitProcess(-1) | |
} | |
val folder = args.getNextTo(listOf("-vault", "-v")) ?: "." | |
val replace = args.contains("-REPLACE") or args.contains("-R") | |
val verbose = args.contains("-verbose") | |
val limit = args.getNextTo(listOf("-limit", "-l"))?.toIntOrNull() ?: Int.MAX_VALUE | |
val logging = (args.contains("-no-logs") or args.contains("-nl")).not() | |
logging.echo("$NAME $VERSION") | |
logging.echo("!!! Make a backup copy of your vault before using -REPLACE param${System.lineSeparator()}") | |
if (!File(folder).isDirectory) { | |
logging.echo("$folder not found") | |
exitProcess(-1) | |
} | |
logging.echo("Getting all *.md files from: $folder") | |
val files: List<File> = File(folder).walk().toList() | |
.filter { it.isFile } | |
.filter { it.extension.equals("md", ignoreCase = true) } | |
.sortedByDescending { it.nameWithoutExtension.length } | |
logging.echo("${files.size} *.md files found") | |
if (files.isEmpty()) exitProcess(0) | |
var total = 0 | |
var replaced = 0 | |
var separator = true | |
files.take(limit).forEach { file -> | |
files | |
.filterNot { file == it } | |
.forEach { other -> | |
val linesNumbered = file.useLines { lines -> | |
lines | |
.map { it.replace("\\[([^\\]]+)\\]\\([^)]+\\)".toRegex(), "") } // remove links | |
.map { it.replace("\\[\\[([^\\]]+)\\]\\]".toRegex(), "") }// remove double brackets links | |
.map { it.replace("(```[\\s\\S]*?```)|(`[^`]+`)".toRegex(), "") } // remove inline code and code blocks | |
.mapIndexed { index, line -> line.contains("(?<!#)\\b${Pattern.quote(other.nameWithoutExtension)}\\b".toRegex()) to (index to line) } // avoid tags | |
.filter { (contains, _) -> contains }.map { it.second }.toMap() | |
} | |
if (linesNumbered.isNotEmpty()) { | |
total += linesNumbered.count() | |
if (separator) { | |
logging.echo("\nFile: ${file.name}") | |
separator = false | |
} | |
logging.echo("contains: ${other.nameWithoutExtension}") | |
if (verbose) linesNumbered.forEach { logging.echo("${it.key.toString().padStart(4)}: ${it.value}") } | |
if (replace) { | |
try { | |
val originalLines = file.readLines() | |
val newLines = originalLines.mapIndexed { index, originalLine -> | |
linesNumbered[index]?.let { | |
if (originalLine.contains("\\[([^\\]]+)\\]\\([^)]+\\)".toRegex()) or | |
originalLine.contains("\\[\\[([^\\]]+)\\]\\]".toRegex()) or | |
originalLine.contains("(```[\\s\\S]*?```)|(`[^`]+`)".toRegex())) { | |
logging.echo(">>> Unable to replace `${other.nameWithoutExtension}` on:") | |
logging.echo("${index.toString().padStart(4)}: $originalLine") | |
originalLine | |
} else { | |
replaced++ | |
originalLine.replace("(?<!#)\\b${Pattern.quote(other.nameWithoutExtension)}\\b".toRegex(), "[[${other.nameWithoutExtension}]]") | |
} | |
} ?: originalLine | |
} | |
file.writeText(newLines.joinToString(separator = System.lineSeparator())) | |
} catch (e: Exception) { | |
logging.echo("Something went wrong: ${e.message}") | |
exitProcess(-1) | |
} | |
} | |
} | |
} | |
separator = true | |
} | |
logging.echo("\n$total links found ${if (replace) "and $replaced replaced" else ""}") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
βοΈMake a backup copy of your vault before using the script!