else
This commit is contained in:
parent
c0e7e8f5fc
commit
c3c9727d49
18 changed files with 657 additions and 33 deletions
BIN
.GitImages/texturetile01.png
Normal file
BIN
.GitImages/texturetile01.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
.GitImages/texturetile02.png
Normal file
BIN
.GitImages/texturetile02.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
.GitImages/texturetile03.png
Normal file
BIN
.GitImages/texturetile03.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 568 KiB |
BIN
.GitImages/toddo01.png
Normal file
BIN
.GitImages/toddo01.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
54
ArchiveIt/archive_it.py
Normal file
54
ArchiveIt/archive_it.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
import requests
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
HEADERS = {'User-Agent': 'archive_it commandline tool for archive.is submissions v1.1',
|
||||
'Referer': 'http://archive.is/',
|
||||
'Origin': 'http://archive.is'}
|
||||
URL_SUBMIT = 'https://archive.is/submit/'
|
||||
|
||||
def archive(url, anyway=0):
|
||||
data = {'url': url}
|
||||
if anyway is 1:
|
||||
data[anyway] = 1
|
||||
response = requests.post(URL_SUBMIT, data=data, headers=HEADERS)
|
||||
try:
|
||||
if 'link' in response.headers:
|
||||
time = extract_timestamp(response.headers['link'])
|
||||
raise Exception('''
|
||||
Link already archived: %s
|
||||
Pass parameter `anyway=1` to overwrite.
|
||||
''' % time)
|
||||
response = response.headers['refresh']
|
||||
response = response.split(';')
|
||||
for item in response:
|
||||
if 'archive.is' in item:
|
||||
return item.split('=')[1]
|
||||
except:
|
||||
return response
|
||||
|
||||
def extract_timestamp(link):
|
||||
times = link.split(';')
|
||||
d = {}
|
||||
for item in times:
|
||||
if '=' in item:
|
||||
x = items.split('=')
|
||||
d[x[0]] = x[1]
|
||||
return d.get('from', d)
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) == 1:
|
||||
print('Use: > archive_it.py http://www.website.com/page')
|
||||
quit()
|
||||
url = sys.argv[1]
|
||||
try:
|
||||
response = archive(url)
|
||||
if isinstance(response, str):
|
||||
print(response)
|
||||
elif isinstance(response, requests.models.Response):
|
||||
print('Did not get the expected response. Here\'s what we got:')
|
||||
print(response)
|
||||
print(response.headers)
|
||||
print(response.text)
|
||||
except:
|
||||
traceback.print_exc()
|
|
@ -35,20 +35,23 @@ def changebase(number, frombase, tobase):
|
|||
result = int(number, frombase)
|
||||
return basex(result, tobase)
|
||||
|
||||
def intinput(prompt, greaterthan=None):
|
||||
def intinput(prompt, greaterthan=None, notequal=None):
|
||||
''' Prompt for input until the user enters an int '''
|
||||
while True:
|
||||
result = input(prompt)
|
||||
try:
|
||||
i = int(result)
|
||||
if greaterthan is None or i > greaterthan:
|
||||
return i
|
||||
if isinstance(notequal, int) and i == notequal:
|
||||
continue
|
||||
if isinstance(greaterthan, int) and i < greaterthan:
|
||||
continue
|
||||
return i
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def configuration():
|
||||
frombase = intinput(' From base: ', 1)
|
||||
tobase = intinput(' To base: ', 1)
|
||||
tobase = intinput(' To base: ', 1, frombase)
|
||||
lower = intinput('Lower bound: ')
|
||||
upper = intinput('Upper bound: ', lower)
|
||||
return {
|
||||
|
|
|
@ -9,6 +9,7 @@ class DataPoint:
|
|||
self.color_outbound = '#999'
|
||||
self.color_crossbar = '#bbb'
|
||||
self.color_point = '#000'
|
||||
self.color_point_out = '#000'
|
||||
self.crossbar_count = 10
|
||||
self.point_diameter = 4
|
||||
self.margin = 0.10
|
||||
|
@ -27,6 +28,7 @@ class DataPoint:
|
|||
|
||||
self.countdown = -1
|
||||
self.lastbump = 0
|
||||
self.resized = True
|
||||
self.t.configure(bg='#f00')
|
||||
self.t.bind('<Configure>', self.movereplot)
|
||||
self.c = tkinter.Canvas(self.t)
|
||||
|
@ -35,6 +37,8 @@ class DataPoint:
|
|||
self.reset()
|
||||
self.previous_w = self.w
|
||||
self.previous_h = self.h
|
||||
self.previous_x = self.windowx
|
||||
self.previous_y = self.windowy
|
||||
self.clear_screen()
|
||||
self.draw_margin()
|
||||
self._started = autostart
|
||||
|
@ -59,6 +63,14 @@ class DataPoint:
|
|||
return self.h
|
||||
return self.t.winfo_height()
|
||||
|
||||
@property
|
||||
def margin_x(self):
|
||||
return self.window_width * self.margin
|
||||
|
||||
@property
|
||||
def margin_y(self):
|
||||
return self.window_height * self.margin
|
||||
|
||||
def mainloop(self):
|
||||
self._started = True
|
||||
self.t.mainloop()
|
||||
|
@ -68,8 +80,8 @@ class DataPoint:
|
|||
When the user expands the window, replot the graph after a
|
||||
short delay.
|
||||
'''
|
||||
previous = (self.previous_w, self.previous_h)
|
||||
current = (self.window_width, self.window_height)
|
||||
previous = (self.previous_w, self.previous_h, self.previous_x, self.previous_y)
|
||||
current = (self.window_width, self.window_height, self.t.winfo_x(), self.t.winfo_y())
|
||||
now = time.time()
|
||||
if now - self.lastbump < 0.2:
|
||||
# Go away.
|
||||
|
@ -78,8 +90,12 @@ class DataPoint:
|
|||
# Set.
|
||||
self.previous_w = current[0]
|
||||
self.previous_h = current[1]
|
||||
self.previous_x = current[2]
|
||||
self.previous_y = current[3]
|
||||
self.countdown = 1
|
||||
self.lastbump = now
|
||||
if previous[:2] != current[:2]:
|
||||
self.resized = False
|
||||
self.t.after(500, self.movereplot)
|
||||
return
|
||||
if self.countdown > -1:
|
||||
|
@ -89,7 +105,9 @@ class DataPoint:
|
|||
self.t.after(500, self.movereplot)
|
||||
if self.countdown == 0:
|
||||
# Plot.
|
||||
self.plotpoints([])
|
||||
if not self.resized:
|
||||
self.plot_points([])
|
||||
self.resized = True
|
||||
return
|
||||
|
||||
def reset(self):
|
||||
|
@ -105,8 +123,6 @@ class DataPoint:
|
|||
self.span_y = None
|
||||
self.drawable_w = None
|
||||
self.drawable_h = None
|
||||
self.margin_x = self.window_width * self.margin
|
||||
self.margin_y = self.window_height * self.margin
|
||||
self.clear_screen()
|
||||
self.draw_margin()
|
||||
|
||||
|
@ -161,6 +177,9 @@ class DataPoint:
|
|||
hi_x = hi[0]
|
||||
hi_y = hi[1]
|
||||
|
||||
if self.crossbar_count < 1:
|
||||
self.crossbar_count = 1
|
||||
|
||||
if self.highest_x != self.lowest_x:
|
||||
# LOW X
|
||||
self.c.create_text(low_x+5, low_y+5,
|
||||
|
@ -170,8 +189,6 @@ class DataPoint:
|
|||
text=str(round(self.highest_x, 4)), anchor='nw')
|
||||
|
||||
increment_x = (self.highest_x - self.lowest_x) / self.crossbar_count
|
||||
# crossbartop = (self.window_height - self.margin_y) - 5
|
||||
# crossbarbot = (self.window_height - self.margin_y) + 5
|
||||
crossbartop = self.margin_y
|
||||
crossbarbot = self.window_height - self.margin_y
|
||||
for x in range(1, self.crossbar_count):
|
||||
|
@ -190,8 +207,6 @@ class DataPoint:
|
|||
self.c.create_text(low_x-5, hi_y,
|
||||
text=str(round(self.highest_y, 4)), anchor='e')
|
||||
increment_y = (self.highest_y - self.lowest_y) / self.crossbar_count
|
||||
# crossbarlef = self.margin_x - 5
|
||||
# crossbarrgt = self.margin_x + 5
|
||||
crossbarlef = self.margin_x
|
||||
crossbarrgt = self.window_width - self.margin_x
|
||||
for y in range(1, self.crossbar_count):
|
||||
|
@ -215,19 +230,24 @@ class DataPoint:
|
|||
or vice-versa.
|
||||
'''
|
||||
if not reverse:
|
||||
if len(self.POINTS) == 1:
|
||||
return (self.window_width/2, self.window_height/2)
|
||||
# Get percentage of the span
|
||||
x = ((x) - self.lowest_x) / self.span_x
|
||||
y = ((y) - self.lowest_y) / self.span_y
|
||||
# Flip y
|
||||
y = 1 - y
|
||||
# Use the percentage to get a location on the board
|
||||
x *= self.drawable_w
|
||||
y *= self.drawable_h
|
||||
# Put into drawing area
|
||||
x += self.margin_x
|
||||
y += self.margin_y
|
||||
if self.highest_x == self.lowest_x:
|
||||
x = self.window_width / 2
|
||||
else:
|
||||
# Get percentage of the span
|
||||
x = ((x) - self.lowest_x) / self.span_x
|
||||
# Use the percentage to get a location on the board
|
||||
x *= self.drawable_w
|
||||
# Put into drawing area
|
||||
x += self.margin_x
|
||||
|
||||
if self.highest_y == self.lowest_y:
|
||||
y = self.window_height / 2
|
||||
else:
|
||||
y = ((y) - self.lowest_y) / self.span_y
|
||||
# Flip y
|
||||
y = 1 - y
|
||||
y *= self.drawable_h
|
||||
y += self.margin_y
|
||||
|
||||
else:
|
||||
if self.highest_x != self.lowest_x:
|
||||
|
@ -247,12 +267,13 @@ class DataPoint:
|
|||
|
||||
return (x, y)
|
||||
|
||||
def plotpoints(self, points=[]):
|
||||
def plot_points(self, points=[]):
|
||||
'''
|
||||
Plot points onto the canvas.
|
||||
var points = list, where each element is a 2-length tuple, where [0]
|
||||
is x and [1] is y coordinate.
|
||||
'''
|
||||
original_len = len(self.POINTS)
|
||||
for point in points:
|
||||
self.POINTS.add(tuple(point))
|
||||
|
||||
|
@ -292,19 +313,19 @@ class DataPoint:
|
|||
outline=self.color_point)
|
||||
self.c.update()
|
||||
|
||||
def plotpoint(self, x, y):
|
||||
self.plotpoints([[x, y]])
|
||||
def plot_point(self, x, y):
|
||||
self.plot_points([[x, y]])
|
||||
|
||||
def set_origin(self, x, y):
|
||||
self.origin = (x, y)
|
||||
self.plotpoints([])
|
||||
self.plot_points([])
|
||||
|
||||
|
||||
def example(function):
|
||||
dp = DataPoint()
|
||||
points = list(range(100))
|
||||
points = [[p, function(p)] for p in points]
|
||||
dp.plotpoints(points)
|
||||
dp.plot_points(points)
|
||||
dp.mainloop()
|
||||
|
||||
|
||||
|
@ -330,10 +351,9 @@ def example2():
|
|||
(94, 22320), (95, 23703), (96, 40752), (97, 21730), (98, 27637),
|
||||
(99, 45931), (100, 18443), (101, 20048), (102, 18097), (103, 11430)
|
||||
]
|
||||
dp.plotpoints(points)
|
||||
dp.plot_points(points)
|
||||
dp.mainloop()
|
||||
|
||||
|
||||
def examplefunction(x):
|
||||
x -= 50
|
||||
x *= 0.1
|
||||
|
|
4
TKCube/README.md
Normal file
4
TKCube/README.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
TKCube
|
||||
=========
|
||||
|
||||
Not done yet.
|
117
TKCube/tkcube.py
Normal file
117
TKCube/tkcube.py
Normal file
|
@ -0,0 +1,117 @@
|
|||
import copy
|
||||
import math
|
||||
import random
|
||||
import tkinter
|
||||
|
||||
class TKCube:
|
||||
def __init__(self):
|
||||
self.t = tkinter.Tk()
|
||||
self.FACES = [
|
||||
[[2, 2, 1], [2, -2, 1], [-2, -2, 1], [-2, 2, 1]],
|
||||
[[2, -2, 1], [-2, -2, -1], [-2, 2, -1], [2, 2, -1]],
|
||||
[[-2, -2, 1], [2, -2, 1], [2, -2, -1], [-2, -2, -1]],
|
||||
[[-2, 2, 1], [2, 2, 1], [2, 2, -1], [-2, 2, -1]],
|
||||
[[-2, -2, -1], [-2, 2, -1], [-2, 2, 1], [-2, -2, -1]],
|
||||
[[2, -2, 1], [2, 2, 1], [2, 2, -1], [2, -2, -1]],
|
||||
]
|
||||
self.INFLATE_SCALE = 8
|
||||
|
||||
self.c = tkinter.Canvas(self.t, width=600, height=600, bg='#444')
|
||||
self.c.pack(fill='both', expand=True)
|
||||
self.t.bind('<Return>', self.render)
|
||||
self.is_mouse_down = False
|
||||
self.prev_mouse_x = None
|
||||
self.prev_mouse_y = None
|
||||
self.t.bind('<ButtonPress-1>', self.mouse_down)
|
||||
self.t.bind('<ButtonRelease-1>', self.mouse_up)
|
||||
self.t.bind('<Motion>', self.mouse_motion)
|
||||
self.t.bind('<Up>', lambda event: self.arbitrarymove(0, -1))
|
||||
self.t.bind('<Down>', lambda event: self.arbitrarymove(0, 1))
|
||||
self.t.bind('<Left>', lambda event: self.arbitrarymove(-1, 0))
|
||||
self.t.bind('<Right>', lambda event: self.arbitrarymove(1, 0))
|
||||
self.render()
|
||||
self.t.mainloop()
|
||||
|
||||
def arbitrarymove(self, deltax, deltay):
|
||||
for face in self.FACES:
|
||||
for point in face:
|
||||
point[0] += deltax
|
||||
point[1] += deltay
|
||||
self.render()
|
||||
|
||||
def mouse_down(self, event):
|
||||
self.is_mouse_down = True
|
||||
|
||||
def mouse_up(self, event):
|
||||
self.is_mouse_down = False
|
||||
|
||||
def mouse_motion(self, event):
|
||||
if not self.is_mouse_down:
|
||||
return
|
||||
if self.prev_mouse_x is None:
|
||||
self.prev_mouse_x = event.x
|
||||
self.prev_mouse_y = event.y
|
||||
distance = math.sqrt( ((event.x - self.prev_mouse_x) ** 2) + ((event.y - self.prev_mouse_y) ** 2) )
|
||||
self.prev_mouse_x = event.x
|
||||
self.prev_mouse_y = event.y
|
||||
print(distance)
|
||||
|
||||
def center_of_square(self, face):
|
||||
x = 0; y = 0; z = 0
|
||||
for point in face:
|
||||
x += point[0]
|
||||
y += point[1]
|
||||
z += point[2]
|
||||
return [x/4, y/4, z/4]
|
||||
|
||||
def plot_point_screen(self, x, y, diameter=4):
|
||||
radius = diameter / 2
|
||||
x1 = x - radius
|
||||
y1 = y - radius
|
||||
x2 = x + radius
|
||||
y2 = y + radius
|
||||
self.c.create_oval(x1, y1, x2, y2, fill='#000')
|
||||
|
||||
def render(self, *event):
|
||||
self.c.delete('all')
|
||||
rendered_faces = copy.deepcopy(self.FACES)
|
||||
|
||||
# Sort by depth from camera
|
||||
# The sort key is the z value of the coordinate
|
||||
# in the center of the face
|
||||
rendered_faces.sort(key=lambda face: self.center_of_square(face)[2])
|
||||
|
||||
canvas_width_half = self.c.winfo_width() / 2
|
||||
canvas_height_half = self.c.winfo_height() / 2
|
||||
highest_z = max([max([point[2] for point in face]) for face in rendered_faces])
|
||||
for face in self.FACES:
|
||||
for point in face:
|
||||
x = point[0]
|
||||
y = point[1]
|
||||
z = point[2]
|
||||
|
||||
# Push everything away from the camera so all z are <= 0
|
||||
z -= highest_z
|
||||
|
||||
# Create vanishing point.
|
||||
distance_camera = math.sqrt((x**2) + (y**2) + (z**2))
|
||||
#if z != 0:
|
||||
# factor = (abs(z) ** 0.2) - 1
|
||||
# print(factor)
|
||||
#else:
|
||||
# factor = 0
|
||||
x += x * factor
|
||||
y += y * factor
|
||||
|
||||
# Inflate for display
|
||||
x *= self.INFLATE_SCALE
|
||||
y *= self.INFLATE_SCALE
|
||||
z *= self.INFLATE_SCALE
|
||||
|
||||
# Shift the coordinates into the screen
|
||||
x += canvas_width_half
|
||||
y += canvas_height_half
|
||||
self.plot_point_screen(x, y)
|
||||
|
||||
#print(rendered_faces)
|
||||
t = TKCube()
|
21
TextureTile/README.md
Normal file
21
TextureTile/README.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
Texture Tile
|
||||
============
|
||||
|
||||
Requires `pip install pillow`
|
||||
|
||||
|
||||
|
||||
Make it big!
|
||||
<p align="center">
|
||||
<img src="https://github.com/voussoir/else/blob/master/.GitImages/texturetile01.png?raw=true" alt="texture tile"/>
|
||||
</p>
|
||||
|
||||
Make it small!
|
||||
<p align="center">
|
||||
<img src="https://github.com/voussoir/else/blob/master/.GitImages/texturetile02.png?raw=true" alt="texture tile"/>
|
||||
</p>
|
||||
|
||||
Make it any size at all!
|
||||
<p align="center">
|
||||
<img src="https://github.com/voussoir/else/blob/master/.GitImages/texturetile03.png?raw=true" alt="texture tile"/>
|
||||
</p>
|
BIN
TextureTile/examples/example1.png
Normal file
BIN
TextureTile/examples/example1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
BIN
TextureTile/examples/example2.png
Normal file
BIN
TextureTile/examples/example2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 221 KiB |
82
TextureTile/texturetile.pyw
Normal file
82
TextureTile/texturetile.pyw
Normal file
|
@ -0,0 +1,82 @@
|
|||
import os
|
||||
from PIL import Image
|
||||
from PIL import ImageTk
|
||||
import tkinter
|
||||
|
||||
class TextureTile:
|
||||
def __init__(self):
|
||||
self.windowtitle = 'Texture Tile'
|
||||
|
||||
self.t = tkinter.Tk()
|
||||
self.t.title(self.windowtitle)
|
||||
self.w = 450
|
||||
self.h = 475
|
||||
self.screenwidth = self.t.winfo_screenwidth()
|
||||
self.screenheight = self.t.winfo_screenheight()
|
||||
self.windowwidth = self.w
|
||||
self.windowheight = self.h
|
||||
self.windowx = (self.screenwidth-self.windowwidth) / 2
|
||||
self.windowy = ((self.screenheight-self.windowheight) / 2) - 27
|
||||
self.geometrystring = '%dx%d+%d+%d' % (self.windowwidth, self.windowheight, self.windowx, self.windowy)
|
||||
self.t.geometry(self.geometrystring)
|
||||
|
||||
self.entry_filename = tkinter.Entry(self.t, font=('Consolas', 12))
|
||||
self.button_load = tkinter.Button(self.t, text='Load', command=self.file_load_display)
|
||||
self.frame_filearea = tkinter.Frame(self.t)
|
||||
self.label_image = tkinter.Label(self.frame_filearea, bg='#222')
|
||||
|
||||
self.t.columnconfigure(0, weight=1)
|
||||
self.t.rowconfigure(1, weight=1)
|
||||
self.entry_filename.grid(row=0, column=0, sticky='ew')
|
||||
self.button_load.grid(row=0, column=1, sticky='ne')
|
||||
self.frame_filearea.grid(row=1, column=0, columnspan=2, sticky='nsew')
|
||||
self.label_image.pack(expand=True, fill='both')
|
||||
#self.entry_filename.pack(fill='x')
|
||||
#self.button_load.pack()
|
||||
#self.label_image.pack()
|
||||
|
||||
self.entry_filename.insert(0, os.getcwd())
|
||||
self.entry_filename.bind('<Return>', self.file_load_display)
|
||||
self.entry_filename.focus_set()
|
||||
|
||||
self.t.mainloop()
|
||||
|
||||
def file_load_display(self, *event):
|
||||
filename = self.entry_filename.get()
|
||||
# Open file or turn red
|
||||
try:
|
||||
image = Image.open(filename)
|
||||
self.entry_filename.configure(bg='#fff')
|
||||
except FileNotFoundError:
|
||||
self.entry_filename.configure(bg='#f00')
|
||||
return
|
||||
|
||||
# 9x the image
|
||||
w = image.size[0]
|
||||
h = image.size[1]
|
||||
expanded = image.copy()
|
||||
expanded = expanded.resize((w * 3, h * 3))
|
||||
for x in range(3):
|
||||
for y in range(3):
|
||||
expanded.paste(image, (w*x, h*y))
|
||||
|
||||
# Resize 9x'ed image into frame
|
||||
w = expanded.size[0]
|
||||
h = expanded.size[1]
|
||||
fw = self.label_image.winfo_width()
|
||||
fh = self.label_image.winfo_height()
|
||||
ratio = min(fw/w, fh/h)
|
||||
|
||||
w = int(w * ratio)
|
||||
h = int(h * ratio)
|
||||
|
||||
expanded = expanded.resize((w, h))
|
||||
image = ImageTk.PhotoImage(expanded)
|
||||
self.label_image.configure(image=image)
|
||||
self.label_image.dont_garbage_me_bro = image
|
||||
r'C:\Users\Owner\AppData\Roaming\.MinecraftMulti\instances\Chocolate\minecraft\texturepacks\SixtyFox\textures\blocks\melon_top.png'
|
||||
r'C:\users\owner\desktop\pi\imergers\ear.jpg'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
t = TextureTile()
|
9
Toddo/README.md
Normal file
9
Toddo/README.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
Toddo
|
||||
======
|
||||
|
||||
Commandline to-do-list manager.
|
||||
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/voussoir/else/blob/master/.GitImages/toddo01.png?raw=true" alt="to-do list manager"/>
|
||||
</p>
|
BIN
Toddo/toddo.db
Normal file
BIN
Toddo/toddo.db
Normal file
Binary file not shown.
314
Toddo/toddo.py
Normal file
314
Toddo/toddo.py
Normal file
|
@ -0,0 +1,314 @@
|
|||
import datetime
|
||||
import shutil
|
||||
import sqlite3
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
SQL_ID = 0
|
||||
SQL_TODOTABLE = 1
|
||||
SQL_CREATED = 2
|
||||
SQL_MESSAGE = 3
|
||||
|
||||
HELP_FULL = [
|
||||
('> toddo', 'Display the todos from the current table'),
|
||||
('> toddo all', 'Display the todos from all tables'),
|
||||
('> toddo 4', 'Display the todo with ID 4'),
|
||||
('> toddo add', 'Add a new todo via multi-line typing prompt'),
|
||||
('> toddo add "message"', 'Add a new todo with this message'),
|
||||
('> toddo remove 8', 'Remove the todo with ID 8'),
|
||||
('> toddo table', 'Display the name of the current table'),
|
||||
('> toddo table name', 'Switch to the table named "name"')
|
||||
]
|
||||
|
||||
HELP_NOENTRIES = '''Your todo list is empty!
|
||||
Use `toddo add` or `toddo add "message"` to make entries.'''
|
||||
|
||||
HELP_NOACTIVE = '''Table `%s` has no entries!
|
||||
Use `toddo all` to see if there are entries for other tables.'''
|
||||
|
||||
HELP_REMOVE = '''Provide an ID number to remove.'''
|
||||
|
||||
class ToddoExc(Exception):
|
||||
pass
|
||||
|
||||
class Toddo():
|
||||
def __init__(self, dbname='C:/git/else/toddo/toddo.db'):
|
||||
self.sql = sqlite3.connect(dbname)
|
||||
self.cur = self.sql.cursor()
|
||||
self.cur.execute('CREATE TABLE IF NOT EXISTS meta(key TEXT, val TEXT)')
|
||||
self.cur.execute('CREATE TABLE IF NOT EXISTS todos(id INT, todotable TEXT, created INT, message TEXT)')
|
||||
self.cur.execute('CREATE INDEX IF NOT EXISTS todoindex on todos(id)')
|
||||
|
||||
def add_todo(self, message=None):
|
||||
'''
|
||||
Create new entry in the database on the active todotable.
|
||||
'''
|
||||
if message is None:
|
||||
message = multi_line_input()
|
||||
message = str(message)
|
||||
if message is '':
|
||||
raise ToddoExc('Todos cannot be blank.')
|
||||
|
||||
todoid = self.increment_lastid()
|
||||
todotable = self.get_todotable()
|
||||
created = int(datetime.datetime.now(datetime.timezone.utc).timestamp())
|
||||
|
||||
self.cur.execute('INSERT INTO todos VALUES(?, ?, ?, ?)', [todoid, todotable, created, message])
|
||||
self.sql.commit()
|
||||
return todoid
|
||||
|
||||
def remove_todo(self, idnumber):
|
||||
'''
|
||||
Drop todo from the database.
|
||||
'''
|
||||
idnumber = int(idnumber)
|
||||
self.cur.execute('SELECT * FROM todos WHERE id=?', [idnumber])
|
||||
todo = self.cur.fetchone()
|
||||
if todo is None:
|
||||
raise ToddoExc('Todo %d does not exist.' % idnumber)
|
||||
activetable = self.get_todotable()
|
||||
requestedtable = todo[SQL_TODOTABLE]
|
||||
if requestedtable.lower() != activetable.lower():
|
||||
raise ToddoExc('Todo %d is not part of the active table `%s`. It belongs to `%s`.' % (idnumber, activetable, requestedtable))
|
||||
self.cur.execute('DELETE FROM todos WHERE id=?', [idnumber])
|
||||
self.sql.commit()
|
||||
return idnumber
|
||||
|
||||
def display_one_todo(self, idnumber):
|
||||
'''
|
||||
Make a nice display that shows a todo's entire contents.
|
||||
'''
|
||||
self.cur.execute('SELECT * FROM todos WHERE id=?', [idnumber])
|
||||
todo = self.cur.fetchone()
|
||||
if todo is None:
|
||||
raise ToddoExc('Todo %d does not exist.' % idnumber)
|
||||
|
||||
message = todo[SQL_MESSAGE]
|
||||
messageleft = len('Message: ')
|
||||
width = shutil.get_terminal_size()[0] - (messageleft + 1)
|
||||
message = nicewrap(message, width, messageleft)
|
||||
|
||||
return 'ID: %d\nTable: %s\nCreated: %s\nMessage: %s' % (
|
||||
todo[SQL_ID], todo[SQL_TODOTABLE], human(todo[SQL_CREATED]), message)
|
||||
|
||||
def display_active_todos(self):
|
||||
'''
|
||||
Pass the active table name into display_todos_from_table
|
||||
'''
|
||||
todotable = self.get_todotable()
|
||||
return self.display_todos_from_table(todotable)
|
||||
|
||||
def display_todos_from_table(self, todotable):
|
||||
'''
|
||||
Make a nice display from the database.
|
||||
'''
|
||||
if todotable is None:
|
||||
self.cur.execute('SELECT * FROM todos ORDER BY id ASC')
|
||||
else:
|
||||
self.cur.execute('SELECT * FROM todos WHERE todotable=? ORDER BY id ASC', [todotable])
|
||||
todos = self.cur.fetchall()
|
||||
if len(todos) == 0:
|
||||
return None
|
||||
|
||||
todos = [list(x) for x in todos]
|
||||
|
||||
longest_id = max(len(str(x[SQL_ID])) for x in todos)
|
||||
longest_table = max(len(str(x[SQL_TODOTABLE])) for x in todos)
|
||||
|
||||
display = []
|
||||
for todo in todos:
|
||||
todoid = str(todo[SQL_ID])
|
||||
todoid = (' '*(longest_id-len(todoid))) + todoid
|
||||
|
||||
timestamp = human(todo[SQL_CREATED])
|
||||
|
||||
todotable = todo[SQL_TODOTABLE]
|
||||
todotable = (' '*(longest_table-len(todotable))) + todotable
|
||||
|
||||
message = todo[SQL_MESSAGE]
|
||||
if '\n' in message:
|
||||
message = message.split('\n')[0] + ' ...'
|
||||
|
||||
total = '%s : %s : %s : %s' % (todoid, todotable, timestamp, message)
|
||||
terminal_width = shutil.get_terminal_size()[0]
|
||||
if len(total) > terminal_width:
|
||||
total = total[:(terminal_width-(len(total)+4))] + '...'
|
||||
display.append(total)
|
||||
|
||||
return '\n'.join(display)
|
||||
|
||||
def switch_todotable(self, newtable=None):
|
||||
'''
|
||||
Update the meta with `newtable` as the new active todotable.
|
||||
'''
|
||||
self.cur.execute('SELECT val FROM meta WHERE key="todotable"')
|
||||
activetable = self.cur.fetchone()
|
||||
if not activetable:
|
||||
activetable = self._install_default_todotable()
|
||||
else:
|
||||
activetable = activetable[0]
|
||||
if newtable is None:
|
||||
return activetable
|
||||
self.cur.execute('UPDATE meta SET val=? WHERE key="todotable"', [newtable])
|
||||
self.sql.commit()
|
||||
return newtable
|
||||
|
||||
def increment_lastid(self, increment=False):
|
||||
'''
|
||||
Increment the lastid in the meta table, THEN return it.
|
||||
'''
|
||||
self.cur.execute('SELECT val FROM meta WHERE key="lastid"')
|
||||
lastid = self.cur.fetchone()
|
||||
if lastid is None:
|
||||
self._install_default_lastid()
|
||||
return 1
|
||||
else:
|
||||
lastid = int(lastid[0]) + 1
|
||||
self.cur.execute('UPDATE meta SET val=? WHERE key="lastid"', [lastid])
|
||||
return lastid
|
||||
|
||||
def get_todotable(self):
|
||||
self.cur.execute('SELECT val FROM meta WHERE key="todotable"')
|
||||
todotable = self.cur.fetchone()
|
||||
if todotable is None:
|
||||
self._install_default_todotable()
|
||||
todotable = 'default'
|
||||
else:
|
||||
todotable = todotable[0]
|
||||
return todotable
|
||||
|
||||
def _install_default_lastid(self):
|
||||
'''
|
||||
This method assumes that "lastid" does not already exist.
|
||||
If it does, it's your fault for calling this.
|
||||
'''
|
||||
self.cur.execute('INSERT INTO meta VALUES("lastid", 1)')
|
||||
self.sql.commit()
|
||||
return 1
|
||||
|
||||
def _install_default_todotable(self):
|
||||
'''
|
||||
This method assumes that "todotable" does not already exist.
|
||||
If it does, it's your fault for calling this.
|
||||
'''
|
||||
self.cur.execute('INSERT INTO meta VALUES("todotable", "default")')
|
||||
self.sql.commit()
|
||||
return 'default'
|
||||
|
||||
def human(timestamp):
|
||||
timestamp = datetime.datetime.utcfromtimestamp(timestamp)
|
||||
timestamp = datetime.datetime.strftime(timestamp, '%d %b %Y %H:%M')
|
||||
return timestamp
|
||||
|
||||
def multi_line_input():
|
||||
print('Submit a ctrl+z to finish typing.')
|
||||
userinput = ''
|
||||
ctrlz = '\x1a'
|
||||
while True:
|
||||
try:
|
||||
additional = input('- ')
|
||||
except EOFError:
|
||||
# If you only enter a ctrlz
|
||||
return userinput
|
||||
|
||||
if ctrlz in additional:
|
||||
additional = additional.split(ctrlz)[0]
|
||||
userinput += additional
|
||||
break
|
||||
|
||||
userinput += additional + '\n'
|
||||
|
||||
return userinput.strip()
|
||||
|
||||
def nicewrap(message, width, paddingleft):
|
||||
# http://stackoverflow.com/a/26538082 ##########################
|
||||
message = '\n'.join(['\n'.join(textwrap.wrap(line, width,#######
|
||||
break_long_words=True, replace_whitespace=False))##########
|
||||
for line in message.split('\n')])##########################
|
||||
################################################################
|
||||
message = message.strip()
|
||||
message = message.replace('\n', '\n' + (' '*paddingleft))
|
||||
return message
|
||||
|
||||
def fullhelp():
|
||||
longestleft = max(len(x[0]) for x in HELP_FULL)
|
||||
width = shutil.get_terminal_size()[0] - 1
|
||||
message = []
|
||||
for item in HELP_FULL:
|
||||
pad = width - (longestleft+ 3)
|
||||
item = '%s : %s' % (item[0] + (' '*(longestleft - len(item[0]))), nicewrap(item[1], pad, longestleft + 3))
|
||||
message.append(item)
|
||||
message = '\n'.join(message)
|
||||
return message
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
toddo = Toddo()
|
||||
|
||||
# Look, no more IndexErrors
|
||||
sys.argv += [None]*10
|
||||
|
||||
if isinstance(sys.argv[1], str):
|
||||
sys.argv[1] = sys.argv[1].lower()
|
||||
|
||||
if sys.argv[1] is None:
|
||||
message = toddo.display_active_todos()
|
||||
if message is None:
|
||||
table = toddo.get_todotable()
|
||||
print(HELP_NOACTIVE % table)
|
||||
else:
|
||||
print(message)
|
||||
|
||||
elif sys.argv[1] == 'all':
|
||||
message = toddo.display_todos_from_table(None)
|
||||
if message is None:
|
||||
print(HELP_NOENTRIES)
|
||||
else:
|
||||
print(message)
|
||||
|
||||
elif sys.argv[1] == 'add':
|
||||
args = list(filter(None, sys.argv))
|
||||
args = args[2:]
|
||||
args = ' '.join(args)
|
||||
if args == '':
|
||||
args = None
|
||||
message = toddo.add_todo(args)
|
||||
if isinstance(message, int):
|
||||
print('Added %d' % message)
|
||||
|
||||
elif sys.argv[1] == 'remove':
|
||||
idnumber = sys.argv[2]
|
||||
if idnumber is None or not idnumber.replace(',', '').isdigit():
|
||||
print(HELP_REMOVE)
|
||||
else:
|
||||
message = []
|
||||
ids = [int(x) for x in idnumber.split(',')]
|
||||
for x in ids:
|
||||
try:
|
||||
t = toddo.remove_todo(x)
|
||||
message.append('Removed %d' % t)
|
||||
except ToddoExc as e:
|
||||
message.append(e.args[0])
|
||||
print('\n'.join(message))
|
||||
|
||||
elif sys.argv[1] == 'table':
|
||||
currenttable = toddo.get_todotable()
|
||||
message = toddo.switch_todotable(sys.argv[2])
|
||||
if currenttable == message:
|
||||
print('You are on table `%s`' % message)
|
||||
else:
|
||||
print('Switched to table `%s`' % message)
|
||||
|
||||
elif sys.argv[1].isdigit():
|
||||
try:
|
||||
message = toddo.display_one_todo(int(sys.argv[1]))
|
||||
print(message)
|
||||
except ToddoExc as e:
|
||||
print(e.args[0])
|
||||
|
||||
elif sys.argv[1] == 'help':
|
||||
print(fullhelp())
|
||||
|
||||
else:
|
||||
print('Command not recognized.')
|
||||
print(fullhelp())
|
Loading…
Reference in a new issue