first commit

This commit is contained in:
2024-03-08 16:51:35 +00:00
commit 26fe54f20c
90 changed files with 24232 additions and 0 deletions

59
scripts/build_authors.py Executable file
View File

@@ -0,0 +1,59 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Scrape the project contributors list from Github and update AUTHORS.rst
"""
from __future__ import unicode_literals
import os
import time
import logging
import requests
_filepath = os.path.dirname(os.path.relpath(__file__))
FILENAME = os.path.abspath(os.path.join(_filepath, '..', 'AUTHORS.rst'))
URL = "https://api.github.com/repos/tildeclub/ttrv/contributors?per_page=1000"
HEADER = """\
================
TTRV Contributors
================
Thanks to the following people for their contributions to this project.
"""
def main():
logging.captureWarnings(True)
# Request the list of contributors
print('GET {}'.format(URL))
resp = requests.get(URL)
contributors = resp.json()
lines = []
for contributor in contributors:
time.sleep(1.0)
# Request each contributor individually to get the full name
print('GET {}'.format(contributor['url']))
resp = requests.get(contributor['url'])
user = resp.json()
name = user.get('name') or contributor['login']
url = user['html_url']
lines.append('* `{} <{}>`_'.format(name, url))
print('Writing to {}'.format(FILENAME))
text = HEADER + '\n'.join(lines)
text = text.encode('utf-8')
with open(FILENAME, 'wb') as fp:
fp.write(text)
if __name__ == '__main__':
main()

84
scripts/build_manpage.py Executable file
View File

@@ -0,0 +1,84 @@
#!/usr/bin/env python
"""
Internal tool used to automatically generate an up-to-date version of the tvr
man page. Currently this script should be manually ran after each version bump.
In the future, it would be nice to have this functionality built into setup.py.
Usage:
$ python scripts/build_manpage.py
"""
import os
import sys
from datetime import datetime
_filepath = os.path.dirname(os.path.relpath(__file__))
ROOT = os.path.abspath(os.path.join(_filepath, '..'))
sys.path.insert(0, ROOT)
import tvr
from tvr import config
def main():
parser = config.build_parser()
help_text = parser.format_help()
help_sections = help_text.split('\n\n')
del help_sections[1]
data = {}
print('Fetching version')
data['version'] = tvr.__version__
print('Fetching release date')
data['release_date'] = datetime.utcnow().strftime('%B %d, %Y')
print('Fetching synopsis')
synopsis = help_sections[0].replace('usage: ', '')
synopsis = ' '.join(line.strip() for line in synopsis.split('\n'))
data['synopsis'] = synopsis
print('Fetching description')
data['description'] = help_sections[1]
# Build the options section for each argument from the help section
# Example Before:
# -h, --help show this help message and exit
# Example After
# .TP
# \fB-h\fR, \fB--help\fR
# show this help message and exit
options = ''
lines = help_sections[2].split('\n')[1:] # positional arguments
lines.extend(help_sections[3].split('\n')[1:]) # optional arguments
lines = [line.strip() for line in lines]
arguments = []
for line in lines:
if line.startswith('-'):
arguments.append(line)
elif line.startswith('URL'):
# Special case for URL which is a positional argument
arguments.append(line)
else:
arguments[-1] = arguments[-1] + ' ' + line
for argument in arguments:
flag, description = (col.strip() for col in argument.split(' ', 1))
flag = ', '.join(r'\fB'+f+r'\fR' for f in flag.split(', '))
options += '\n'.join(('.TP', flag, description, '\n'))
data['options'] = options
print('Fetching license')
data['license'] = tvr.__license__
print('Fetching copyright')
data['copyright'] = tvr.__copyright__
# Escape dashes is all of the sections
data = {k: v.replace('-', r'\-') for k, v in data.items()}
print('Reading from %s/scripts/tvr.1.template' % ROOT)
with open(os.path.join(ROOT, 'scripts/tvr.1.template')) as fp:
template = fp.read()
print('Populating template')
out = template.format(**data)
print('Writing to %s/tvr.1' % ROOT)
with open(os.path.join(ROOT, 'tvr.1'), 'w') as fp:
fp.write(out)
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load Diff

15
scripts/count_lines.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
ROOT="$(dirname "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )")"
cd ${ROOT}
echo -e "\nTests: "
echo "$(wc -l tests/*.py)"
echo -e "\nScripts: "
echo "$(wc -l scripts/*)"
echo -e "\nTemplates: "
echo "$(wc -l ttrv/templates/*)"
echo -e "\nCode: "
echo "$(wc -l ttrv/*.py)"
echo -e "\nCombined: "
echo "$(cat tests/*.py scripts/* ttrv/templates/* ttrv/*.py | wc -l) total lines"

283
scripts/demo_theme.py Executable file
View File

@@ -0,0 +1,283 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from __future__ import print_function
import os
import sys
import time
import curses
import locale
import threading
from types import MethodType
from collections import Counter
from vcr import VCR
from six.moves.urllib.parse import urlparse, parse_qs
from ttrv.theme import Theme, ThemeList
from ttrv.config import Config
from ttrv.packages import praw
from ttrv.oauth import OAuthHelper
from ttrv.terminal import Terminal
from ttrv.objects import curses_session
from ttrv.subreddit_page import SubredditPage
from ttrv.submission_page import SubmissionPage
from ttrv.subscription_page import SubscriptionPage
try:
from unittest import mock
except ImportError:
import mock
def initialize_vcr():
def auth_matcher(r1, r2):
return (r1.headers.get('authorization') ==
r2.headers.get('authorization'))
def uri_with_query_matcher(r1, r2):
p1, p2 = urlparse(r1.uri), urlparse(r2.uri)
return (p1[:3] == p2[:3] and
parse_qs(p1.query, True) == parse_qs(p2.query, True))
cassette_dir = os.path.join(os.path.dirname(__file__), 'cassettes')
if not os.path.exists(cassette_dir):
os.makedirs(cassette_dir)
filename = os.path.join(cassette_dir, 'demo_theme.yaml')
if os.path.exists(filename):
record_mode = 'none'
else:
record_mode = 'once'
vcr = VCR(
record_mode=record_mode,
filter_headers=[('Authorization', '**********')],
filter_post_data_parameters=[('refresh_token', '**********')],
match_on=['method', 'uri_with_query', 'auth', 'body'],
cassette_library_dir=cassette_dir)
vcr.register_matcher('auth', auth_matcher)
vcr.register_matcher('uri_with_query', uri_with_query_matcher)
return vcr
# Patch the getch method so we can display multiple notifications or
# other elements that require a keyboard input on the screen at the
# same time without blocking the main thread.
def notification_getch(self):
if self.pause_getch:
return -1
return 0
def prompt_getch(self):
while self.pause_getch:
time.sleep(1)
return 0
def draw_screen(stdscr, reddit, config, theme, oauth):
threads = []
max_y, max_x = stdscr.getmaxyx()
mid_x = int(max_x / 2)
tall_y, short_y = int(max_y / 3 * 2), int(max_y / 3)
stdscr.clear()
stdscr.refresh()
# ===================================================================
# Submission Page
# ===================================================================
win1 = stdscr.derwin(tall_y - 1, mid_x - 1, 0, 0)
term = Terminal(win1, config)
term.set_theme(theme)
oauth.term = term
url = 'https://www.reddit.com/r/Python/comments/4dy7xr'
with term.loader('Loading'):
page = SubmissionPage(reddit, term, config, oauth, url=url)
# Tweak the data in order to demonstrate the full range of settings
data = page.content.get(-1)
data['object'].link_flair_text = 'flair'
data['object'].gilded = 1
data['object'].over_18 = True
data['object'].saved = True
data.update(page.content.strip_praw_submission(data['object']))
data = page.content.get(0)
data['object'].author.name = 'kafoozalum'
data['object'].stickied = True
data['object'].author_flair_text = 'flair'
data['object'].likes = True
data.update(page.content.strip_praw_comment(data['object']))
data = page.content.get(1)
data['object'].saved = True
data['object'].likes = False
data['object'].score_hidden = True
data['object'].gilded = 1
data.update(page.content.strip_praw_comment(data['object']))
data = page.content.get(2)
data['object'].author.name = 'kafoozalum'
data['object'].body = data['object'].body[:100]
data.update(page.content.strip_praw_comment(data['object']))
page.content.toggle(9)
page.content.toggle(5)
page.draw()
# ===================================================================
# Subreddit Page
# ===================================================================
win2 = stdscr.derwin(tall_y - 1, mid_x - 1, 0, mid_x + 1)
term = Terminal(win2, config)
term.set_theme(theme)
oauth.term = term
with term.loader('Loading'):
page = SubredditPage(reddit, term, config, oauth, '/u/saved')
# Tweak the data in order to demonstrate the full range of settings
data = page.content.get(3)
data['object'].hide_score = True
data['object'].author = None
data['object'].saved = False
data.update(page.content.strip_praw_submission(data['object']))
page.content.order = 'rising'
page.nav.cursor_index = 1
page.draw()
term.pause_getch = True
term.getch = MethodType(notification_getch, term)
thread = threading.Thread(target=term.show_notification,
args=('Success',),
kwargs={'style': 'Success'})
thread.start()
threads.append((thread, term))
# ===================================================================
# Subscription Page
# ===================================================================
win3 = stdscr.derwin(short_y, mid_x - 1, tall_y, 0)
term = Terminal(win3, config)
term.set_theme(theme)
oauth.term = term
with term.loader('Loading'):
page = SubscriptionPage(reddit, term, config, oauth, 'popular')
page.nav.cursor_index = 1
page.draw()
term.pause_getch = True
term.getch = MethodType(notification_getch, term)
thread = threading.Thread(target=term.show_notification,
args=('Error',),
kwargs={'style': 'Error'})
thread.start()
threads.append((thread, term))
# ===================================================================
# Multireddit Page
# ===================================================================
win4 = stdscr.derwin(short_y, mid_x - 1, tall_y, mid_x + 1)
term = Terminal(win4, config)
term.set_theme(theme)
oauth.term = term
with term.loader('Loading'):
page = SubscriptionPage(reddit, term, config, oauth, 'multireddit')
page.nav.cursor_index = 1
page.draw()
term.pause_getch = True
term.getch = MethodType(notification_getch, term)
thread = threading.Thread(target=term.show_notification,
args=('Info',),
kwargs={'style': 'Info'})
thread.start()
threads.append((thread, term))
term = Terminal(win4, config)
term.set_theme(theme)
term.pause_getch = True
term.getch = MethodType(prompt_getch, term)
thread = threading.Thread(target=term.prompt_y_or_n, args=('Prompt: ',))
thread.start()
threads.append((thread, term))
time.sleep(0.5)
curses.curs_set(0)
return threads
def main():
locale.setlocale(locale.LC_ALL, '')
if len(sys.argv) > 1:
theme = Theme.from_name(sys.argv[1])
else:
theme = Theme()
vcr = initialize_vcr()
with vcr.use_cassette('demo_theme.yaml') as cassette, \
curses_session() as stdscr:
config = Config()
if vcr.record_mode == 'once':
config.load_refresh_token()
else:
config.refresh_token = 'mock_refresh_token'
reddit = praw.Reddit(user_agent='TTRV Theme Demo',
decode_html_entities=False,
disable_update_check=True)
reddit.config.api_request_delay = 0
config.history.add('https://api.reddit.com/comments/6llvsl/_/djutc3s')
config.history.add('http://i.imgur.com/Z9iGKWv.gifv')
config.history.add('https://www.reddit.com/r/Python/comments/6302cj/rpython_official_job_board/')
term = Terminal(stdscr, config)
term.set_theme()
oauth = OAuthHelper(reddit, term, config)
oauth.authorize()
theme_list = ThemeList()
while True:
term = Terminal(stdscr, config)
term.set_theme(theme)
threads = draw_screen(stdscr, reddit, config, theme, oauth)
try:
ch = term.show_notification(theme.display_string)
except KeyboardInterrupt:
ch = Terminal.ESCAPE
for thread, term in threads:
term.pause_getch = False
thread.join()
if vcr.record_mode == 'once':
break
else:
cassette.play_counts = Counter()
theme_list.reload()
if ch == curses.KEY_RIGHT:
theme = theme_list.next(theme)
elif ch == curses.KEY_LEFT:
theme = theme_list.previous(theme)
elif ch == Terminal.ESCAPE:
break
else:
# Force the theme to reload
theme = theme_list.next(theme)
theme = theme_list.previous(theme)
sys.exit(main())

View File

@@ -0,0 +1,30 @@
"""
Initialize an authenticated instance of PRAW to interact with.
$ python -i initialize_session.py
"""
from ttrv.docs import AGENT
from ttrv.packages import praw
from ttrv.content import RequestHeaderRateLimiter
from ttrv.config import Config
config = Config()
config.load_refresh_token()
reddit = praw.Reddit(
user_agent=AGENT.format(version='test_session'),
decode_html_entities=False,
disable_update_check=True,
timeout=10, # 10 second request timeout
handler=RequestHeaderRateLimiter())
reddit.set_oauth_app_info(
config['oauth_client_id'],
config['oauth_client_secret'],
config['oauth_redirect_uri'])
reddit.refresh_access_information(config.refresh_token)
inbox = reddit.get_inbox()
items = [next(inbox) for _ in range(20)]
pass

31
scripts/inspect_webbrowser.py Executable file
View File

@@ -0,0 +1,31 @@
#!/usr/bin/env python
"""
Utility script used to examine the python webbrowser module with different OSs.
"""
import os
os.environ['BROWSER'] = 'firefox'
# If we want to override the $BROWSER variable that the python webbrowser
# references, it needs to be done before the webbrowser module is imported
# for the first time.
TTRV_BROWSER, BROWSER = os.environ.get('TTRV_BROWSER'), os.environ.get('BROWSER')
if TTRV_BROWSER:
os.environ['BROWSER'] = TTRV_BROWSER
print('TTRV_BROWSER=%s' % TTRV_BROWSER)
print('BROWSER=%s' % BROWSER)
import webbrowser
print('webbrowser._browsers:')
for key, val in webbrowser._browsers.items():
print(' %s: %s' % (key, val))
print('webbrowser._tryorder:')
for name in webbrowser._tryorder:
print(' %s' % name)
webbrowser.open_new_tab('https://www.python.org')

9
scripts/pip_clean.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# Removes any lingering build/release files from the project directory
find . -type f -name '*.pyc' -delete
find . -type f -name '*.pyo' -delete
find . -type d -name '__pycache__' -exec rm -rv {} +
find . -type d -name 'build' -exec rm -rv {} +
find . -type d -name 'dist' -exec rm -rv {} +
find . -type d -name '*.egg-info' -exec rm -rv {} +

47
scripts/ttrv.1.template Normal file
View File

@@ -0,0 +1,47 @@
.TH "TTRV" "1" "{release_date}" "Version {version}" "Usage and Commands"
.SH NAME
TTRV - Reddit Terminal Viewer
.SH SYNOPSIS
{synopsis}
.SH DESCRIPTION
{description}
.SH OPTIONS
{options}
.SH CONTROLS
Move the cursor using the arrow keys or vim style movement.
.br
Press \fBup\fR and \fBdown\fR to scroll through submissions.
.br
Press \fBright\fR to view the selected submission and \fBleft\fR to return.
.br
Press \fB?\fR to open the help screen.
.SH FILES
.TP
.BR $XDG_CONFIG_HOME/ttrv/ttrv.cfg
The configuration file can be used to customize default program settings.
.TP
.BR $XDG_DATA_HOME/ttrv/refresh-token
After you login to reddit, your most recent OAuth refresh token will be stored
for future sessions.
.TP
.BR $XDG_DATA_HOME/ttrv/history.log
This file stores URLs that have been recently opened in order to
visually highlight them as "seen".
.SH ENVIRONMENT
.TP
.BR TTRV_EDITOR
Text editor to use when editing comments and submissions. Will fallback to
\fI$EDITOR\fR.
.TP
.BR TTRV_URLVIEWER
Url viewer to use to extract links from comments. Requires a compatible
program to be installed.
.TP
.BR TTRV_BROWSER
Web browser to use when opening links. Will fallback to \fI$BROWSER\fR.
.SH AUTHOR
Michael Lazar <lazar.michael22@gmail.com> (2017).
.SH BUGS
Report bugs to \fIhttps://github.com/tildeclub/ttrv/issues\fR
.SH LICENSE
{license}

49
scripts/update_packages.py Executable file
View File

@@ -0,0 +1,49 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Update the project's bundled dependencies by downloading the git repository and
copying over the most recent commit.
"""
import os
import shutil
import subprocess
import tempfile
_filepath = os.path.dirname(os.path.relpath(__file__))
ROOT = os.path.abspath(os.path.join(_filepath, '..'))
PRAW_REPO = 'https://github.com/michael-lazar/praw3.git'
def main():
tmpdir = tempfile.mkdtemp()
subprocess.check_call(['git', 'clone', PRAW_REPO, tmpdir])
# Update the commit hash reference
os.chdir(tmpdir)
p = subprocess.Popen(['git', 'rev-parse', 'HEAD'], stdout=subprocess.PIPE)
p.wait()
commit = p.stdout.read().strip()
print('Found commit %s' % commit)
regex = 's/^__praw_hash__ =.*$/__praw_hash__ = \'%s\'/g' % commit
packages_root = os.path.join(ROOT, 'ttrv', 'packages', '__init__.py')
print('Updating commit hash in %s' % packages_root)
subprocess.check_call(['sed', '-i', '', regex, packages_root])
# Overwrite the project files
src = os.path.join(tmpdir, 'praw')
dest = os.path.join(ROOT, 'ttrv', 'packages', 'praw')
print('Copying package files to %s' % dest)
shutil.rmtree(dest, ignore_errors=True)
shutil.copytree(src, dest)
# Cleanup
print('Removing directory %s' % tmpdir)
shutil.rmtree(tmpdir)
if __name__ == '__main__':
main()