Python3 Code Library
Python Code Library
This is a onenote/joplin inspired note taking app. It can be used for any type of text file archive.
How you save your files does not matter except in a way that you can organize them. For example, if you have spl code, you might save all spl snippets with .spl, then use the button that searches for *.spl
You might also want to categorize your items like: linux_some_command.txt
or linux_some_command.cli
so you can search only files that start with linux or end with cli. Organize how your workflow works.
Warning
It will do what you tell it. So if you mess up a valid json or xml file, it will not warn you. I plan on adding validation but for now, be careful on specially formatted files. Don’t blame me if this makes your cheerios taste bad, there are no warranties of any kind.
The Script
Save as _code-library.py in the folder where all of your text snippets are. It will do the rest.
"""
NoteBook Application
This script provides a graphical user interface for a simple notebook application,
enabling users to create, search, edit, save, and delete text and JSON files. The
application supports basic text file management functionalities, including the ability
to search files using glob patterns, view and edit file content, and manage files within
a specified directory.
The interface allows for editing and special formatting for JSON files, including
pretty-printing and validation. Additionally, users can toggle fullscreen mode and
uses keyboard shortcuts for common actions, for more intuitive workflow.
Use Case:
- library of code
- library of ascii art
- library of guitar tabs
Requirements:
- Python 3.x
- tkinter library (usually included with Python)
- uses all standard modules, no venv required
Keyboard Shortcuts:
- Ctrl+S: Save the current file.
- Ctrl+A: Select all text in the current file.
- F11: Toggle fullscreen mode.
Author: James Fraze
Version: 0.02
License: MIT
"""
# for most of the gui
import tkinter as tk
# for file name glob searches
from glob import glob
# for message boxes and ttk for themes
from tkinter import messagebox, ttk
# to delete files
import os
# to detect os
import platform
# for the help file
import json
VERSION = ".02"
class NoteBook:
"""
A NoteBook application class for managing and editing text files.
This class provides a graphical user interface for a notebook application
that allows users to create, search, save, and delete text files. It also
supports viewing and editing JSON files with syntax highlighting and
special formatting.
Attributes:
root (tk.Tk): The main window of the application.
frame0 (tk.Frame): A frame for the top section of the application.
frame1 (tk.Frame): A frame for the bottom section of the application.
lb (tk.Listbox): A listbox for displaying available text files.
text (tk.Text): A text widget for displaying and editing the content of text files.
save_var (tk.StringVar): A variable to hold the name of the current file.
"""
fullscreen = False
def __init__(self, root):
"""
Initializes the NoteBook application with a specified root window.
Parameters:
root (tk.Tk): The main window of the application.
Frames:
root
|_frame0 - a single skinny row at the top
|_frame1 - the whole bottom area, other stuff stacked in this
|_lb - list box on the left part of frame1
|_text - stacked up next to frame 1, effectively the right side text area
"""
# basic app dimenions/title on "root"
# this holds other frames
self.root = root
self.root.geometry("900x700")
self.root.title(f"Python Code Library v{VERSION}")
# the top frame where the grey bar is
# frame0 is the name we give to the top section
self.frame0 = tk.Frame(self.root)
self.frame0["bg"] = "grey"
self.frame0.pack(side="top", fill=tk.X)
# frame1 is the name we give to the bottom area
self.frame1 = tk.Frame(self.root)
self.frame1["bg"] = "coral"
self.frame1.pack(side="left", fill=tk.Y)
# list box in that bottom area, 30 wide
self.lb = tk.Listbox(self.frame1, width=30)
self.lb.pack(side="bottom", fill=tk.Y, expand=1)
# text block in bottom frame, stacked next to list box, 70 wide
self.text = tk.Text(self.root, width=70)
self.text.pack(fill=tk.BOTH, expand=1)
# file save name box, also in frame0
self.save_var = tk.StringVar()
self.name = tk.Entry(self.frame0, textvariable=self.save_var)
self.name.pack(side="left", ipady=3)
# other button controls save/del, still in frame 0
self.b_search = tk.Button(self.frame0, text="FIND", command=self.glob_stuff)
self.b_search.pack(side="left")
self.b_save = tk.Button(self.frame0, text="SAVE", command=self.save_text)
self.b_save.pack(side="left")
self.b_del = tk.Button(self.frame0, text="DEL", command=self.del_text)
self.b_del.pack(side="left")
# Insert a spacer
spacer = tk.Frame(self.frame0, width=20, height=40, bg="grey")
spacer.pack(side="left", padx=5)
# Buttons that come after the spacer
self.b_ps1 = tk.Button(
self.frame0, text="PS1", command=lambda: self.glob_stuff("*.ps1")
)
self.b_ps1.pack(side="left")
self.b_py = tk.Button(
self.frame0, text="PY", command=lambda: self.glob_stuff("*.py")
)
self.b_py.pack(side="left")
self.b_spl = tk.Button(
self.frame0, text="SPL", command=lambda: self.glob_stuff("*.spl")
)
self.b_spl.pack(side="left")
self.b_txt = tk.Button(
self.frame0, text="TXT", command=lambda: self.glob_stuff("*.txt")
)
self.b_txt.pack(side="left")
self.b_json = tk.Button(
self.frame0, text="JSON", command=lambda: self.glob_stuff("*.json")
)
self.b_json.pack(side="left")
# Insert a spacer
spacer = tk.Frame(self.frame0, width=20, height=40, bg="grey")
spacer.pack(side="left", padx=5)
self.b_help = tk.Button(self.frame0, text="?", command=self.show_help)
self.b_help.pack(side="left")
# some keybindings and mouse click function
# not visible, just bound in memory
self.lb.bind(
"<Double-Button>", lambda x: self.view_text()
) # when you double click an item in left
self.root.bind("<F11>", lambda x: self.f11_fullscreen()) # makes full stcreen
self.root.bind(
"<Return>", lambda x: self.glob_stuff()
) # executes search when you hit enter
self.root.bind("<Control-s>", lambda event: self.save_text()) # ctrl s to save
self.root.bind(
"<Control-S>", lambda event: self.save_text()
) # ctrl s to save, Caps
self.root.bind(
"<Control-a>", lambda event: self.select_all_text()
) # ctrl a to select all
self.root.bind(
"<Control-A>", lambda event: self.select_all_text()
) # ctrl a to select all, Caps
def select_all_text(self, event=None):
"""
Selects all text within the text widget.
Parameters:
event (Event, optional): The event that triggered this method.
"""
# Select all the text in the text widget
self.text.tag_add(tk.SEL, "1.0", tk.END)
# Set the mark for the last selected character so that keyboard actions
# apply to the selection
self.text.mark_set(tk.INSERT, tk.END)
# Ensure the text widget has focus so the user can immediately take action
# (like deleting the selection)
self.text.focus()
# Prevent the event from propagating further
return "break"
def glob_stuff(self, search_pattern=None):
"""
Populates the list box with files that match the specified search pattern.
If no search pattern is provided, all files are listed. Supports basic
glob patterns.
Parameters:
search_pattern (str, optional): The glob pattern to filter files.
"""
# Use the provided search pattern if not None, else use the current value of save_var
theglob = search_pattern if search_pattern is not None else self.save_var.get()
# if it's blank, then search all
if not theglob:
theglob = "*.*"
# first, delete whatever is in my view box
self.lb.delete(0, "end")
# then, get the list and populate my view box
for file in glob("*" + theglob + "*"):
self.lb.insert(tk.END, file)
def glob_stuff2(self, theglob):
"""
Populates the list box with files that match the specified search pattern.
If no search pattern is provided, all files are listed. Supports basic
glob patterns.
Parameters:
search_pattern (str, optional): The glob pattern to filter files.
"""
# if it's blank, then search all
if theglob == "":
theglob = "*.*"
else:
theglob = self.save_var.get()
# first, delete whatever is in my view box
self.lb.delete(0, "end")
# then, get the list and populate my view box
for file in glob("*" + theglob + "*"):
self.lb.insert(tk.END, file)
def view_text(self):
"""
Displays the content of the selected file in the text widget.
This method checks the file extension and applies appropriate formatting
for JSON files. (fixme)
"""
# the index of item selected
num_item = self.lb.curselection()
# the file name selected
fname = self.lb.get(num_item)
# update our "save as file name"
self.save_var.set(fname)
# Check if the file is a JSON file
#if fname.endswith(".json"):
# try:
# with open(fname, "r", encoding="utf-8") as file:
# # Load JSON content
# data = json.load(file)
# # Pretty-print the JSON content with indentation
# pretty_json = json.dumps(data, indent=4)
# # Replace literal \n with actual new lines for display
# pretty_json = pretty_json.replace("\\n", "\n")
# # Display the pretty-printed and newline-corrected JSON
# self.text.delete("1.0", tk.END)
# self.text.insert(tk.END, pretty_json)
# except Exception as e:
# messagebox.showerror("Error", f"Failed to load JSON file: {e}")
#else:
# # Handle non-JSON files as before
# with open(fname, "r", encoding="utf-8") as file:
# self.text.delete("1.0", tk.END)
# file_content = file.read()
# self.text.insert(tk.END, file_content)
# fixme: add some json validation to help them
with open(fname, "r", encoding="utf-8") as file:
self.text.delete("1.0", tk.END)
file_content = file.read()
self.text.insert(tk.END, file_content)
def show_help(self):
"""
Displays help information in the text widget.
The help content is loaded from a '_help.json' file.
"""
fname = self.save_var.get()
verify = messagebox.askquestion(
"LEAVE PAGE?",
f"You are about to leave the page {fname}. If you have not saved, your work will be lost. Do you want to leave the page?",
icon="warning",
)
if verify == "yes":
# Load help content from a JSON file
try:
with open("_help.json", "r", encoding="utf-8") as file:
data = json.load(file)
help_text = data["help_text"]
except FileNotFoundError:
help_text = "Help content file not found."
except json.JSONDecodeError as e:
help_text = f"Error reading the help content file: {e}"
except Exception as e:
help_text = f"An unexpected error occurred: {e}"
# Display the help text in the main text area
self.text.delete("1.0", tk.END)
self.text.insert(tk.END, help_text)
def save_text(self):
"""
Saves the content of the text widget into the currently selected file.
Performs special JSON formatting if the file is a JSON file.
"""
# the file name selected
fname = self.save_var.get()
if fname == "":
messagebox.showinfo("Oops", "File name cannot be empty!")
return
# Extract the text from the Text widget
content_to_save = self.text.get(1.0, "end-1c")
if fname.endswith(".json"):
try:
# Attempt to parse the edited text back into JSON to ensure it's valid
json_content = json.loads(content_to_save)
# Use json.dump to write the JSON content back into the file,
# attempting to preserve its structure
with open(fname, "w", encoding="utf-8") as file:
json.dump(json_content, file, ensure_ascii=False, indent=4)
messagebox.showinfo("Message", f"{fname} SAVED as valid JSON!")
except json.JSONDecodeError as e:
messagebox.showerror(
"Error",
f"Failed to save file. The content is not valid JSON. Error: {e}",
)
return
else:
# For non-JSON files, save the text directly
with open(fname, "w", encoding="utf-8") as file:
file.write(content_to_save)
messagebox.showinfo("Message", f"{fname} SAVED!")
# Refresh the file list if needed
theglob = self.save_var.get()
if theglob == "":
theglob = "*.*"
self.glob_stuff2(theglob)
def del_text(self):
"""
Toggles the application between fullscreen and windowed mode.
"""
# the file name selected
fname = self.save_var.get()
if fname == "":
messagebox.showinfo("Oops", fname + " File name cannot be empty!")
return
# open specific file in write mode
verify = messagebox.askquestion(
"REALLY DELETE FOREVER?",
"You are about to Delete '"
+ fname
+ "'. This cannot be undone. Are you sure?",
icon="warning",
)
if verify == "yes":
# fixme: not really a useful check, verify read/write etc
if os.path.exists(fname):
os.remove(fname)
print(fname + " was deleted")
self.text.delete("1.0", tk.END)
else:
messagebox.showinfo(
"Oops",
"the file '"
+ fname
+ "' Does Not Exist! Double click the file on the left to "
+ "automatically enter the proper file name",
)
theglob = self.save_var.get()
if theglob == "":
theglob = "*.*"
self.glob_stuff2(theglob)
def f11_fullscreen(self):
"""
Toggles the application between fullscreen and windowed mode.
"""
if NoteBook.fullscreen is False:
self.root.attributes("-fullscreen", True)
NoteBook.fullscreen = True
else:
self.root.attributes("-fullscreen", False)
NoteBook.fullscreen = False
def main():
"""
main logic
"""
# create a main window in tk
root = tk.Tk()
# Setting up the theme based on OS
os_name = platform.system().lower()
style = ttk.Style()
# fixme: mac users
if "linux" in os_name:
style.theme_use("clam") # Using clam theme for Linux
elif "windows" in os_name:
style.theme_use(
"default"
)
# new class creation
app = NoteBook(root)
# kick it off
root.mainloop()
if __name__ == "__main__":
main()
Help File
save this file as _help.json
{
"help_text": "----------------------------------------------------------------------\nWHAT IS THIS?\n----------------------------------------------------------------------\n NOTE Script to organize notes. Saves notes as simple text files.\n\n----------------------------------------------------------------------\nWHY?\n----------------------------------------------------------------------\n I wanted to learn about Tk and take notes. It's inspired from\n joplin and onenote, without the bells and whistles (and just text)\n\n----------------------------------------------------------------------\nUX FEATURES\n----------------------------------------------------------------------\n- enter key bound to search\n- blank entry hit search/enter? auto searches everything\n- if fname doesn't match and you try to delete warns you\n- warns you before you delete a file\n- type a new name and click save and it makes new article\n- has ? feature to show this help\n- allows for glob searches (*.bleh) in unix format\n\n----------------------------------------------------------------------\nBUTTONS\n----------------------------------------------------------------------\n SEARCH: To filter, just type partial name or glob and hit enter\n SAVE: saves current file name selected, or new one you typed\n DEL: delects current file name selected\n ?: This help message\n\n----------------------------------------------------------------------\nWARNING:\n----------------------------------------------------------------------\n File system limits for max inodes will limit max number of files\n\n----------------------------------------------------------------------\nSUGGESTED USES:\n----------------------------------------------------------------------\n Save a bunch of py files and then search for *.py\n Outline a story with filename divisions 100.txt 101.txt 200.txt, etc\n prefix a name like tutorial_bleh.txt then glob for tutorial*.txt\n tablature\n\n----------------------------------------------------------------------\nTODO:\n----------------------------------------------------------------------\n- add a Date Time Stamp and use \"Logger\"\n- file names with spaces choke, force name normalization\n- put the alerts into a logger function and standardize\n- refactor the glob function into 1\n- create a \"default data\" button to pre-load examples from json\n- colorize or syntax highlight some of the code\n- change up font so it's easier for people to read\n- rename variables into \"notebook\" instead of story, so it's more generic\n- \"template\" out a set of text documents like for policies/rules/handbooks\n- Hide the DOS Window\n- Keybind Alt F4 or other\n- Add encryption like from encrypted journal program\n- Verify linux/windows operation\n- add copy to clipboard button or mouse right clicks\n- refactor the json save protections to allow multline, valid json\n- clean up code deadcode, black, pylint, etc\n- to help with possible inode issues\\directory limits add directories"
}
Sample ASCI:
save as linux.txt
.-"""-.
' \
|,. ,-. |
|()L( ()| |
|,' `".| |
|.___.',| `
.j `--"' ` `.
/ ' ' \
/ / ` `.
/ / ` .
/ / l |
. , | |
,"`. .| |
_.' ``. | `..-'l
| `.`, | `.
| `. __.j )
|__ |--""___| ,-'
`"--...,+"""" `._,.-' mh
save as hollow.tab
Hollow by PANTERA
Tabbed by: KoRny Freak
[Rhythm] (clean) >--What's Left Inside-< >-No one knows what's done is done--<
e|-----------------------------|-----------------------------------------------|
B|----0------0------0------0---|----0-----0-----0-----0------0-----0------0----|
G|---5-5----5-5----5-5----5-5--|---5-5---5-5---5-5---5-5----5-5---5-5----5-5---|
D|--2---2--2---2--2---2--2---2-|--2---2-2---2-----0-----0------0-----0--2---2--|
A|-0------3-------------0------|--------------3-----3------2-----2-------------|
E|---------------3-------------|-0-----0-----2-----2------3-----3------0-----0-|
[Lead] (use neck pickup to get warm tone)
e|----------------------------------------|-------------------------------------|
B|------------------12-15~~-15/\--15/\-12-|----------------12---11~~---9/\/\----|
G|-----------12~~-12----------------------|---------12~~-12---9------7-------10-|
D|--------14------------------------------|-------14----------------------------|
A|15~~-15---------------------------------|15~~-15------------------------------|
E|----------------------------------------|-------------------------------------|
[2nd Lead]
e|--------------------------------5---------------------------------------------|
B|-8/\5-8~~--8/\5---5-----------5--8^5---5-----------5--------------------------|
G|----------------8---8-7-5-7-8--------8---8-7-5-7-8---8-7-5---5----------------|
D|-----------------------------------------------------------7---7--------------|
A|-------------------------------------------------------------------7-6-565--3-|
E|------------------------------------------------------------------------------|
e|--------------------------------------------------------------|
B|------20-20-20-------------20-20-20-----------20/\-20-20/^----|
G|-19/\----------19/\--19~~-----------19/\-19~~-----------------|
D|--------------------------------------------------------------|
A|--------------------------------------------------------------|
E|--------------------------------------------------------------|
[Pre Bridge] >--He as hollow as I alone now.....--<
e|-----------------------------------|---------------------------|
B|-----------------------------------|---------------------------|
G|---0------0------0-----------------|---0---0---0---0--0--0-----|
D|--4-4----4-4----4-4-------0--0-----|--5---4---2---2------------|
A|-2---2--2---2--2---2-----2---------|-7---5---4---3---2---------|
E|0------0------0---------3---1--1-0-|--------------------3--1-0-|
[Bridge] >--He as hollow as I alone, a shell of my friend....--<
e|---------------|-----------------|-----------------------------|
B|---------------|-----------------|-----------------------------|
G|---------------|-----------------|-----------------------------|
D|2-2-2-4-5-6-7--|--2-2-2-7-8-9-10-|-----------------------------|
A|2-2-2-4-5-6-7--|--2-2-2-7-8-9-10-|-----------------------------|
E|0-0-0-2-3-4-5--|--0-0-0-5-6-7-8--|0-8-0-0-6-0-0-8-0-0-6-0-0-6~-|
[After Lyrics]
e|------------------------------|-------------------------------|
B|------------------------------|-------------------------------|
G|------------------------------|-------------------------------|
D|------------------------------|-------------------------------|
A|------------------------------|-------------------------------|
E|-0-8-0-0-6-0-0-8-0-0-6-0-0-6~-|-0-8-0-0-6-0-0-8-0-0-6-6-7-8~--|
[End] [Harmonics]
e|-------------------------------|---------12------|
B|-------------------------------|-----5-----------|
G|---0---0---0---0--0--0---------|--7--------------|
D|--5---4---2---2----------2-----|-----------------|
A|-7---5---4---3---2-------2-----|-----------------|
E|--------------------3--1-0-----|-----------------|
************************************
| ~ Vibrato
************************************