mirror of
https://github.com/al0cam/AutoMover.git
synced 2026-07-22 05:41:25 +00:00
finished plugin core functions
This commit is contained in:
parent
9b146acfbb
commit
fedc3210a6
3 changed files with 296 additions and 244 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
158
main.ts
|
|
@ -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 !== "",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue