Upload files to ''

This commit is contained in:
simejo 2021-11-20 23:22:06 +00:00
parent 3f2c06e06f
commit 35abc07e2d
5 changed files with 421 additions and 0 deletions

58
ankiconnect.py Normal file
View File

@ -0,0 +1,58 @@
import json
import urllib.request
import sys
import tempfile
import os
def request(action, **params):
return {'action': action, 'params': params, 'version': 6}
def invoke(action, **params):
requestJson = json.dumps(request(action, **params)).encode('utf-8')
response = json.load(urllib.request.urlopen(urllib.request.Request('http://localhost:8765', requestJson)))
if len(response) != 2:
raise Exception('response has an unexpected number of fields')
if 'error' not in response:
raise Exception('response is missing required error field')
if 'result' not in response:
raise Exception('response is missing required result field')
if response['error'] is not None:
raise Exception(response['error'])
return response['result']
def getNoteTypes():
return invoke('modelNames')
def getFields(noteType):
return invoke('modelFieldNames', modelName=noteType)
def getDecks():
return invoke('deckNames')
def getCards(noteType):
cards = invoke('modelTemplates', modelName=noteType)
return [card for card in cards]
def getTemplate(noteType, card):
cards = invoke('modelTemplates', modelName=noteType)
style = invoke('modelStyling', modelName=noteType)
return cards[card], style['css']
def getTags():
return invoke('getTags')
def addNote(note):
options = {
'allowDuplicate': False,
'duplicateScope': note['deckName']
}
note['options'] = options
return invoke('addNote', note=note)
if __name__ == '__main__':
#print(getNoteTypes())
#print(getFields('Ultimate Geography'))
#print(getDecks())
print(getTemplate('Basic'))
#print(getTags())
#print(addNote('Basic', 'Test', {'Front': 'Hello World!', 'Back': 'From Python...'}))

16
keys.py Normal file
View File

@ -0,0 +1,16 @@
QUIT = 'q' # (q)uit
SELECT_NOTE_TYPE = 'n' # select (n)ote type
SELECT_DECK = 'd' # select (d)eck
SELECT_TAGS = 'g' # select ta(g)s
PIN_FIELD = 'p' # (p)in field
ENTER = '\r' # (e)nter
PREVIEW = 'f' # preview (f)ront
PREVIEW_BACK = 'b' # preview (b)ack
ADD = 'a' # (a)dd card
PREV = 'i' # j
NEXT = 't' # k
ESCAPE = '\x1b'

201
main.py Normal file
View File

@ -0,0 +1,201 @@
import os
import sys
import tty
import termios
import signal
import traceback
import shutil
from subprocess import call
from tempfile import NamedTemporaryFile
from keys import *
from menu import Menu
from ankiconnect import *
from preview import previewCard
def preview(*args):
# Get size in pixels
sys.stdout.write('\x1b[16t')
sys.stdout.flush()
report = ''
c = sys.stdin.read(1)
while c != 't':
report += c
c = sys.stdin.read(1)
report = report[2:].split(';')
size = shutil.get_terminal_size((80, 20))
h = int(report[1])*(size[1] - 1)
w = int(report[2])*(size[0]//2 - 1)
previewCard(*args, width=w, height=h, htmlWidth=w)
#sys.stdout.write('\x1b[?25l\x1b[2J')
#sys.stdout.flush()
def redraw(data):
sys.stdout.write('\x1b[2J')
data['menu'].draw()
sys.stdout.flush()
def process(char, data):
def previewSide(side='Back'):
card = getCards(current['modelName'])[0]
template, style = getTemplate(current['modelName'], card)
preview(template[side], style, current['fields'])
def changeView(view):
data['view'] = view
if view == 'selectTags':
menu.setItems(getTags())
menu.setMarked(current['tags'])
elif view == 'selectDeck':
menu.setItems(getDecks())
elif view == 'selectNoteType':
menu.setItems(getNoteTypes())
elif view == 'addNote':
menu.setItems(getFields(current['modelName']))
menu.setMarked(data['pinned'])
menu = data['menu']
current = data['current']
if char == SELECT_NOTE_TYPE:
changeView('selectNoteType')
elif char == SELECT_DECK:
changeView('selectDeck')
elif char == SELECT_TAGS:
changeView('selectTags')
elif char == PIN_FIELD:
if data['view'] == 'addNote':
menu.toggleMark()
if menu.isCurrentMarked():
data['pinned'].append(menu.selected())
else:
data['pinned'].remove(menu.selected())
elif char == ENTER:
if data['view'] == 'selectTags':
menu.toggleMark()
if menu.isCurrentMarked():
current['tags'].append(menu.selected())
else:
current['tags'].remove(menu.selected())
elif data['view'] == 'selectDeck':
current['deckName'] = menu.selected()
changeView('addNote')
elif data['view'] == 'selectNoteType':
current['modelName'] = menu.selected()
data['pinned'] = []
changeView('addNote')
previewSide()
elif data['view'] == 'addNote':
# Open editor
if menu.selected() in current['fields']:
content = current['fields'][menu.selected()]
else:
content = ''
with NamedTemporaryFile(suffix=".html") as tf:
tf.write(bytes(content, encoding='utf-8'))
tf.flush()
call([data['EDITOR'], tf.name])
tf.seek(0)
current['fields'][menu.selected()] = tf.read().decode('utf-8')
sys.stdout.write('\x1b[?25l')
sys.stdout.flush()
previewSide()
elif char == PREVIEW:
previewSide('Front')
elif char == PREVIEW_BACK:
previewSide()
elif char == ADD:
addNote(current)
for field in current['fields']:
if field not in data['pinned']:
current['fields'][field] = ''
previewSide()
elif char == PREV:
menu.prev()
elif char == NEXT:
menu.next()
elif char == ESCAPE:
changeView('addNote')
if data['view'] == 'selectTags':
head = {'Selecting': 'Tags'}
elif data['view'] == 'selectDeck':
head = {'Selecting': 'Deck'}
elif data['view'] == 'selectNoteType':
head = {'Selecting': 'Note type'}
else:
head = {
'Note Type': current['modelName'],
'Deck': current['deckName'],
'Tags': ' '.join(current['tags'])
}
menu.setHead(head)
#sys.stdout.write(f'\x1b[1;1H{repr(char)}')
sys.stdout.flush()
def reset(data):
data['EDITOR'] = os.environ.get('EDITOR', 'vim')
head = {
'Note Type': 'Basic',
'Deck': 'Default',
'Tags': ''
}
data['noteType'] = 'Basic'
data['deck'] = 'Default'
data['view'] = 'addNote' # selectNoteType selectDeck selectTags
data['pinned'] = []
fields = getFields('Basic')
data['current'] = {
'deckName': 'Default',
'modelName': 'Basic',
'fields': {field: '' for field in fields},
'tags': [],
}
data['menu'] = Menu(fields, head=head)
if __name__ == '__main__':
# Setup raw mode
fd = sys.stdin.fileno()
oldSettings = termios.tcgetattr(fd)
tty.setraw(sys.stdin)
try:
# Hide the cursor and clear the screen
sys.stdout.write('\x1b[?25l\x1b[2J')
sys.stdout.flush()
# Data
data = {}
reset(data)
# Redraw on resize
signal.signal(signal.SIGWINCH, lambda signum, frame: redraw(data))
# Read input
process('', data)
while True:
char = sys.stdin.read(1)
if char == QUIT:
break
process(char, data)
# Avoid breaking the terminal after a crash
except Exception:
tb = traceback.format_exc()
else:
tb = ''
# Restore terminal settings
sys.stdout.write('\x1b[?25h\n\r')
termios.tcsetattr(fd, termios.TCSADRAIN, oldSettings)
print(tb)

80
menu.py Normal file
View File

@ -0,0 +1,80 @@
import sys
import shutil
class Menu:
def __init__(self, items, cursor=0, head={}):
self.head = head
self.items = items
self.cursor = cursor
self.marked = []
self.draw()
def setHead(self, head):
self.head = head
self.draw()
def setItems(self, items, cursor=0):
self.__init__(items, cursor)
def toggleMark(self):
if self.cursor in self.marked:
self.marked.remove(self.cursor)
else:
self.marked.append(self.cursor)
self.draw()
def setMarked(self, marked):
self.marked = [i for i in range(len(self.items)) if self.items[i] in marked]
self.draw()
def isCurrentMarked(self):
return self.cursor in self.marked
def isMarked(self, item):
return item in [self.items[i] for i in self.marked]
def next(self):
self.cursor += 1
if self.cursor >= len(self.items):
self.cursor = len(self.items) - 1
self.draw()
def prev(self):
self.cursor -= 1
if self.cursor < 0:
self.cursor = 0
self.draw()
def selected(self):
return self.items[self.cursor]
def draw(self):
size = shutil.get_terminal_size((80, 20))
w = size[0]
# Heading
for i in range(size[1]):
sys.stdout.write(f'\x1b[{i + 1};{w//2 + 1}H'+' '*(w//2))
for row, key in enumerate(self.head):
sys.stdout.write(f'\x1b[{row + 1};{w//2 + 1}H {key}: {self.head[key][:w//2 - len(key) - 3]}\x1b[0m')
offset = len(self.head) + 2
if offset == 2:
offset = 1
h = size[1] - offset
first = 0
if len(self.items) > h:
if self.cursor > (h - offset)/2:
first = self.cursor - h//2 - 1
if first < 0:
first = 0
elif len(self.items) - first < h + 1:
first = len(self.items) - h - 1
for row, item in enumerate(self.items[first:h + first + 1]):
mark = '\x1b[44m+' if row + first in self.marked else ' '
text = mark+item[:w//2 - 1]+' '*(w//2 - 1 - len(item))
if self.cursor == row + first:
sys.stdout.write(f'\x1b[{row + offset};{w//2 + 1}H\x1b[7m{text}\x1b[0m')
else:
sys.stdout.write(f'\x1b[{row + offset};{w//2 + 1}H{text}\x1b[0m')

66
preview.py Normal file
View File

@ -0,0 +1,66 @@
import imgkit
import sys
from libsixel.encoder import Encoder, SIXEL_OPTFLAG_WIDTH, SIXEL_OPTFLAG_COLORS
from tempfile import NamedTemporaryFile
HTML_WIDTH = 720
def preview(html, width, height, htmlWidth=HTML_WIDTH):
encoder = Encoder()
encoder.setopt(SIXEL_OPTFLAG_WIDTH, str(width))
encoder.setopt(SIXEL_OPTFLAG_COLORS, '256')
options = {
'encoding': 'utf-8',
'width': htmlWidth,
'height': int(htmlWidth/width*height),
'quiet': None,
}
with NamedTemporaryFile(suffix='.png') as img:
with NamedTemporaryFile(suffix='.html') as page:
page.write(bytes(html, encoding='utf-8'))
page.flush()
imgkit.from_file(page.name, img.name, options=options)
sys.stdout.write('\x1b[1;1H')
sys.stdout.flush()
encoder.encode(img.name)
def previewCard(template, style, content, width, height, htmlWidth=HTML_WIDTH):
for field in content:
# conditional sections
formated = ''
i = 0
remove = False
while i < len(template):
section = template[i:i + len(field) + 5]
if section in ('{{#'+field+'}}', '{{^'+field+'}}'):
i += len(field) + 5
if (section[2] == '#' and not content[field]) or (section[2] == '^' and content[field]):
remove = True
if section == '{{/'+field+'}}':
i += len(field) + 5
if remove:
remove = False
if not remove and i < len(template):
formated += template[i]
i += 1;
template = formated.replace('{{'+field+'}}', content[field])
template = '<style>'+style+'</style>'+template
preview(TEMPLATE.replace('{{CONTENT}}', template), width, height, htmlWidth)
TEMPLATE_FILE = 'template.html'
with open(TEMPLATE_FILE) as templateFile:
TEMPLATE = templateFile.read()
if __name__ == '__main__':
preview('<h1>Hello World!</h1>', 500, 500)
previewCard('<h1>{{Front}}</h1>',
'h1{color:cyan;}',
{
'Front': 'Hello World!',
'Back': 'From Python...'
},
500, 500)