Created
March 21, 2025 07:27
-
-
Save jdheyburn/f64effd0b606a68044d7d28960becc77 to your computer and use it in GitHub Desktop.
Scripts to help move Obsidian journal notes from flat directory structure to nested.
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
import os | |
import re | |
import shutil | |
from pathlib import Path | |
# Get the script directory and define the daily directory path | |
SCRIPT_DIR = Path(__file__).parent | |
DAILY_DIR = SCRIPT_DIR.parent / "planner" / "daily" | |
# Function to ensure a directory exists | |
def ensure_directory_exists(dir_path): | |
if not dir_path.exists(): | |
dir_path.mkdir(parents=True) | |
print(f"Created directory: {dir_path}") | |
# Function to get user confirmation | |
def confirm_action(message): | |
while True: | |
response = input(f"{message} (y/n/all/q): ").lower() | |
if response in ['y', 'yes']: | |
return True | |
elif response in ['n', 'no']: | |
return False | |
elif response == 'all': | |
return 'all' | |
elif response in ['q', 'quit']: | |
print("Operation cancelled.") | |
exit(0) | |
else: | |
print("Please respond with 'y' (yes), 'n' (no), 'all' (yes to all), or 'q' (quit)") | |
# Main function to organize daily files | |
def organize_daily_files_by_year_month(): | |
print("Starting organization of daily files by year and month...") | |
print("Options for each file:") | |
print(" y or yes: Move this file") | |
print(" n or no: Skip this file") | |
print(" all: Move all remaining files without asking") | |
print(" q or quit: Exit the script") | |
# Ensure the daily directory exists | |
ensure_directory_exists(DAILY_DIR) | |
# Track if user has chosen to move all files | |
move_all = False | |
# Get all valid files and sort them chronologically | |
valid_files = [] | |
for filename in os.listdir(DAILY_DIR): | |
file_path = DAILY_DIR / filename | |
# Skip directories and non-markdown files | |
if file_path.is_dir() or not filename.endswith('.md'): | |
continue | |
# Extract year, month, and day from filename (YYYY-MM-DD format) | |
match = re.match(r'^(\d{4})-(\d{2})-(\d{2})\.md$', filename) | |
if not match: | |
print(f"Skipping file with unexpected format: {filename}") | |
continue | |
year = match.group(1) | |
month = match.group(2) | |
day = match.group(3) | |
valid_files.append((filename, year, month, day, file_path)) | |
# Sort files by year, month, and day | |
valid_files.sort(key=lambda x: (x[1], x[2], x[3])) | |
# Process files in chronological order | |
for filename, year, month, day, file_path in valid_files: | |
# Create year and month directories if they don't exist | |
year_dir = DAILY_DIR / year | |
ensure_directory_exists(year_dir) | |
month_dir = year_dir / month | |
ensure_directory_exists(month_dir) | |
# Move file to year/month directory | |
new_file_path = month_dir / filename | |
# Ask for confirmation if not already moving all | |
if not move_all: | |
confirmation = confirm_action(f"Move {filename} to {year}/{month}/ directory?") | |
if confirmation == 'all': | |
move_all = True | |
elif not confirmation: | |
print(f"Skipped {filename}") | |
continue | |
shutil.move(file_path, new_file_path) | |
print(f"Moved {filename} to {year}/{month}/ directory") | |
print("Organization complete!") | |
# Run the organization function | |
if __name__ == "__main__": | |
organize_daily_files_by_year_month() |
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
import os | |
import re | |
import shutil | |
from pathlib import Path | |
# Get the script directory and define the monthly directory path | |
SCRIPT_DIR = Path(__file__).parent | |
MONTHLY_DIR = SCRIPT_DIR.parent / "planner" / "monthly" | |
# Function to ensure a directory exists | |
def ensure_directory_exists(dir_path): | |
if not dir_path.exists(): | |
dir_path.mkdir(parents=True) | |
print(f"Created directory: {dir_path}") | |
# Function to get user confirmation | |
def confirm_action(message): | |
while True: | |
response = input(f"{message} (y/n/all/q): ").lower() | |
if response in ['y', 'yes']: | |
return True | |
elif response in ['n', 'no']: | |
return False | |
elif response == 'all': | |
return 'all' | |
elif response in ['q', 'quit']: | |
print("Operation cancelled.") | |
exit(0) | |
else: | |
print("Please respond with 'y' (yes), 'n' (no), 'all' (yes to all), or 'q' (quit)") | |
# Main function to organize monthly files | |
def organize_monthly_files_by_year(): | |
print("Starting organization of monthly files by year...") | |
print("Options for each file:") | |
print(" y or yes: Move this file") | |
print(" n or no: Skip this file") | |
print(" all: Move all remaining files without asking") | |
print(" q or quit: Exit the script") | |
# Ensure the monthly directory exists | |
ensure_directory_exists(MONTHLY_DIR) | |
# Track if user has chosen to move all files | |
move_all = False | |
# Get all valid files and sort them chronologically | |
valid_files = [] | |
for filename in os.listdir(MONTHLY_DIR): | |
file_path = MONTHLY_DIR / filename | |
# Skip directories and non-markdown files | |
if file_path.is_dir() or not filename.endswith('.md'): | |
continue | |
# Extract year and month from filename (YYYY-MM-M format) | |
match = re.match(r'^(\d{4})-(\d{2})-M\.md$', filename) | |
if not match: | |
print(f"Skipping file with unexpected format: {filename}") | |
continue | |
year = match.group(1) | |
month = match.group(2) | |
valid_files.append((filename, year, month, file_path)) | |
# Sort files by year and month | |
valid_files.sort(key=lambda x: (x[1], x[2])) | |
# Process files in chronological order | |
for filename, year, month, file_path in valid_files: | |
year_dir = MONTHLY_DIR / year | |
# Create year directory if it doesn't exist | |
ensure_directory_exists(year_dir) | |
# Move file to year directory | |
new_file_path = year_dir / filename | |
# Ask for confirmation if not already moving all | |
if not move_all: | |
confirmation = confirm_action(f"Move {filename} to {year}/ directory?") | |
if confirmation == 'all': | |
move_all = True | |
elif not confirmation: | |
print(f"Skipped {filename}") | |
continue | |
shutil.move(file_path, new_file_path) | |
print(f"Moved {filename} to {year}/ directory") | |
print("Organization complete!") | |
# Run the organization function | |
if __name__ == "__main__": | |
organize_monthly_files_by_year() |
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
import os | |
import re | |
import shutil | |
import argparse | |
from pathlib import Path | |
# Get the script directory | |
SCRIPT_DIR = Path(__file__).parent | |
VAULT_DIR = SCRIPT_DIR.parent | |
# Dictionary mapping file types to their properties | |
FILE_TYPES = { | |
"daily": { | |
"dir": VAULT_DIR / "planner" / "daily", | |
"pattern": r'^(\d{4})-(\d{2})-(\d{2})\.md$', | |
"groups": ["year", "month", "day"], | |
"sort_keys": 3, # Sort by year, month, day | |
"nested": True # Use year/month nested structure | |
}, | |
"weekly": { | |
"dir": VAULT_DIR / "planner" / "weekly", | |
"pattern": r'^(\d{4})-W(\d{2})\.md$', | |
"groups": ["year", "week"], | |
"sort_keys": 2, # Sort by year, week | |
"nested": False # Use only year structure | |
}, | |
"monthly": { | |
"dir": VAULT_DIR / "planner" / "monthly", | |
"pattern": r'^(\d{4})-(\d{2})-M\.md$', | |
"groups": ["year", "month"], | |
"sort_keys": 2, # Sort by year, month | |
"nested": False # Use only year structure | |
}, | |
"quarterly": { | |
"dir": VAULT_DIR / "planner" / "quarterly", | |
"pattern": r'^(\d{4})-Q(\d)\.md$', | |
"groups": ["year", "quarter"], | |
"sort_keys": 2, # Sort by year, quarter | |
"nested": False # Use only year structure | |
} | |
} | |
# Function to ensure a directory exists | |
def ensure_directory_exists(dir_path): | |
if not dir_path.exists(): | |
dir_path.mkdir(parents=True) | |
print(f"Created directory: {dir_path}") | |
# Function to get user confirmation | |
def confirm_action(message): | |
while True: | |
response = input(f"{message} (y/n/all/q): ").lower() | |
if response in ['y', 'yes']: | |
return True | |
elif response in ['n', 'no']: | |
return False | |
elif response == 'all': | |
return 'all' | |
elif response in ['q', 'quit']: | |
print("Operation cancelled.") | |
exit(0) | |
else: | |
print("Please respond with 'y' (yes), 'n' (no), 'all' (yes to all), or 'q' (quit)") | |
# Main function to organize files | |
def organize_files(file_type): | |
# Validate file type | |
if file_type not in FILE_TYPES: | |
print(f"Error: Unknown file type '{file_type}'") | |
print(f"Valid types are: {', '.join(FILE_TYPES.keys())}") | |
return | |
config = FILE_TYPES[file_type] | |
directory = config["dir"] | |
pattern = config["pattern"] | |
groups = config["groups"] | |
sort_keys = config["sort_keys"] | |
nested = config["nested"] | |
print(f"Starting organization of {file_type} files...") | |
print("Options for each file:") | |
print(" y or yes: Move this file") | |
print(" n or no: Skip this file") | |
print(" all: Move all remaining files without asking") | |
print(" q or quit: Exit the script") | |
# Ensure the directory exists | |
ensure_directory_exists(directory) | |
# Track if user has chosen to move all files | |
move_all = False | |
# Get all valid files and sort them chronologically | |
valid_files = [] | |
for filename in os.listdir(directory): | |
file_path = directory / filename | |
# Skip directories and non-markdown files | |
if file_path.is_dir() or not filename.endswith('.md'): | |
continue | |
# Extract components from filename | |
match = re.match(pattern, filename) | |
if not match: | |
print(f"Skipping file with unexpected format: {filename}") | |
continue | |
# Create a list of extracted groups plus the filename and path | |
file_info = [filename] | |
for i in range(1, len(groups) + 1): | |
file_info.append(match.group(i)) | |
file_info.append(file_path) | |
valid_files.append(file_info) | |
# Sort files chronologically | |
# The +1 in the lambda is to skip the filename at position 0 | |
valid_files.sort(key=lambda x: tuple(x[1:sort_keys+1])) | |
# Process files in chronological order | |
for file_info in valid_files: | |
filename = file_info[0] | |
year = file_info[1] | |
file_path = file_info[-1] | |
# Create year directory | |
year_dir = directory / year | |
ensure_directory_exists(year_dir) | |
# For daily files, create month directory | |
if nested and len(file_info) > 3: # Daily files have year, month, day | |
month = file_info[2] | |
month_dir = year_dir / month | |
ensure_directory_exists(month_dir) | |
new_dir = month_dir | |
move_path = f"{year}/{month}" | |
else: | |
new_dir = year_dir | |
move_path = year | |
# Move file to the appropriate directory | |
new_file_path = new_dir / filename | |
# Ask for confirmation if not already moving all | |
if not move_all: | |
confirmation = confirm_action(f"Move {filename} to {move_path}/ directory?") | |
if confirmation == 'all': | |
move_all = True | |
elif not confirmation: | |
print(f"Skipped {filename}") | |
continue | |
shutil.move(file_path, new_file_path) | |
print(f"Moved {filename} to {move_path}/ directory") | |
print(f"Organization of {file_type} files complete!") | |
# Parse command line arguments | |
def parse_arguments(): | |
parser = argparse.ArgumentParser(description="Organize Obsidian planner files into year/month directories") | |
parser.add_argument('type', choices=FILE_TYPES.keys(), help="Type of files to organize") | |
parser.add_argument('--all', action='store_true', help="Organize all types of files") | |
return parser.parse_args() | |
# Main execution | |
if __name__ == "__main__": | |
args = parse_arguments() | |
if args.all: | |
# Process all file types | |
for file_type in FILE_TYPES.keys(): | |
print(f"\nProcessing {file_type} files...") | |
organize_files(file_type) | |
print("\nAll file organization complete!") | |
else: | |
# Process only the specified file type | |
organize_files(args.type) |
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
import os | |
import re | |
import shutil | |
from pathlib import Path | |
# Get the script directory and define the quarterly directory path | |
SCRIPT_DIR = Path(__file__).parent | |
QUARTERLY_DIR = SCRIPT_DIR.parent / "planner" / "quarterly" | |
# Function to ensure a directory exists | |
def ensure_directory_exists(dir_path): | |
if not dir_path.exists(): | |
dir_path.mkdir(parents=True) | |
print(f"Created directory: {dir_path}") | |
# Function to get user confirmation | |
def confirm_action(message): | |
while True: | |
response = input(f"{message} (y/n/all/q): ").lower() | |
if response in ['y', 'yes']: | |
return True | |
elif response in ['n', 'no']: | |
return False | |
elif response == 'all': | |
return 'all' | |
elif response in ['q', 'quit']: | |
print("Operation cancelled.") | |
exit(0) | |
else: | |
print("Please respond with 'y' (yes), 'n' (no), 'all' (yes to all), or 'q' (quit)") | |
# Main function to organize quarterly files | |
def organize_quarterly_files_by_year(): | |
print("Starting organization of quarterly files by year...") | |
print("Options for each file:") | |
print(" y or yes: Move this file") | |
print(" n or no: Skip this file") | |
print(" all: Move all remaining files without asking") | |
print(" q or quit: Exit the script") | |
# Ensure the quarterly directory exists | |
ensure_directory_exists(QUARTERLY_DIR) | |
# Track if user has chosen to move all files | |
move_all = False | |
# Get all valid files and sort them chronologically | |
valid_files = [] | |
for filename in os.listdir(QUARTERLY_DIR): | |
file_path = QUARTERLY_DIR / filename | |
# Skip directories and non-markdown files | |
if file_path.is_dir() or not filename.endswith('.md'): | |
continue | |
# Extract year and quarter from filename (YYYY-QQ format) | |
match = re.match(r'^(\d{4})-Q(\d)\.md$', filename) | |
if not match: | |
print(f"Skipping file with unexpected format: {filename}") | |
continue | |
year = match.group(1) | |
quarter = match.group(2) | |
valid_files.append((filename, year, quarter, file_path)) | |
# Sort files by year and quarter | |
valid_files.sort(key=lambda x: (x[1], x[2])) | |
# Process files in chronological order | |
for filename, year, quarter, file_path in valid_files: | |
year_dir = QUARTERLY_DIR / year | |
# Create year directory if it doesn't exist | |
ensure_directory_exists(year_dir) | |
# Move file to year directory | |
new_file_path = year_dir / filename | |
# Ask for confirmation if not already moving all | |
if not move_all: | |
confirmation = confirm_action(f"Move {filename} to {year}/ directory?") | |
if confirmation == 'all': | |
move_all = True | |
elif not confirmation: | |
print(f"Skipped {filename}") | |
continue | |
shutil.move(file_path, new_file_path) | |
print(f"Moved {filename} to {year}/ directory") | |
print("Organization complete!") | |
# Run the organization function | |
if __name__ == "__main__": | |
organize_quarterly_files_by_year() |
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
import os | |
import re | |
import shutil | |
from pathlib import Path | |
# Get the script directory and define the weekly directory path | |
SCRIPT_DIR = Path(__file__).parent | |
WEEKLY_DIR = SCRIPT_DIR.parent / "planner" / "weekly" | |
# Function to ensure a directory exists | |
def ensure_directory_exists(dir_path): | |
if not dir_path.exists(): | |
dir_path.mkdir(parents=True) | |
print(f"Created directory: {dir_path}") | |
# Function to get user confirmation | |
def confirm_action(message): | |
while True: | |
response = input(f"{message} (y/n/all/q): ").lower() | |
if response in ['y', 'yes']: | |
return True | |
elif response in ['n', 'no']: | |
return False | |
elif response == 'all': | |
return 'all' | |
elif response in ['q', 'quit']: | |
print("Operation cancelled.") | |
exit(0) | |
else: | |
print("Please respond with 'y' (yes), 'n' (no), 'all' (yes to all), or 'q' (quit)") | |
# Main function to organize weekly files | |
def organize_weekly_files_by_year(): | |
print("Starting organization of weekly files by year...") | |
print("Options for each file:") | |
print(" y or yes: Move this file") | |
print(" n or no: Skip this file") | |
print(" all: Move all remaining files without asking") | |
print(" q or quit: Exit the script") | |
# Ensure the weekly directory exists | |
ensure_directory_exists(WEEKLY_DIR) | |
# Track if user has chosen to move all files | |
move_all = False | |
# Get all valid files and sort them chronologically | |
valid_files = [] | |
for filename in os.listdir(WEEKLY_DIR): | |
file_path = WEEKLY_DIR / filename | |
# Skip directories and non-markdown files | |
if file_path.is_dir() or not filename.endswith('.md'): | |
continue | |
# Extract year and week from filename (YYYY-Www format) | |
match = re.match(r'^(\d{4})-W(\d{2})\.md$', filename) | |
if not match: | |
print(f"Skipping file with unexpected format: {filename}") | |
continue | |
year = match.group(1) | |
week = match.group(2) | |
valid_files.append((filename, year, week, file_path)) | |
# Sort files by year and week | |
valid_files.sort(key=lambda x: (x[1], x[2])) | |
# Process files in chronological order | |
for filename, year, week, file_path in valid_files: | |
year_dir = WEEKLY_DIR / year | |
# Create year directory if it doesn't exist | |
ensure_directory_exists(year_dir) | |
# Move file to year directory | |
new_file_path = year_dir / filename | |
# Ask for confirmation if not already moving all | |
if not move_all: | |
confirmation = confirm_action(f"Move {filename} to {year}/ directory?") | |
if confirmation == 'all': | |
move_all = True | |
elif not confirmation: | |
print(f"Skipped {filename}") | |
continue | |
shutil.move(file_path, new_file_path) | |
print(f"Moved {filename} to {year}/ directory") | |
print("Organization complete!") | |
# Run the organization function | |
if __name__ == "__main__": | |
organize_weekly_files_by_year() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment