232 lines
7.9 KiB
Python
Executable File
232 lines
7.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# Chalk: A line mode text editor
|
|
# by Brian Evans
|
|
# You may copy or modify at will for profit or not so long
|
|
# as any usage is attributed and it released with these same
|
|
# provisions
|
|
#_
|
|
|
|
import sys
|
|
import os
|
|
import subprocess
|
|
import re
|
|
import readline
|
|
|
|
|
|
class c:
|
|
black = ''
|
|
red = '\033[0;31m'
|
|
b_red = '\033[1;31m'
|
|
yellow = '\033[1;33m'
|
|
green = '\033[0;32m'
|
|
b_green = '\033[1;32m'
|
|
cyan = '\033[0;36m'
|
|
b_cyan = '\033[1;36m'
|
|
purple = '\033[1;35m'
|
|
blue = '\033[0;34m'
|
|
b_blue = '\033[1;34m'
|
|
white = '\033[1;37m'
|
|
end = '\033[0m'
|
|
|
|
|
|
def input_editable(prompt, prefill=''):
|
|
readline.set_startup_hook(lambda: readline.insert_text(prefill))
|
|
try:
|
|
return input(prompt)
|
|
finally:
|
|
readline.set_startup_hook()
|
|
|
|
|
|
def validate_path(path):
|
|
path_body_list = path.split('/')
|
|
path_body_list.pop()
|
|
path_body = '/'.join(path_body_list)
|
|
is_path = os.path.isdir(path_body)
|
|
if not is_path:
|
|
print("Invalid filepath")
|
|
os.exit(2)
|
|
|
|
def print_help():
|
|
helptext = [
|
|
"",
|
|
"",
|
|
"{}Commands are entered as the only entry for their row:{}".format(c.yellow, c.end),
|
|
" {}.{} - Finish writing/exit, will prompt for save".format(c.b_green, c.end),
|
|
" {}!?{} - Print this help message".format(c.b_green, c.end),
|
|
" {}!d{} - Display the contents of the file".format(c.b_green, c.end),
|
|
" {}!v{} - View lines, prompt will request a range of lines".format(c.b_green, c.end),
|
|
" {}!#{} - Rewrite a line, # is replaced by line number e.g. !27".format(c.b_green, c.end),
|
|
" {}!x{} - Delete line(s), prompt will request line or range".format(c.b_green, c.end),
|
|
" {}!i{} - Insert line(s), prompt will request start point and optional quantity".format(c.b_green, c.end),
|
|
" {}!g{} - Print the ruler/guide".format(c.b_green, c.end),
|
|
"",
|
|
""
|
|
]
|
|
for x in helptext:
|
|
print('{:8} {}'.format(' ',x))
|
|
|
|
def print_ruler():
|
|
width = os.get_terminal_size()[0] - 9
|
|
counter = " "
|
|
ticker = " "
|
|
current = 5
|
|
while current < width - 5:
|
|
counter += "{:5}".format(current)
|
|
ticker += "....|"
|
|
current += 5
|
|
print(counter)
|
|
print(ticker)
|
|
|
|
def print_banner(fn):
|
|
print('\n Chalk 1.0 by sloum')
|
|
print('\n{} Writing:{} {}{}'.format(c.yellow, c.white, fn, c.end))
|
|
print(" For a command list, enter {}!?\n{}".format(c.green, c.end))
|
|
|
|
|
|
def chalk(path):
|
|
abspath = os.path.abspath(path)
|
|
try:
|
|
validate_path(abspath)
|
|
with open(os.path.abspath(abspath), 'r') as f:
|
|
content = f.read().split('\n')
|
|
if content[-1] == '':
|
|
content.pop()
|
|
except FileNotFoundError:
|
|
content = []
|
|
|
|
filename = path.split('/')[-1]
|
|
|
|
edited_file = False
|
|
|
|
print_banner(filename)
|
|
print_ruler()
|
|
|
|
while True:
|
|
ln = input('{:6} {}>{} '.format(len(content), c.yellow, c.end))
|
|
if ln == '.':
|
|
break
|
|
elif ln == '!?':
|
|
print_help()
|
|
elif ln == '!g':
|
|
print_ruler()
|
|
elif ln == '!d':
|
|
print('\n - - -')
|
|
for i, x in enumerate(content):
|
|
print('{:6} - {}{}{}'.format(i, c.green, x, c.end))
|
|
print(' - - -\n')
|
|
elif ln == '!x':
|
|
print('\n{:8} {}Enter line to delete, for a range enter the start and\n{:8} end separated by a space. To cancel, enter -1.\n{:8} Deletion cannot be undone. Be careful.{}'.format(' ', c.cyan, ' ', ' ', c.end))
|
|
delete = input('{:6} {}>{} '.format(' ', c.b_red, c.end))
|
|
entry = delete.split(' ')
|
|
try:
|
|
beg = int(entry[0])
|
|
end = beg + 1
|
|
if len(entry) > 1:
|
|
end = int(entry[1]) + 1
|
|
if beg < 0:
|
|
continue
|
|
content = content[:beg] + content[end:]
|
|
edited_file = True
|
|
except:
|
|
print('{}{:8} Invalid entry{}'.format(c.red, ' ', c.end))
|
|
print('')
|
|
elif ln == '!i':
|
|
print('\n{:8} {}Enter the line number that you want the new line(s) to\n{:8} appear after.To insert multiple lines enter the starting\n{:8} point and the number of lines separated by a space.\n{:8}To cancel, enter -1{}'.format(' ', c.cyan,' ', ' ', ' ', c.end))
|
|
insert = input('{:6} {}>{} '.format(' ', c.b_green, c.end))
|
|
entry = insert.split(' ')
|
|
try:
|
|
beg = int(entry[0]) + 1
|
|
count = 1
|
|
if len(entry) > 1:
|
|
count = int(entry[1])
|
|
if beg < 0 or count < 1:
|
|
continue
|
|
while count > 0:
|
|
content.insert(beg,'')
|
|
count -= 1
|
|
edited_file = True
|
|
except:
|
|
print('{}{:8} Invalid entry{}'.format(c.red, ' ', c.end))
|
|
print('')
|
|
elif ln == '!v':
|
|
print('\n{:8} {}Enter the start & end, separated by a space, to indicate\n{:8} the range you wish to view. To cancel, enter: -1{}'.format(' ', c.cyan, ' ', c.end))
|
|
view = input('{:6} {}>{}'.format(' ', c.yellow, c.end))
|
|
entry = view.split(' ')
|
|
try:
|
|
beg = int(entry[0])
|
|
if beg < 0:
|
|
continue
|
|
|
|
if len(entry) < 2 or beg > int(entry[1]):
|
|
print('{}{:8} Invalid entry, must provide two numbers separated by a space{}'.format(c.red, ' ', c.end))
|
|
continue
|
|
end = int(entry[1])
|
|
end = end if end < len(content) else len(content) - 1
|
|
if beg > end:
|
|
print('{}{:8} Invalid entry, start position must be less than end position.{}'.format(c.red, ' ', c.end))
|
|
continue
|
|
else:
|
|
for x in range(beg, end + 1):
|
|
print('{:6} - {}{}{}'.format(x, c.green, content[x], c.end))
|
|
print('')
|
|
print_ruler()
|
|
|
|
except:
|
|
print('{}{:8} Invalid entry{}'.format(c.red, ' ', c.end))
|
|
elif re.match(r'^\!\d+$',ln):
|
|
try:
|
|
row = int(ln[1:])
|
|
newln = input_editable('{:6} {}>{} '.format(row, c.b_blue, c.end), content[row])
|
|
if newln == '!c':
|
|
print('{:8} Cancelled...'.format(' '))
|
|
else:
|
|
content[row] = newln
|
|
edited_file = True
|
|
except:
|
|
print('{}{:8} Invalid entry!{}'.format(c.b_red, ' ', c.end))
|
|
print('')
|
|
print_ruler()
|
|
else:
|
|
edited_file = True
|
|
content.append(ln)
|
|
edited_file = True
|
|
|
|
if not edited_file:
|
|
print('{}exiting...{}\n'.format(c.white, c.end))
|
|
sys.exit(0)
|
|
|
|
confirmation = ''
|
|
while confirmation.lower() not in ['y','yes','n','no']:
|
|
confirmation = input('{}Save {}?{} (Y/n) '.format(c.b_green, filename, c.end))
|
|
|
|
if not len(confirmation):
|
|
continue
|
|
|
|
if confirmation.lower()[0] == 'y':
|
|
print('{}saving...{}'.format(c.white, c.end))
|
|
text = '\n'.join(content)
|
|
text += '\n'
|
|
try:
|
|
with open(abspath, 'w') as f:
|
|
f.write(text)
|
|
print('Saved \033[1m{}\033[0m'.format(abspath))
|
|
sys.exit(0)
|
|
except PermissionError:
|
|
print('{} You do not have permission to write to this file.{}'.format(c.red, c.end))
|
|
sys.exit(1)
|
|
else:
|
|
print('{}exiting...{}\n'.format(c.white, c.end))
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
args = sys.argv
|
|
if len(args) < 2:
|
|
print('Incorrect number of arguments.')
|
|
sys.exit(1)
|
|
filepath = args[1]
|
|
readline.parse_and_bind('set editing-mode emacs')
|
|
readline.parse_and_bind('set show-mode-in-prompt off')
|
|
chalk(filepath)
|