primitive, awful text formatting
parent
28680865ee
commit
b731ab69fa
|
@ -408,7 +408,7 @@ class BBJ(object):
|
|||
return response["data"], response["usermap"]
|
||||
|
||||
|
||||
def thread_load(self, thread_id):
|
||||
def thread_load(self, thread_id, format=None):
|
||||
"""
|
||||
Returns a tuple where [0] is a thread object and [1] is a usermap object.
|
||||
|
||||
|
@ -419,5 +419,5 @@ class BBJ(object):
|
|||
print(usermap[author_id]["user_name"])
|
||||
print(message["body"])
|
||||
"""
|
||||
response = self("thread_load", thread_id=thread_id)
|
||||
response = self("thread_load", format=format, thread_id=thread_id)
|
||||
return response["data"], response["usermap"]
|
||||
|
|
|
@ -62,6 +62,11 @@ welcome = """>>> Welcome to Bulletin Butter & Jelly! ------------------@
|
|||
@_________________________________________________________@
|
||||
"""
|
||||
|
||||
colornames = [
|
||||
"none", "red", "yellow", "green", "blue",
|
||||
"cyan", "magenta"
|
||||
]
|
||||
|
||||
colors = [
|
||||
"\033[1;31m", "\033[1;33m", "\033[1;33m",
|
||||
"\033[1;32m", "\033[1;34m", "\033[1;35m"
|
||||
|
@ -90,10 +95,13 @@ class App(object):
|
|||
("default", "default", "default"),
|
||||
("bar", "light magenta", "default"),
|
||||
("button", "light red", "default"),
|
||||
("quote", "light green,underline", "default"),
|
||||
("opt_prompt", "black", "light gray"),
|
||||
("opt_header", "light cyan", "default"),
|
||||
("hover", "light cyan", "default"),
|
||||
("dim", "dark gray", "default"),
|
||||
("bold", "default,bold", "default"),
|
||||
("underline", "default,underline", "default"),
|
||||
|
||||
# map the bbj api color values for display
|
||||
("0", "default", "default"),
|
||||
|
@ -258,7 +266,7 @@ class App(object):
|
|||
return [
|
||||
head,
|
||||
urwid.Divider(),
|
||||
MessageBody(message["body"]),
|
||||
urwid.Columns([(self.prefs["max_text_width"], MessageBody(message["body"]))]),
|
||||
urwid.Divider(),
|
||||
urwid.AttrMap(urwid.Divider("-"), "dim")
|
||||
]
|
||||
|
@ -333,7 +341,7 @@ class App(object):
|
|||
if self.mode == "index":
|
||||
self.last_pos = self.loop.widget.body.base_widget.get_focus()[1]
|
||||
self.mode = "thread"
|
||||
thread, usermap = network.thread_load(thread_id)
|
||||
thread, usermap = network.thread_load(thread_id, format="sequential")
|
||||
self.usermap.update(usermap)
|
||||
self.thread = thread
|
||||
self.walker.clear()
|
||||
|
@ -480,6 +488,11 @@ class App(object):
|
|||
widget.set_text(rendered)
|
||||
|
||||
|
||||
def edit_width(self, editor, content):
|
||||
self.prefs["max_text_width"] = \
|
||||
int(content) if content else 0
|
||||
bbjrc("update", **self.prefs)
|
||||
|
||||
|
||||
def options_menu(self):
|
||||
"""
|
||||
|
@ -488,14 +501,13 @@ class App(object):
|
|||
"""
|
||||
editor_buttons = []
|
||||
edit_mode = []
|
||||
user_colors = []
|
||||
|
||||
if network.user_auth:
|
||||
account_message = "Logged in as %s." % network.user_name
|
||||
colors = ["None", "Red", "Yellow", "Green", "Blue", "Cyan", "Magenta"]
|
||||
for index, color in enumerate(colors):
|
||||
user_colors = []
|
||||
for index, color in enumerate(colornames):
|
||||
urwid.RadioButton(
|
||||
user_colors, color,
|
||||
user_colors, color.title(),
|
||||
network.user["color"] == index,
|
||||
self.set_color, index)
|
||||
|
||||
|
@ -513,11 +525,11 @@ class App(object):
|
|||
account_stuff = [urwid.Button("Login/Register", on_press=self.relog)]
|
||||
|
||||
time_box = urwid.Text(self.timestring(time(), "time"))
|
||||
time_edit = urwid.Edit(edit_text=self.prefs["time"])
|
||||
time_edit = Prompt(edit_text=self.prefs["time"])
|
||||
urwid.connect_signal(time_edit, "change", self.live_time_render, (time_box, "time"))
|
||||
|
||||
date_box = urwid.Text(self.timestring(time(), "date"))
|
||||
date_edit = urwid.Edit(edit_text=self.prefs["date"])
|
||||
date_edit = Prompt(edit_text=self.prefs["date"])
|
||||
urwid.connect_signal(date_edit, "change", self.live_time_render, (date_box, "date"))
|
||||
|
||||
time_stuff = [
|
||||
|
@ -528,7 +540,10 @@ class App(object):
|
|||
date_box, urwid.AttrMap(date_edit, "opt_prompt"),
|
||||
]
|
||||
|
||||
editor_display = urwid.Edit(edit_text=self.prefs["editor"])
|
||||
width_edit = urwid.IntEdit(default=self.prefs["max_text_width"])
|
||||
urwid.connect_signal(width_edit, "change", self.edit_width)
|
||||
|
||||
editor_display = Prompt(edit_text=self.prefs["editor"])
|
||||
urwid.connect_signal(editor_display, "change", self.set_new_editor, editor_buttons)
|
||||
for editor in editors:
|
||||
urwid.RadioButton(
|
||||
|
@ -564,6 +579,9 @@ class App(object):
|
|||
urwid.Divider(),
|
||||
*time_stuff,
|
||||
urwid.Divider(),
|
||||
urwid.Text(("button", "Max message width:")),
|
||||
urwid.AttrMap(width_edit, "opt_prompt"),
|
||||
urwid.Divider(),
|
||||
urwid.Text(("button", "Text editor:")),
|
||||
urwid.Text("You can type in your own command or use one of these presets."),
|
||||
urwid.Divider(),
|
||||
|
@ -587,7 +605,7 @@ class App(object):
|
|||
align="center",
|
||||
valign="middle",
|
||||
width=30,
|
||||
height=(self.loop.screen_size[1] - 10)
|
||||
height=("relative", 75)
|
||||
)
|
||||
|
||||
|
||||
|
@ -681,8 +699,8 @@ class App(object):
|
|||
self.loop.widget,
|
||||
align="center",
|
||||
valign="middle",
|
||||
width=self.loop.screen_size[0] - 2,
|
||||
height=(self.loop.screen_size[1] - 4))
|
||||
width=("relative", 90),
|
||||
height=("relative", 80))
|
||||
|
||||
elif self.mode == "thread":
|
||||
self.window_split=True
|
||||
|
@ -701,10 +719,87 @@ class App(object):
|
|||
|
||||
|
||||
class MessageBody(urwid.Text):
|
||||
pass
|
||||
def __init__(self, text_objects):
|
||||
result = []
|
||||
last_directive = None
|
||||
for paragraph in text_objects:
|
||||
for directive, body in paragraph:
|
||||
|
||||
if directive in colornames:
|
||||
color = str(colornames.index(directive))
|
||||
result.append((color, body))
|
||||
|
||||
elif directive in ["underline", "bold"]:
|
||||
result.append((directive, body))
|
||||
|
||||
elif directive == "linequote":
|
||||
if directive != last_directive and result[-1][-1][-1] != "\n":
|
||||
result.append(("default", "\n"))
|
||||
result.append(("3", "%s\n" % body.strip()))
|
||||
|
||||
elif directive == "quote":
|
||||
result.append(("quote", ">>%s" % body))
|
||||
|
||||
elif directive == "rainbow":
|
||||
color = 1
|
||||
for char in body:
|
||||
if color == 7:
|
||||
color = 1
|
||||
result.append((str(color), char))
|
||||
color += 1
|
||||
|
||||
else:
|
||||
result.append(("default", body))
|
||||
last_directive = directive
|
||||
|
||||
result.append("\n\n")
|
||||
result.pop()
|
||||
super(MessageBody, self).__init__(result)
|
||||
|
||||
|
||||
class FootPrompt(urwid.Edit):
|
||||
class Prompt(urwid.Edit):
|
||||
"""
|
||||
Supports basic bashmacs keybinds. Key casing is
|
||||
ignored and ctrl/alt are treated the same. Only
|
||||
character-wise (not word-wise) movements are
|
||||
implemented.
|
||||
"""
|
||||
def keypress(self, size, key):
|
||||
if not super(Prompt, self).keypress(size, key):
|
||||
return
|
||||
elif key[0:4] not in ["meta", "ctrl"]:
|
||||
return key
|
||||
|
||||
column = self.get_cursor_coords((app.loop.screen_size[0],))[0]
|
||||
text = self.get_edit_text()
|
||||
key = key[-1].lower()
|
||||
|
||||
if key == "u":
|
||||
self.set_edit_pos(0)
|
||||
self.set_edit_text(text[column:])
|
||||
|
||||
elif key == "k":
|
||||
self.set_edit_text(text[:column])
|
||||
|
||||
elif key == "f":
|
||||
self.keypress(size, "right")
|
||||
|
||||
elif key == "b":
|
||||
self.keypress(size, "left")
|
||||
|
||||
elif key == "a":
|
||||
self.set_edit_pos(0)
|
||||
|
||||
elif key == "e":
|
||||
self.set_edit_pos(len(text))
|
||||
|
||||
elif key == "d":
|
||||
self.set_edit_text(text[0:column] + text[column+1:])
|
||||
|
||||
return key
|
||||
|
||||
|
||||
class FootPrompt(Prompt):
|
||||
def __init__(self, callback, *callback_args):
|
||||
super(FootPrompt, self).__init__()
|
||||
self.callback = callback
|
||||
|
@ -728,6 +823,10 @@ class ExternalEditor(urwid.Terminal):
|
|||
self.endpoint = endpoint
|
||||
self.params = params
|
||||
env = os.environ
|
||||
# barring this, programs will happily spit out unicode chars which
|
||||
# urwid+python3 seem to choke on. This seems to be a bug on urwid's
|
||||
# behalf. Users who take issue to programs trying to supress unicode
|
||||
# should use the options menu to switch to Overthrow mode.
|
||||
env.update({"LANG": "POSIX"})
|
||||
command = ["bash", "-c", "{} {}; echo Press any key to kill this window...".format(
|
||||
app.prefs["editor"], self.path)]
|
||||
|
@ -757,6 +856,16 @@ class ExternalEditor(urwid.Terminal):
|
|||
app.switch_editor()
|
||||
|
||||
|
||||
def __del__(self):
|
||||
"""
|
||||
Make damn sure we scoop up after ourselves here...
|
||||
"""
|
||||
try:
|
||||
os.remove(self.path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
class OptionsMenu(urwid.LineBox):
|
||||
def keypress(self, size, key):
|
||||
if key == "esc":
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from src.exceptions import BBJException, BBJParameterError, BBJUserError
|
||||
from src import db, schema
|
||||
from src import db, schema, formatting
|
||||
from functools import wraps
|
||||
from uuid import uuid1
|
||||
import traceback
|
||||
|
@ -244,6 +244,9 @@ class API(object):
|
|||
thread = db.thread_get(database, args["thread_id"])
|
||||
cherrypy.thread_data.usermap = \
|
||||
create_usermap(database, thread["messages"])
|
||||
if args.get("format") == "sequential":
|
||||
formatting.apply_formatting(thread["messages"],
|
||||
formatting.sequential_expressions)
|
||||
return thread
|
||||
|
||||
|
||||
|
|
|
@ -3,34 +3,135 @@ This module is not complete and none of its functions are currently
|
|||
used elsewhere. Subject to major refactoring.
|
||||
"""
|
||||
|
||||
from markdown import markdown
|
||||
from html import escape
|
||||
test = """
|
||||
This is a small paragraph
|
||||
thats divided between a
|
||||
few rows.
|
||||
|
||||
this opens a few linequotes.
|
||||
>this is a few
|
||||
>rows of
|
||||
>sequential line breaks
|
||||
and this is what follows right after
|
||||
"""
|
||||
|
||||
# from markdown import markdown
|
||||
# from html import escape
|
||||
import re
|
||||
|
||||
#0, 1 2 3 4 5 6
|
||||
colors = [
|
||||
#0, 1 2 3 4 5 6
|
||||
"red", "yellow", "green", "blue", "cyan", "magenta"
|
||||
]
|
||||
|
||||
markup = [
|
||||
"bold", "italic", "underline", "strike"
|
||||
"bold", "italic", "underline", "linequote", "quote", "rainbow"
|
||||
]
|
||||
|
||||
tokens = re.compile(r"\[(%s): (.+?)]" % "|".join(colors + markup),
|
||||
flags=re.DOTALL)
|
||||
# tokens being [red: this will be red] and [bold: this will be bold]
|
||||
# tokens = re.compile(r"\[(%s): (.+?)]" % "|".join(colors + markup), flags=re.DOTALL)
|
||||
|
||||
# quotes being references to other post_ids, like >>34 or >>0 for OP
|
||||
quotes = re.compile(">>([0-9]+)")
|
||||
linequotes = re.compile("^(>.+)$",
|
||||
flags=re.MULTILINE)
|
||||
|
||||
# linequotes being chan-style greentext,
|
||||
# >like this
|
||||
linequotes = re.compile("^(>.+)$", flags=re.MULTILINE)
|
||||
|
||||
|
||||
def parse_segments(text, sanitize_linequotes=True):
|
||||
"""
|
||||
Parse linequotes, quotes, and paragraphs into their appropriate
|
||||
representations. Paragraphs are represented as separate strings
|
||||
in the returned list, and quote-types are compiled to their
|
||||
[bracketed] representations.
|
||||
"""
|
||||
result = list()
|
||||
for paragraph in [p.strip() for p in re.split("\n{2,}", text)]:
|
||||
pg = str()
|
||||
for segment in [s.strip() for s in paragraph.split("\n")]:
|
||||
if not segment:
|
||||
continue
|
||||
segment = quotes.sub(lambda m: "[quote: %s]" % m.group(1), segment)
|
||||
if segment.startswith(">"):
|
||||
if sanitize_linequotes:
|
||||
inner = segment.replace("]", "\\]")
|
||||
else:
|
||||
inner = segment
|
||||
segment = "[linequote: %s]" % inner
|
||||
# pg = pg[0:-1]
|
||||
pg += segment
|
||||
else:
|
||||
pg += segment + " "
|
||||
result.append(pg.strip())
|
||||
return result
|
||||
|
||||
|
||||
def sequential_expressions(string):
|
||||
"""
|
||||
Takes a string, sexpifies it, and returns a list of lists
|
||||
who contain tuples. Each list of tuples represents a paragraph.
|
||||
Within each paragraph, [0] is either None or a markup directive,
|
||||
and [1] is the body of text to which it applies. This representation
|
||||
is very easy to handle for a client. It semi-supports nesting:
|
||||
eg, the expression [red: this [blue: is [green: mixed]]] will
|
||||
return [("red", "this "), ("blue", "is "), ("green", "mixed")],
|
||||
but this cannot effectively express an input like
|
||||
[bold: [red: bolded colors.]], in which case the innermost
|
||||
expression will take precedence. For the input:
|
||||
"[bold: [red: this] is some shit [green: it cant handle]]"
|
||||
you get:
|
||||
[('red', 'this'), ('bold', ' is some shit '), ('green', 'it cant handle')]
|
||||
"""
|
||||
# abandon all hope ye who enter here
|
||||
directives = colors + markup
|
||||
result = list()
|
||||
for paragraph in parse_segments(string):
|
||||
stack = [[None, str()]]
|
||||
skip_iters = []
|
||||
nest = [None]
|
||||
escaped = False
|
||||
for index, char in enumerate(paragraph):
|
||||
if skip_iters:
|
||||
skip_iters.pop()
|
||||
continue
|
||||
|
||||
if not escaped and char == "[":
|
||||
directive = paragraph[index+1:paragraph.find(": ", index+1)]
|
||||
open_p = directive in directives
|
||||
else: open_p = False
|
||||
clsd_p = not escaped and nest[-1] != None and char == "]"
|
||||
|
||||
# dont splice other directives into linequotes: that is far
|
||||
# too confusing for the client to determine where to put line
|
||||
# breaks
|
||||
if open_p and nest[-1] != "linequote":
|
||||
stack.append([directive, str()])
|
||||
nest.append(directive)
|
||||
[skip_iters.append(x) for x in range(len(directive)+2)]
|
||||
|
||||
elif clsd_p:
|
||||
nest.pop()
|
||||
stack.append([nest[-1], str()])
|
||||
|
||||
else:
|
||||
escaped = char == "\\"
|
||||
if not (escaped and paragraph[index+1] in "[]"):
|
||||
stack[-1][1] += char
|
||||
# filter out unused stacks, eg ["red", ""]
|
||||
result.append([(directive, body) for directive, body in stack if body])
|
||||
return result
|
||||
|
||||
|
||||
def apply_formatting(msg_obj, formatter):
|
||||
"""
|
||||
Receives a messages object from a thread and returns it with
|
||||
all the message bodies passed through FORMATTER.
|
||||
all the message bodies passed through FORMATTER. Not all
|
||||
formatting functions have to return a string. Refer to the
|
||||
documentation for each formatter.
|
||||
"""
|
||||
for x in range(len(msg_obj)):
|
||||
msg_obj[x]["body"] = formatter(msg_obj[x]["body"])
|
||||
for x, obj in enumerate(msg_obj):
|
||||
msg_obj[x]["body"] = formatter(obj["body"])
|
||||
return msg_obj
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue