Skip to content

Instantly share code, notes, and snippets.

@hrules6872
Last active September 14, 2023 10:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hrules6872/38a7298510ae23f1b36c954a31949236 to your computer and use it in GitHub Desktop.
Save hrules6872/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
#!/usr/bin/env kscript
/*
* Copyright (c) 2023. HĂ©ctor de Isidro - hrules6872
*
* 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 ""}")
@hrules6872
Copy link
Author

❗️Make a backup copy of your vault before using the script!

@hrules6872
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment