Initial commit.

This commit is contained in:
= 2022-05-29 00:21:54 +05:30
commit 3098d31989
2 changed files with 364 additions and 0 deletions

34
README.md Normal file
View 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
View 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}'")