Initial commit.
This commit is contained in:
commit
3098d31989
34
README.md
Normal file
34
README.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
# gempost
|
||||
An **experimental** gemlog manager for tildes.
|
||||
|
||||
# Quick Start
|
||||
Basically, gempost manages a *posts.gmi* file and maintains it in a clean format
|
||||
such that it shows your latest post, your five most recent posts, along with
|
||||
an archive of all your posts, arranged from newest to oldest.
|
||||
|
||||
Doing all this manually would be very tedious.
|
||||
|
||||
If you are an existing user, please back up your **public_gemini/** directory and then
|
||||
proceed with the given steps.
|
||||
|
||||
**STEP 1**
|
||||
Wih only your *index.gmi* file in **public_gemini/** directory (and optionally also *postPageHeader.gmi*), run the following to set everyting up:
|
||||
`gempost init`
|
||||
|
||||
Note that `gempost init` won't work if you don't have it added to your path. For tildeverse members, you can just copy the executable to you **~/bin/** directory. Otherwise, you can just do `./gempost <args>` instead of `gempost <args>`.
|
||||
|
||||
**STEP 2**
|
||||
gempost doesn't touch your *index.gmi* file, so you will have to manually add a link to *posts.gmi*.
|
||||
|
||||
To do that, just add the following line to your *index.gmi* file:
|
||||
`=> ./posts.gmi Posts`
|
||||
|
||||
**CONGRATULATIONS!**
|
||||
You are ready to use gempost. Please just use `gempost help` from now on to only get a list of commands.
|
||||
|
||||
# Notes
|
||||
This software is in its early stages and **I am not responsible if you mess up your gemlog using this program.** It works rather fine, it is something I intend to keep for personal use for now. Although if you are comfortable, I invite you to try it out. To make it perfect, I will have to use it for a long term and work out the long-term posts storage solution.
|
||||
|
||||
Personally, I am happy with the set of features I currently have and will work out the bugs as I find them.
|
||||
|
||||
**This software is provided without any guarantees.** I might not even check this page again or even delete this repository out of anxiety xD
|
330
gempost
Executable file
330
gempost
Executable file
|
@ -0,0 +1,330 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# gempost - gemlog manager for tilde.team, written by ~desertmouse
|
||||
|
||||
from os import system as exec
|
||||
from os import listdir
|
||||
from os import getlogin
|
||||
from sys import argv
|
||||
from hashlib import sha1
|
||||
from random import random
|
||||
|
||||
# colored output functions
|
||||
def prRed(skk): print("\033[91m {}\033[00m" .format(skk))
|
||||
def prGreen(skk): print("\033[92m {}\033[00m" .format(skk))
|
||||
def prYellow(skk): print("\033[93m {}\033[00m" .format(skk))
|
||||
def prPurple(skk): print("\033[95m {}\033[00m" .format(skk))
|
||||
|
||||
# main variable declarations
|
||||
default_post_page_header = """
|
||||
# posts
|
||||
"""
|
||||
|
||||
wdir = f"/home/{getlogin()}/public_gemini/" # working/main directory
|
||||
postDir = wdir + "postdir/" # all post files along with the archive.gmi are stored here
|
||||
indexFile = wdir + "postIndex" # the index is maintained in this file
|
||||
editor = "nano"
|
||||
|
||||
def rebuildReferences(filename = None, title = None, callingFrom = "default"):
|
||||
""" reads {wdir}postIndex and rebuilds the {wdir}posts.gmi and {postDir}archive.gmi files """
|
||||
customized = False
|
||||
allposts = [] # storing all indexFile entries inside a list
|
||||
|
||||
with open(f"{indexFile}",'r') as i:
|
||||
allposts = i.readlines()
|
||||
if (len(allposts) == 0): # if postIndex is empty, there is nothing to to
|
||||
# reset posts.gmi and delete archive.gmi
|
||||
# ie, customize the function
|
||||
customized = True
|
||||
|
||||
headerText = []
|
||||
try:
|
||||
with open(f"{wdir}postPageHeader.gmi",'r') as h: # the file postPageHeader should be defined by the user in {wdir}. It should contain the user's custom post page header
|
||||
headerText = h.readlines()
|
||||
except FileNotFoundError: # If user hasn't created that file, a default line "# posts" is added to posts.gmi
|
||||
headerText.append(default_post_page_header)
|
||||
|
||||
if (customized == True): # if postIndex was empty, then do not do more than this. Just remove archive.gmi after this
|
||||
exec(f"rm {postDir}archive.gmi") # delete archive.gmi
|
||||
with open(f"{wdir}posts.gmi",'w') as po: # writing the header into posts.gmi
|
||||
po.writelines(headerText)
|
||||
return 0
|
||||
|
||||
headerText.append("\n## Latest\n")
|
||||
if (callingFrom == "delete"): # the function's callingFrom switch is to perform special operations based on where it is invoked from
|
||||
headerText.append(f"{allposts[-1]}")
|
||||
else:
|
||||
headerText.append(f"=> postdir/{filename}.gmi {title}\n")
|
||||
|
||||
headerText.append("\n## Recent\n") # adding the five most recent posts to posts.gmi
|
||||
try:
|
||||
counter = 0
|
||||
for i in allposts[::-1]:
|
||||
if (counter == 5):
|
||||
break
|
||||
headerText.append(i)
|
||||
counter += 1
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
allposts = [] # storing all indexFile entries inside a list
|
||||
with open(f"{indexFile}",'r') as i:
|
||||
allposts = i.readlines()
|
||||
|
||||
headerText.append("\n## Older\n") # for older entries, refer to another file, archive.gmi, which is basically a reversed copy of {indexFile} to show the most recent posts on top
|
||||
|
||||
with open(f"{postDir}archive.gmi","w") as ar: # creating all posts list
|
||||
ar.writelines(allposts[::-1])
|
||||
|
||||
headerText.append(f"=> postdir/archive.gmi Posts Archive")
|
||||
|
||||
with open(f"{wdir}posts.gmi",'w') as po: # finally writing into posts.gmi to "bring it all together"
|
||||
po.writelines(headerText)
|
||||
|
||||
|
||||
def newpost(title, existing_content = None):
|
||||
modified = False
|
||||
""" Write and submit a new post """
|
||||
if (existing_content == None or existing_content == []):
|
||||
pass
|
||||
else:
|
||||
modified = True # modify the function if doing this for existing file
|
||||
|
||||
filename = sha1(str(f"{title}{random()}").encode("utf-8")).hexdigest() # generating a unique filename
|
||||
|
||||
exec(f"touch {wdir + filename}.gmi") # creating empty file
|
||||
|
||||
if modified: # add content to file using the existing_content list instead of opening editor
|
||||
with open(f"{wdir+filename}.gmi", 'w') as o:
|
||||
o.writelines(existing_content)
|
||||
else:
|
||||
temp = "# " + title # adding title to empty file
|
||||
exec(f'echo "{temp}" | cat > {wdir + filename}.gmi')
|
||||
|
||||
exec(f"{editor} {wdir+filename}.gmi") # opening editor
|
||||
|
||||
ch = input("\nWould you like to review before posting? (c to cancel) (y/[n]): ")
|
||||
|
||||
if (ch == 'y' or ch == 'Y'):
|
||||
exec(f"less {wdir+filename}.gmi") # opening the file in editor for review
|
||||
exec(f"mv {wdir+filename}.gmi {postDir+filename}.gmi") # moving file to postdir
|
||||
elif (ch == 'c'):
|
||||
exec(f"mv {wdir+filename}.gmi {wdir}trash/")
|
||||
print("Cancelled")
|
||||
return None
|
||||
else:
|
||||
exec(f"mv {wdir+filename}.gmi {postDir+filename}.gmi") # moving file to postdir without review
|
||||
|
||||
# updating postIndex
|
||||
exec(f'echo "=> postdir/{filename}.gmi {title}" | cat >> {indexFile}')
|
||||
|
||||
# updating posts.gmi, preserving the user's custom header - rbfn begin
|
||||
rebuildReferences(filename, title)
|
||||
|
||||
# changing file permissions
|
||||
exec(f"chmod 745 {wdir}posts.gmi")
|
||||
exec(f"chmod 745 {wdir}postdir && chmod 745 {wdir}postdir/*")
|
||||
|
||||
#print("\nYour post is live!") # yay?
|
||||
prGreen("\nYour post is live!") # yay?
|
||||
|
||||
def manage():
|
||||
allposts = [] # storing all indexFile entries inside a list
|
||||
with open(f"{indexFile}",'r') as i:
|
||||
allposts = i.readlines()
|
||||
|
||||
if (len(allposts) == 0): # exit function if no files in index
|
||||
return 0
|
||||
|
||||
postsList = []
|
||||
for i in allposts:
|
||||
postsList.append([i[56:-1],i[11:51]])
|
||||
|
||||
print() # line break
|
||||
try:
|
||||
counter = 0
|
||||
for i in postsList:
|
||||
print(f"{counter}. {i[0]} | {i[1]}")
|
||||
counter += 1
|
||||
which = int(input("Select which post to manage: ")) # getting the index of the menu entry the user wants to edit
|
||||
temp = allposts[which]
|
||||
except:
|
||||
prRed("\nInvalid selection. Exiting...")
|
||||
return 0
|
||||
try:
|
||||
mode = int(input("\nSELECT MODE:\n1 - EDIT\n2 - DELETE\n3 - CANCEL\n-> ")) # what does the user want to do with the selection?
|
||||
except ValueError:
|
||||
print("\nCancelling...")
|
||||
return 0
|
||||
|
||||
if (mode == 1): # if mode is EDIT, open editor with the post file
|
||||
print(f'\nEditing "{postsList[which][0]}" ...')
|
||||
exec(f"{editor} {postDir}{postsList[which][1]}.gmi")
|
||||
prGreen("Post updated.")
|
||||
elif (mode == 2): # for DELETE mode, first delete post file, delete its reference from {wdir}postIndex, and rebuild the {wdir}posts.gmi and {postDir}archive.gmi files
|
||||
exec(f"mv {postDir}{postsList[which][1]}.gmi {wdir}trash/")
|
||||
print(f"\nfile moved to trash")
|
||||
exec(f"sed -i '/{postsList[which][1]}.gmi/d' {indexFile}")
|
||||
print(f"postIndex updated")
|
||||
rebuildReferences(None, None, "delete")
|
||||
print("rebuilt post.gmi, archive.gmi")
|
||||
prGreen("All done.")
|
||||
elif (mode == 3):
|
||||
print("\nReturning to main menu...")
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
# information strings
|
||||
helpText = """
|
||||
gempost (v0.6ish) - gemlog manager for tildes
|
||||
|
||||
Available arguments:
|
||||
post - create a new post
|
||||
Usage:
|
||||
post (without arguments) Write a blog post from scratch.
|
||||
post [path to file without braces] Post from .gmi file. First line will be used as heading.
|
||||
|
||||
manage - edit or delete your posts
|
||||
|
||||
purge - premanently delete the files in trash/
|
||||
init - set up your public_gemini/ directory for gempost
|
||||
reset - delete all posts and re-initialize public_gemini/
|
||||
|
||||
help - display this help text
|
||||
desc - a brief description of how the program works
|
||||
|
||||
qs - Quick Start for new users
|
||||
"""
|
||||
|
||||
quickStart = """
|
||||
Basically, gempost manages a "posts.gmi" file and maintains it in a clean format
|
||||
such that it shows your latest post, your five most recent posts, along with
|
||||
an archive of all your posts, arranged from newest to oldest.
|
||||
|
||||
Doing all this manually would be *very* tedious.
|
||||
|
||||
If you are an existing user, please back up your public_gemini/ directory and then
|
||||
proceed with the given steps.
|
||||
|
||||
Quickstart:
|
||||
STEP 1
|
||||
Wih only your index.gmi file in public_gemini/ (and optionally "postPageHeader.gmi"), run the following to set everyting up:
|
||||
|
||||
gempost init
|
||||
|
||||
STEP 2
|
||||
gempost doesn't touch your index.gmi file, so you will have to manually add a link to "posts.gmi"
|
||||
|
||||
To do that, just add the following line to your index.gmi file:
|
||||
=> ./posts.gmi Posts
|
||||
|
||||
CONGRATULATIONS!
|
||||
You are ready to use gempost. Please just use "gempost help" from now on to only get a list of commands.
|
||||
|
||||
You may run "gempost desc" to get to know a bit more about how gempost works, along with a single optional feature.
|
||||
"""
|
||||
|
||||
description = """
|
||||
Directory Structure:
|
||||
gempost expects the following structure of your ~/public_gemini/ directory:
|
||||
|
||||
public_gemini/
|
||||
├── index.gmi
|
||||
├── postdir/
|
||||
├── postIndex
|
||||
├── postPageHeader.gmi (optional)
|
||||
├── posts.gmi
|
||||
└── trash/
|
||||
|
||||
2 directories, 4 files
|
||||
|
||||
- "postdir/" is where all your gemlog posts are stored.
|
||||
- "postIndex" is the file that gempost uses to maintain an index of your posts.
|
||||
- "postPageHeader.gmi" is a file that you can populate with your custom ascii artwork or other text.
|
||||
This will come before the auto-generated links to your posts written by gempost.
|
||||
Note that this is optional.
|
||||
- "posts.gmi" is where the program will maintain links to:
|
||||
- your latest post
|
||||
- your 5 most recent posts
|
||||
- a link to "archive.gmi" that contains a list of all your posts
|
||||
- "trash/" is the directory where gempost keeps your deleted posts. use "gempost purge" to delete its contents.
|
||||
"""
|
||||
|
||||
# Process command-line arguments
|
||||
try:
|
||||
arg = argv[1]
|
||||
except IndexError:
|
||||
print(helpText) # print the help text if no arguments passed
|
||||
exit()
|
||||
|
||||
if (arg == "help" or arg == "-h" or arg == "--help"): # help text listing all available arguments
|
||||
print(helpText)
|
||||
|
||||
elif (arg == "desc"): # description of how the program uses different files and dierctories
|
||||
print(description)
|
||||
|
||||
elif (arg == "qs"):
|
||||
print(quickStart) # a simple 2-step quick start for new users
|
||||
|
||||
elif (arg == "post"):
|
||||
if (len(argv) == 3): # checking if more than 1 arg (except python) to see if source file has been supplied
|
||||
filePath = argv[2] # storing that file's path in a var
|
||||
print(f"Using file {filePath}") # tell the user that the program is using a source file
|
||||
exiting_content, firstLine = [], "" # content of the file, and title respectively
|
||||
try:
|
||||
with open(filePath, 'r') as f: # populating file content and title variables
|
||||
existing_content = f.readlines()
|
||||
firstLine = existing_content[0][2:] # first line, used as title
|
||||
|
||||
if (firstLine[-1] == '\n'): # if there is a NewLine character at the end of title line (probably will be), remove it from post title
|
||||
firstLine = firstLine[:-1]
|
||||
|
||||
if (len(firstLine) == 0): # message and exit if title empty
|
||||
prRed("Post title cannot be empty.")
|
||||
exit()
|
||||
else:
|
||||
newpost(firstLine, existing_content)
|
||||
except FileNotFoundError:
|
||||
prRed("Invalid file path / file does not exist.")
|
||||
|
||||
else:
|
||||
print("\nAfter entering a title, an editor will be launched for you to write your blog post.")
|
||||
print("The process will continue after you save your post (Ctrl+S) and exit the editor (Ctrl+X).")
|
||||
title = input("\nPOST TITLE: ")
|
||||
if (len(title) == 0):
|
||||
prRed("Post title cannot be empty.")
|
||||
else:
|
||||
newpost(input("\nPOST TITLE: "))
|
||||
|
||||
elif (arg == "manage"):
|
||||
manage()
|
||||
|
||||
elif (arg == "purge"): # permanently delete trashed posts
|
||||
if (len(listdir(f"{wdir}trash/")) == 0):
|
||||
prYellow("Trash is empty.")
|
||||
else:
|
||||
exec(f"rm {wdir}trash/*")
|
||||
prGreen("Trash cleared.")
|
||||
|
||||
elif (arg == "init"): # initialize public_gemini/ for use with gempost
|
||||
if (len(listdir(f"{wdir}")) <= 2):
|
||||
exec(f"touch {wdir}postIndex && mkdir {wdir}postdir/ {wdir}trash/ && chmod 745 {wdir}* && chmod 700 {wdir}trash/")
|
||||
prGreen('Done. Use "gempost help" to view available arguments.')
|
||||
else:
|
||||
prRed('Only your "index.gmi" (and optionally "postPageHeader.gmi") should be there in public_gemini/')
|
||||
|
||||
elif (arg == "reset"): # delete all posts and re-initialize public_gemini/ for use with gempost
|
||||
prRed("Warning: This will delete all your posts.")
|
||||
if (input('Type "yes, I understand" to continue: ') == "yes, I understand"):
|
||||
exec(f"rm -rf {wdir}postdir/ {wdir}trash/ && rm -f {wdir}posts.gmi {indexFile}")
|
||||
exec(f"touch {wdir}postIndex && mkdir {wdir}postdir/ {wdir}trash/ && chmod 745 {wdir}* && chmod 700 {wdir}trash/")
|
||||
prYellow("public_gemini/ has been re-initialized successfully.")
|
||||
else:
|
||||
print("Cancelled.")
|
||||
|
||||
else:
|
||||
print(f"Unknown argument '{arg}'")
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user