Skip to content

Instantly share code, notes, and snippets.

@jdheyburn
Created March 21, 2025 07:27
Show Gist options
  • Save jdheyburn/f64effd0b606a68044d7d28960becc77 to your computer and use it in GitHub Desktop.
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.
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()
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()
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)
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()
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