finished plugin core functions

This commit is contained in:
Al0cam 2025-02-10 12:01:16 +01:00
parent 9b146acfbb
commit fedc3210a6
3 changed files with 296 additions and 244 deletions

View file

@ -1,92 +1,133 @@
import { MovingRule } from "Models/MovingRule";
import { App, Notice, TFile, TFolder } from "obsidian";
class MovingUtil {
private static instance: MovingUtil;
private app: App;
private static instance: MovingUtil;
private app: App;
private constructor() {}
private constructor() { }
public static getInstance(): MovingUtil {
if (!MovingUtil.instance) {
MovingUtil.instance = new MovingUtil();
}
return MovingUtil.instance;
}
public static getInstance(): MovingUtil {
if (!MovingUtil.instance) {
MovingUtil.instance = new MovingUtil();
}
return MovingUtil.instance;
}
public init(app: App): void {
this.app = app;
}
public init(app: App): void {
this.app = app;
}
/**
* Checks if the path is a file
*
* @param path - Path to be checked
* @returns boolean
*/
public isFile(path: string): boolean {
const file = this.app.vault.getAbstractFileByPath(path);
return file instanceof TFile;
}
/**
* Checks if the path is a file
*
* @param path - Path to be checked
* @returns boolean
*/
public isFile(path: string): boolean {
const file = this.app.vault.getAbstractFileByPath(path);
return file instanceof TFile;
}
/**
* Checks if the path is a folder
*
* @param path - Path to be checked
* @returns boolean
*/
public isFolder(path: string): boolean {
return this.app.vault.getAbstractFileByPath(path) instanceof TFolder;
}
/**
* Checks if the path is a folder
*
* @param path - Path to be checked
* @returns boolean
*/
public isFolder(path: string): boolean {
return this.app.vault.getAbstractFileByPath(path) instanceof TFolder;
}
/**
* Moves the file to the destination path
*
* @param file - File to be moved
* @param newPath - Destination path
* @returns void
*/
public moveFile(file: TFile, newPath: string): void {
if (this.isFolder(newPath)) {
this.app.vault.rename(file, `${newPath}/${file.name}`);
} else {
new Notice(`Invalid destination path\n${newPath} is not a folder!`, 5000);
console.error(`Invalid destination path\n${newPath} is not a folder!`);
}
}
/**
* Moves the file to the destination path
*
* @param file - File to be moved
* @param newPath - Destination path
* @returns void
*/
public moveFile(file: TFile, newPath: string): void {
if (this.isFolder(newPath)) {
this.app.vault.rename(file, `${newPath}/${file.name}`);
} else {
new Notice(
`Invalid destination path\n${newPath} is not a folder!\nCreating requested folder.`,
5000,
);
console.error(
`Invalid destination path\n${newPath} is not a folder!\nCreating requested folder.`,
);
this.createMissingFolders(newPath).then(() => {
this.app.vault.rename(file, `${newPath}/${file.name}`);
});
}
}
/**
* Moves the file to the destination path
*
* @param folder - Folder to be moved
* @param newPath - Destination path
* @returns void
*/
public moveFolder(folder: TFolder, newPath: string): void {
if (this.isFolder(newPath)) {
this.app.vault.rename(folder, `${newPath}/${folder.name}`);
} else {
new Notice(`Invalid destination path\n${newPath} is not a folder!`, 5000);
console.error(`Invalid destination path\n${newPath} is not a folder!`);
}
}
/**
* Moves the folder to the destination path
*
* @param folder - Folder to be moved
* @param newPath - Destination path
* @returns void
*/
public moveFolder(folder: TFolder, newPath: string): void {
if (this.isFolder(newPath)) {
this.app.vault.rename(folder, `${newPath}/${folder.name}`);
} else {
new Notice(`Invalid destination path\n${newPath} is not a folder!`, 5000);
console.error(`Invalid destination path\n${newPath} is not a folder!`);
}
}
/**
* Creates folder in destination path if it does not exist already
*
* @param path - Path of the folder to be created
* @returns Created folder or null if folder already exists
*/
public createFolder(path: string): TFolder | null {
if (!this.isFolder(path)) {
this.app.vault.createFolder(path).then((folder) => {
return folder;
});
}
new Notice("Folder already exists", 5000);
console.error("Folder already exists");
return null;
}
/**
* Creates folder in destination path if it does not exist already
*
* @param path - Path of the folder to be created
* @returns Created folder or null if folder already exists
*/
public createFolder(path: string): TFolder | null {
if (!this.isFolder(path)) {
this.app.vault.createFolder(path).then((folder) => {
return folder;
});
} else {
new Notice("Folder already exists", 5000);
console.error("Folder already exists");
}
return null;
}
/**
* Splits the path into an array of strings
*
* @param path - Path to be split
* @returns string[]
*/
private splitPath(path: string): string[] {
return path.split("/");
}
/**
* Create missing folders in the path
*
* @param path - Path to be checked
* @returns void
*/
private async createMissingFolders(path: string): Promise<boolean> {
const splitPath = this.splitPath(path);
let currentPath = "";
for (const folder of splitPath) {
currentPath += folder;
if (!this.isFolder(currentPath)) {
await this.app.vault.createFolder(path);
}
currentPath += "/";
}
if (this.isFolder(path)) {
return true;
}
return false;
}
}
const movingUtil = MovingUtil.getInstance();

View file

@ -2,92 +2,123 @@ import type { MovingRule } from "Models/MovingRule";
import type { TFile } from "obsidian";
class RuleMatcherUtil {
private static instance: RuleMatcherUtil;
private static instance: RuleMatcherUtil;
private constructor() {}
private constructor() { }
public static getInstance(): RuleMatcherUtil {
if (!RuleMatcherUtil.instance) {
RuleMatcherUtil.instance = new RuleMatcherUtil();
}
return RuleMatcherUtil.instance;
}
public static getInstance(): RuleMatcherUtil {
if (!RuleMatcherUtil.instance) {
RuleMatcherUtil.instance = new RuleMatcherUtil();
}
return RuleMatcherUtil.instance;
}
/**
* Returns the first rule that matches the file
* If no rule matches, returns null
*
* @param file
* @param rules
* @returns MovingRule | null
*/
public getMatchingRule(file: TFile, rules: MovingRule[]): MovingRule | null {
for(const rule of rules) {
if(rule.regex == null || rule.regex === "") {
console.error("Rule does not have a regex: ", rule);
continue;
} else if(!this.isValidRegex(rule.regex)) {
console.error("Rule has an invalid regex: ", rule);
continue;
}
console.log("Checking if matches: ", file.name.match(rule.regex));
console.log("Rule: ", rule);
if(file.name.match(rule.regex)) {
return rule;
}
}
return null;
}
/**
* Returns the first rule that matches the file
* If no rule matches, returns null
*
* @param file
* @param rules
* @returns MovingRule | null
*/
public getMatchingRule(file: TFile, rules: MovingRule[]): MovingRule | null {
for (const rule of rules) {
if (rule.regex == null || rule.regex === "") {
console.error("Rule does not have a regex: ", rule);
continue;
}
if (!this.isValidRegex(rule.regex)) {
console.error("Rule has an invalid regex: ", rule);
continue;
}
if (file.name.match(rule.regex)) {
return rule;
}
}
return null;
}
/**
* Returns the regex groups of the file that match the rule
* If no groups match, returns an empty array
*
* @param file
* @param rule
* @returns string[]
*/
public getGroupMatches(file: TFile, rule: MovingRule): RegExpMatchArray | null {
const matches = file.name.match(rule.regex);
return matches;
}
/**
* Returns the regex groups of the file that match the rule
* If no groups match, returns an empty array
*
* @param file
* @param rule
* @returns string[]
*/
public getGroupMatches(
file: TFile,
rule: MovingRule,
): RegExpMatchArray | null {
const matches = file.name.match(rule.regex);
return matches;
}
/**
* Checks if the regex pattern is valid
* Meaning, syntax errors are caught, but not logical errors
*
* @param pattern
* @returns boolean
*/
private isValidRegex(pattern: string): boolean {
try {
new RegExp(pattern);
return true;
} catch (e) {
if (e instanceof SyntaxError) {
console.error(`Invalid regex pattern: ${e.message}`);
}
return false;
}
}
/**
* Checks if the regex pattern is valid
* Meaning, syntax errors are caught, but not logical errors
*
* @param pattern
* @returns boolean
*/
private isValidRegex(pattern: string): boolean {
try {
new RegExp(pattern);
return true;
} catch (e) {
if (e instanceof SyntaxError) {
console.error(`Invalid regex pattern: ${e.message}`);
}
return false;
}
}
// TODO: implement the distinction between named and unnamed groups
public constructFinalDesinationPath(rule: MovingRule, matches: RegExpMatchArray): string {
const folderPath = rule.folder;
return folderPath;
}
public constructFinalDesinationPath(
rule: MovingRule,
matches: RegExpMatchArray,
): string {
let folderPath = rule.folder;
const unnamedGroups = this.getUnnamedGroups(rule.regex);
for (let i = 0; i < unnamedGroups!.length; i++) {
folderPath = folderPath.replace(`$${i + 1}`, matches[i + 1] ?? "");
}
return folderPath;
}
private hasUnnamedGroups(pattern: string): boolean {
return /\(/.test(pattern);
}
/**
* Is regex grouped
* Groups are defined by (...) and used with $1, $2, $3, etc.
*
* @param rule
* @returns boolean
*/
public isRegexGrouped(rule: MovingRule): boolean {
return (
this.doesDestinationUseGroups(rule) &&
this.getUnnamedGroups(rule.regex) != null
);
}
private getNamedGroups(pattern: string): RegExpMatchArray | null {
return pattern.match(/\(\?<\w+>/g);
}
/**
* Returns the unnamed groups in the regex pattern
*
* @param pattern
* @returns RegExpMatchArray | null
*/
private getUnnamedGroups(pattern: string): RegExpMatchArray | null {
return pattern.match(/\(/g);
}
private getUnnamedGroups(pattern: string): RegExpMatchArray | null {
return pattern.match(/\(/g);
}
/**
* Does destination use groups
* Groups are defined by $1, $2, $3, etc.
*
* @param rule
* @returns boolean
*/
private doesDestinationUseGroups(rule: MovingRule): boolean {
return /\$\d/.test(rule.folder);
}
}
const ruleMatcherUtil = RuleMatcherUtil.getInstance();

158
main.ts
View file

@ -5,99 +5,79 @@ import movingUtil from "Utils/MovingUtil";
import ruleMatcherUtil from "Utils/RuleMatcherUtil";
export default class AutoMoverPlugin extends Plugin {
settings: AutoMoverSettings;
settings: AutoMoverSettings;
async onload() {
console.log("loading plugin");
movingUtil.init(this.app);
/**
* Loading settings and creating the settings tab
*/
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
this.addSettingTab(new SettingsTab(this.app, this));
console.log(this.settings);
async onload() {
// console.log("loading plugin");
movingUtil.init(this.app);
/**
* Loading settings and creating the settings tab
*/
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
this.addSettingTab(new SettingsTab(this.app, this));
if (
this.checkIfMovingTriggersAreEnabled() &&
this.checkIfThereAreRulesToApply() &&
this.checkIfRulesAreValid()
) {
if (this.settings.moveOnOpen) {
this.registerEvent(
this.app.workspace.on("file-open", (file: TFile) => {
if (file == null || file.path == null) return;
const rule = ruleMatcherUtil.getMatchingRule(
file,
this.settings.movingRules,
);
if (rule == null || rule.folder == null) return;
if (ruleMatcherUtil.isRegexGrouped(rule)) {
const matches = ruleMatcherUtil.getGroupMatches(file, rule);
const finalDestinationPath =
ruleMatcherUtil.constructFinalDesinationPath(rule, matches!);
movingUtil.moveFile(file, finalDestinationPath);
} else {
movingUtil.moveFile(file, rule.folder);
}
}),
);
}
} }
// Statement: check if there are any settings on when to apply the rules,
// because if you arent doing on open, save, close or create, then there is no point in checking for rules
if (
this.checkIfMovingTriggersAreEnabled() &&
this.checkIfThereAreRulesToApply() &&
this.checkIfRulesAreValid()
) {
if (this.settings.moveOnOpen) {
this.registerEvent(
this.app.workspace.on("file-open", (file: TFile) => {
if (file == null || file.path == null) return;
console.log("File opened: ", file.path);
// Question: what if there are multiple rules that apply to the same file?
// so far only the first one is applied and the rest are ignored
// i can live with that
async asyncloadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
// Question: what if the file is already in the correct folder?
// do nothing...
async onunload() {
// console.log("unloading plugin");
}
// TODO: what if the folder which the file is supposed to be moved to does not exist?
// create the whole path to the folder and the folder itself
// having it do nothing would be conflicting with the idea to use regex groups in the first place
/**
* No point in doing anything if there is no trigger set which will cause you to move the files
* @returns boolean
*/
checkIfMovingTriggersAreEnabled(): boolean {
return (
this.settings.moveOnClose ||
this.settings.moveOnCreate ||
this.settings.moveOnOpen ||
this.settings.moveOnSave
);
}
const rule = ruleMatcherUtil.getMatchingRule(
file,
this.settings.movingRules,
);
/**
* If there are no rules to apply, then there is no point in checking for them
* @returns boolean
*/
checkIfThereAreRulesToApply(): boolean {
return this.settings.movingRules.length > 0;
}
if (rule != null) {
// TODO: Construct the folder path using the regex groups
const matches = ruleMatcherUtil.getGroupMatches(file, rule);
console.log("Matches: ", matches);
console.log("Moving file to: ", rule.folder);
// movingUtil.moveFile(file, rule.folder);
}
}),
);
} else {
// TODO: create event listener which happens on file save
// scratched feature
// maybe requires definition of new event and listener
}
}
}
async asyncloadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async onunload() {
console.log("unloading plugin");
}
/**
* No point in doing anything if there is no trigger set which will cause you to move the files
* @returns boolean
*/
checkIfMovingTriggersAreEnabled(): boolean {
return (
this.settings.moveOnClose ||
this.settings.moveOnCreate ||
this.settings.moveOnOpen ||
this.settings.moveOnSave
);
}
/**
* If there are no rules to apply, then there is no point in checking for them
* @returns boolean
*/
checkIfThereAreRulesToApply(): boolean {
return this.settings.movingRules.length > 0;
}
/**
* Superficail check if the rules are valid
* @returns boolean
*/
checkIfRulesAreValid(): boolean {
return this.settings.movingRules.every(
(rule) => rule.regex !== "" && rule.folder !== "",
);
}
/**
* Superficail check if the rules are valid
* @returns boolean
*/
checkIfRulesAreValid(): boolean {
return this.settings.movingRules.every(
(rule) => rule.regex !== "" && rule.folder !== "",
);
}
}