primitive, awful text formatting
This commit is contained in:
		
							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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user