diff -Nru asciinema-2.0.1/asciinema/api.py asciinema-2.0.2/asciinema/api.py --- asciinema-2.0.1/asciinema/api.py 2018-04-04 07:05:41.000000000 +0000 +++ asciinema-2.0.2/asciinema/api.py 2019-01-12 18:58:29.000000000 +0000 @@ -1,5 +1,6 @@ import platform import re +import json from urllib.parse import urlparse from asciinema import __version__ @@ -44,10 +45,15 @@ if status != 200 and status != 201: self._handle_error(status, body) - return body, headers.get('Warning') + if (headers.get('content-type') or '')[0:16] == 'application/json': + result = json.loads(body) + else: + result = {'url': body} + + return result, headers.get('Warning') def _headers(self): - return {'User-Agent': self._user_agent()} + return {'User-Agent': self._user_agent(), 'Accept': 'application/json'} def _user_agent(self): os = re.sub('([^-]+)-(.*)', '\\1/\\2', platform.platform()) diff -Nru asciinema-2.0.1/asciinema/asciicast/raw.py asciinema-2.0.2/asciinema/asciicast/raw.py --- asciinema-2.0.1/asciinema/asciicast/raw.py 2018-04-04 07:05:41.000000000 +0000 +++ asciinema-2.0.2/asciinema/asciicast/raw.py 2019-01-12 18:58:29.000000000 +0000 @@ -1,11 +1,10 @@ import os from multiprocessing import Process, Queue -from asciinema.pty_recorder import PtyRecorder - def write_bytes_from_queue(path, mode, queue): mode = mode + 'b' + with open(path, mode=mode, buffering=0) as f: for data in iter(queue.get, None): f.write(data) @@ -13,7 +12,10 @@ class writer(): - def __init__(self, path, append): + def __init__(self, path, _metadata, append=False, _time_offset=0): + if append and os.path.exists(path) and os.stat(path).st_size == 0: # true for pipes + append = False + self.path = path self.mode = 'a' if append else 'w' self.queue = Queue() @@ -35,16 +37,3 @@ def write_stdout(self, data): self.queue.put(data) - - -class Recorder: - - def __init__(self, pty_recorder=None): - self.pty_recorder = pty_recorder if pty_recorder is not None else PtyRecorder() - - def record(self, path, append, command, command_env, _captured_env, _rec_stdin, _title, _idle_time_limit): - if os.path.exists(path) and os.stat(path).st_size == 0: # true for pipes - append = False - - with writer(path, append) as w: - self.pty_recorder.record_command(['sh', '-c', command], w, command_env) diff -Nru asciinema-2.0.1/asciinema/asciicast/v2.py asciinema-2.0.2/asciinema/asciicast/v2.py --- asciinema-2.0.1/asciinema/asciicast/v2.py 2018-04-04 07:05:41.000000000 +0000 +++ asciinema-2.0.2/asciinema/asciicast/v2.py 2019-01-12 18:58:29.000000000 +0000 @@ -1,12 +1,17 @@ -import os -import subprocess import json import json.decoder import time import codecs -from multiprocessing import Process, Queue -from asciinema.pty_recorder import PtyRecorder +try: + # Importing synchronize is to detect platforms where + # multiprocessing does not work (python issue 3770) + # and cause an ImportError. Otherwise it will happen + # later when trying to use Queue(). + from multiprocessing import synchronize, Process, Queue +except ImportError: + from threading import Thread as Process + from queue import Queue try: @@ -71,6 +76,22 @@ return last_frame[0] +def build_header(metadata): + header = {} + header.update(metadata) + header['version'] = 2 + + assert 'width' in header, 'width missing in metadata' + assert 'height' in header, 'height missing in metadata' + assert type(header['width']) == int + assert type(header['height']) == int + + if 'timestamp' in header: + assert type(header['timestamp']) == int or type(header['timestamp']) == float + + return header + + class writer(): def __init__(self, path, width=None, height=None, header=None, mode='w', buffering=-1): @@ -137,21 +158,25 @@ class async_writer(): - def __init__(self, path, header, rec_stdin, start_time_offset=0): + def __init__(self, path, metadata, append=False, time_offset=0): + if append: + assert time_offset > 0 + self.path = path - self.header = header - self.rec_stdin = rec_stdin - self.start_time_offset = start_time_offset + self.metadata = metadata + self.append = append + self.time_offset = time_offset self.queue = Queue() def __enter__(self): - mode = 'a' if self.start_time_offset > 0 else 'w' + header = build_header(self.metadata) + mode = 'a' if self.append else 'w' self.process = Process( target=write_json_lines_from_queue, - args=(self.path, self.header, mode, self.queue) + args=(self.path, header, mode, self.queue) ) self.process.start() - self.start_time = time.time() - self.start_time_offset + self.start_time = time.time() - self.time_offset return self def __exit__(self, exc_type, exc_value, exc_traceback): @@ -159,44 +184,9 @@ self.process.join() def write_stdin(self, data): - if self.rec_stdin: - ts = time.time() - self.start_time - self.queue.put([ts, 'i', data]) + ts = time.time() - self.start_time + self.queue.put([ts, 'i', data]) def write_stdout(self, data): ts = time.time() - self.start_time self.queue.put([ts, 'o', data]) - - -class Recorder: - - def __init__(self, pty_recorder=None): - self.pty_recorder = pty_recorder if pty_recorder is not None else PtyRecorder() - - def record(self, path, append, command, command_env, captured_env, rec_stdin, title, idle_time_limit): - start_time_offset = 0 - - if append and os.stat(path).st_size > 0: - start_time_offset = get_duration(path) - - cols = int(subprocess.check_output(['tput', 'cols'])) - lines = int(subprocess.check_output(['tput', 'lines'])) - - header = { - 'version': 2, - 'width': cols, - 'height': lines, - 'timestamp': int(time.time()), - } - - if idle_time_limit is not None: - header['idle_time_limit'] = idle_time_limit - - if captured_env: - header['env'] = captured_env - - if title: - header['title'] = title - - with async_writer(path, header, rec_stdin, start_time_offset) as w: - self.pty_recorder.record_command(['sh', '-c', command], w, command_env) diff -Nru asciinema-2.0.1/asciinema/commands/record.py asciinema-2.0.2/asciinema/commands/record.py --- asciinema-2.0.1/asciinema/commands/record.py 2018-04-04 07:05:41.000000000 +0000 +++ asciinema-2.0.2/asciinema/commands/record.py 2019-01-12 18:58:29.000000000 +0000 @@ -1,12 +1,12 @@ -import sys import os +import sys import tempfile -from asciinema.commands.command import Command -import asciinema.asciicast as asciicast -import asciinema.asciicast.v2 as v2 +import asciinema import asciinema.asciicast.raw as raw +import asciinema.asciicast.v2 as v2 from asciinema.api import APIError +from asciinema.commands.command import Command class RecordCommand(Command): @@ -24,7 +24,7 @@ self.append = args.append self.overwrite = args.overwrite self.raw = args.raw - self.recorder = raw.Recorder() if args.raw else v2.Recorder() + self.writer = raw.writer if args.raw else v2.async_writer self.env = env if env is not None else os.environ def execute(self): @@ -58,24 +58,24 @@ else: self.print_info("recording asciicast to %s" % self.filename) - self.print_info("""press or type "exit" when you're done""") + if self.command: + self.print_info("""exit opened program when you're done""") + else: + self.print_info("""press or type "exit" when you're done""") - command = self.command or self.env.get('SHELL') or 'sh' - command_env = self.env.copy() - command_env['ASCIINEMA_REC'] = '1' vars = filter(None, map((lambda var: var.strip()), self.env_whitelist.split(','))) - captured_env = {var: self.env.get(var) for var in vars} try: - self.recorder.record( + asciinema.record_asciicast( self.filename, - append, - command, - command_env, - captured_env, - self.rec_stdin, - self.title, - self.idle_time_limit + command=self.command, + append=append, + title=self.title, + idle_time_limit=self.idle_time_limit, + command_env=self.env, + capture_env=vars, + rec_stdin=self.rec_stdin, + writer=self.writer ) except v2.LoadError: self.print_error("can only append to asciicast v2 format recordings") @@ -85,7 +85,8 @@ if upload: if not self.assume_yes: - self.print_info("press to upload to %s, to save locally" % self.api.hostname()) + self.print_info("press to upload to %s, to save locally" + % self.api.hostname()) try: sys.stdin.readline() except KeyboardInterrupt: @@ -94,11 +95,14 @@ return 0 try: - url, warn = self.api.upload_asciicast(self.filename) + result, warn = self.api.upload_asciicast(self.filename) + if warn: self.print_warning(warn) + os.remove(self.filename) - self.print(url) + self.print(result.get('message') or result['url']) + except APIError as e: self.print("\r\x1b[A", end="") self.print_error("upload failed: %s" % str(e)) diff -Nru asciinema-2.0.1/asciinema/commands/upload.py asciinema-2.0.2/asciinema/commands/upload.py --- asciinema-2.0.1/asciinema/commands/upload.py 2018-04-04 07:05:41.000000000 +0000 +++ asciinema-2.0.2/asciinema/commands/upload.py 2019-01-12 18:58:29.000000000 +0000 @@ -11,12 +11,12 @@ def execute(self): try: - url, warn = self.api.upload_asciicast(self.filename) + result, warn = self.api.upload_asciicast(self.filename) if warn: self.print_warning(warn) - self.print(url) + self.print(result.get('message') or result['url']) except OSError as e: self.print_error("upload failed: %s" % str(e)) diff -Nru asciinema-2.0.1/asciinema/__init__.py asciinema-2.0.2/asciinema/__init__.py --- asciinema-2.0.1/asciinema/__init__.py 2018-04-04 07:05:41.000000000 +0000 +++ asciinema-2.0.2/asciinema/__init__.py 2019-01-12 18:58:29.000000000 +0000 @@ -1,7 +1,58 @@ import sys __author__ = 'Marcin Kulik' -__version__ = '2.0.1' +__version__ = '2.0.2' if sys.version_info[0] < 3: raise ImportError('Python < 3 is unsupported.') + + +import os +import subprocess +import time + +import asciinema.asciicast.v2 as v2 +import asciinema.pty as pty +import asciinema.term as term + + +def record_asciicast(path, command=None, append=False, idle_time_limit=None, + rec_stdin=False, title=None, metadata=None, + command_env=None, capture_env=None, writer=v2.async_writer, + record=pty.record): + if command is None: + command = os.environ.get('SHELL') or 'sh' + + if command_env is None: + command_env = os.environ.copy() + command_env['ASCIINEMA_REC'] = '1' + + if capture_env is None: + capture_env = ['SHELL', 'TERM'] + + w, h = term.get_size() + + full_metadata = { + 'width': w, + 'height': h, + 'timestamp': int(time.time()) + } + + full_metadata.update(metadata or {}) + + if idle_time_limit is not None: + full_metadata['idle_time_limit'] = idle_time_limit + + if capture_env: + full_metadata['env'] = {var: command_env.get(var) for var in capture_env} + + if title: + full_metadata['title'] = title + + time_offset = 0 + + if append and os.stat(path).st_size > 0: + time_offset = v2.get_duration(path) + + with writer(path, full_metadata, append, time_offset) as w: + record(['sh', '-c', command], w, command_env, rec_stdin) diff -Nru asciinema-2.0.1/asciinema/__main__.py asciinema-2.0.2/asciinema/__main__.py --- asciinema-2.0.1/asciinema/__main__.py 2018-04-04 07:05:41.000000000 +0000 +++ asciinema-2.0.2/asciinema/__main__.py 2019-01-12 18:58:29.000000000 +0000 @@ -50,8 +50,8 @@ def main(): - if locale.nl_langinfo(locale.CODESET).upper() != 'UTF-8': - print("asciinema needs a UTF-8 native locale to run. Check the output of `locale` command.") + if locale.nl_langinfo(locale.CODESET).upper() not in ['US-ASCII', 'UTF-8']: + print("asciinema needs an ASCII or UTF-8 character encoding to run. Check the output of `locale` command.") sys.exit(1) try: diff -Nru asciinema-2.0.1/asciinema/player.py asciinema-2.0.2/asciinema/player.py --- asciinema-2.0.1/asciinema/player.py 2018-04-04 07:05:41.000000000 +0000 +++ asciinema-2.0.2/asciinema/player.py 2019-01-12 18:58:29.000000000 +0000 @@ -13,7 +13,7 @@ stdin = open('/dev/tty') with raw(stdin.fileno()): self._play(asciicast, idle_time_limit, speed, stdin) - except: + except Exception: self._play(asciicast, idle_time_limit, speed, None) def _play(self, asciicast, idle_time_limit, speed, stdin): diff -Nru asciinema-2.0.1/asciinema/pty.py asciinema-2.0.2/asciinema/pty.py --- asciinema-2.0.1/asciinema/pty.py 1970-01-01 00:00:00.000000000 +0000 +++ asciinema-2.0.2/asciinema/pty.py 2019-01-12 18:58:29.000000000 +0000 @@ -0,0 +1,140 @@ +import array +import errno +import fcntl +import io +import os +import pty +import select +import shlex +import signal +import struct +import sys +import termios + +from asciinema.term import raw + + +def record(command, writer, env=os.environ, rec_stdin=False): + master_fd = None + + def _set_pty_size(): + ''' + Sets the window size of the child pty based on the window size + of our own controlling terminal. + ''' + + # Get the terminal size of the real terminal, set it on the pseudoterminal. + if os.isatty(pty.STDOUT_FILENO): + buf = array.array('h', [0, 0, 0, 0]) + fcntl.ioctl(pty.STDOUT_FILENO, termios.TIOCGWINSZ, buf, True) + else: + buf = array.array('h', [24, 80, 0, 0]) + + fcntl.ioctl(master_fd, termios.TIOCSWINSZ, buf) + + def _write_stdout(data): + '''Writes to stdout as if the child process had written the data.''' + + os.write(pty.STDOUT_FILENO, data) + + def _handle_master_read(data): + '''Handles new data on child process stdout.''' + + writer.write_stdout(data) + _write_stdout(data) + + def _write_master(data): + '''Writes to the child process from its controlling terminal.''' + + while data: + n = os.write(master_fd, data) + data = data[n:] + + def _handle_stdin_read(data): + '''Handles new data on child process stdin.''' + + _write_master(data) + + if rec_stdin: + writer.write_stdin(data) + + def _signals(signal_list): + old_handlers = [] + for sig, handler in signal_list: + old_handlers.append((sig, signal.signal(sig, handler))) + return old_handlers + + def _copy(signal_fd): + '''Main select loop. + + Passes control to _master_read() or _stdin_read() + when new data arrives. + ''' + + fds = [master_fd, pty.STDIN_FILENO, signal_fd] + + while True: + try: + rfds, wfds, xfds = select.select(fds, [], []) + except OSError as e: # Python >= 3.3 + if e.errno == errno.EINTR: + continue + except select.error as e: # Python < 3.3 + if e.args[0] == 4: + continue + + if master_fd in rfds: + data = os.read(master_fd, 1024) + if not data: # Reached EOF. + fds.remove(master_fd) + else: + _handle_master_read(data) + + if pty.STDIN_FILENO in rfds: + data = os.read(pty.STDIN_FILENO, 1024) + if not data: + fds.remove(pty.STDIN_FILENO) + else: + _handle_stdin_read(data) + + if signal_fd in rfds: + data = os.read(signal_fd, 1024) + if data: + signals = struct.unpack('%uB' % len(data), data) + for sig in signals: + if sig in [signal.SIGCHLD, signal.SIGHUP, signal.SIGTERM, signal.SIGQUIT]: + os.close(master_fd) + return + elif sig == signal.SIGWINCH: + _set_pty_size() + + pid, master_fd = pty.fork() + + if pid == pty.CHILD: + os.execvpe(command[0], command, env) + + pipe_r, pipe_w = os.pipe() + flags = fcntl.fcntl(pipe_w, fcntl.F_GETFL, 0) + flags = flags | os.O_NONBLOCK + flags = fcntl.fcntl(pipe_w, fcntl.F_SETFL, flags) + + signal.set_wakeup_fd(pipe_w) + + old_handlers = _signals(map(lambda s: (s, lambda signal, frame: None), + [signal.SIGWINCH, + signal.SIGCHLD, + signal.SIGHUP, + signal.SIGTERM, + signal.SIGQUIT])) + + _set_pty_size() + + with raw(pty.STDIN_FILENO): + try: + _copy(pipe_r) + except (IOError, OSError): + pass + + _signals(old_handlers) + + os.waitpid(pid, 0) diff -Nru asciinema-2.0.1/asciinema/pty_recorder.py asciinema-2.0.2/asciinema/pty_recorder.py --- asciinema-2.0.1/asciinema/pty_recorder.py 2018-04-04 07:05:41.000000000 +0000 +++ asciinema-2.0.2/asciinema/pty_recorder.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,140 +0,0 @@ -import errno -import os -import pty -import signal -import array -import fcntl -import termios -import select -import io -import shlex -import sys -import struct - -from asciinema.term import raw - - -class PtyRecorder: - - def record_command(self, command, output, env=os.environ): - master_fd = None - - def _set_pty_size(): - ''' - Sets the window size of the child pty based on the window size - of our own controlling terminal. - ''' - - # Get the terminal size of the real terminal, set it on the pseudoterminal. - if os.isatty(pty.STDOUT_FILENO): - buf = array.array('h', [0, 0, 0, 0]) - fcntl.ioctl(pty.STDOUT_FILENO, termios.TIOCGWINSZ, buf, True) - else: - buf = array.array('h', [24, 80, 0, 0]) - - fcntl.ioctl(master_fd, termios.TIOCSWINSZ, buf) - - def _write_stdout(data): - '''Writes to stdout as if the child process had written the data.''' - - os.write(pty.STDOUT_FILENO, data) - - def _handle_master_read(data): - '''Handles new data on child process stdout.''' - - output.write_stdout(data) - _write_stdout(data) - - def _write_master(data): - '''Writes to the child process from its controlling terminal.''' - - while data: - n = os.write(master_fd, data) - data = data[n:] - - def _handle_stdin_read(data): - '''Handles new data on child process stdin.''' - - output.write_stdin(data) - _write_master(data) - - def _signals(signal_list): - old_handlers = [] - for sig, handler in signal_list: - old_handlers.append((sig, signal.signal(sig, handler))) - return old_handlers - - def _copy(signal_fd): - '''Main select loop. - - Passes control to _master_read() or _stdin_read() - when new data arrives. - ''' - - fds = [master_fd, pty.STDIN_FILENO, signal_fd] - - while True: - try: - rfds, wfds, xfds = select.select(fds, [], []) - except OSError as e: # Python >= 3.3 - if e.errno == errno.EINTR: - continue - except select.error as e: # Python < 3.3 - if e.args[0] == 4: - continue - - if master_fd in rfds: - data = os.read(master_fd, 1024) - if not data: # Reached EOF. - fds.remove(master_fd) - else: - _handle_master_read(data) - - if pty.STDIN_FILENO in rfds: - data = os.read(pty.STDIN_FILENO, 1024) - if not data: - fds.remove(pty.STDIN_FILENO) - else: - _handle_stdin_read(data) - - if signal_fd in rfds: - data = os.read(signal_fd, 1024) - if data: - signals = struct.unpack('%uB' % len(data), data) - for sig in signals: - if sig in [signal.SIGCHLD, signal.SIGHUP, signal.SIGTERM, signal.SIGQUIT]: - os.close(master_fd) - return - elif sig == signal.SIGWINCH: - _set_pty_size() - - pid, master_fd = pty.fork() - - if pid == pty.CHILD: - os.execvpe(command[0], command, env) - - pipe_r, pipe_w = os.pipe() - flags = fcntl.fcntl(pipe_w, fcntl.F_GETFL, 0) - flags = flags | os.O_NONBLOCK - flags = fcntl.fcntl(pipe_w, fcntl.F_SETFL, flags) - - signal.set_wakeup_fd(pipe_w) - - old_handlers = _signals(map(lambda s: (s, lambda signal, frame: None), - [signal.SIGWINCH, - signal.SIGCHLD, - signal.SIGHUP, - signal.SIGTERM, - signal.SIGQUIT])) - - _set_pty_size() - - with raw(pty.STDIN_FILENO): - try: - _copy(pipe_r) - except (IOError, OSError): - pass - - _signals(old_handlers) - - os.waitpid(pid, 0) diff -Nru asciinema-2.0.1/asciinema/term.py asciinema-2.0.2/asciinema/term.py --- asciinema-2.0.1/asciinema/term.py 2018-04-04 07:05:41.000000000 +0000 +++ asciinema-2.0.2/asciinema/term.py 2019-01-12 18:58:29.000000000 +0000 @@ -1,6 +1,7 @@ -import tty -import select import os +import select +import subprocess +import tty class raw(): @@ -26,3 +27,11 @@ return os.read(fd, 1024) return b'' + + +def get_size(): + # TODO maybe use os.get_terminal_size ? + return ( + int(subprocess.check_output(['tput', 'cols'])), + int(subprocess.check_output(['tput', 'lines'])) + ) diff -Nru asciinema-2.0.1/asciinema/urllib_http_adapter.py asciinema-2.0.2/asciinema/urllib_http_adapter.py --- asciinema-2.0.1/asciinema/urllib_http_adapter.py 2018-04-04 07:05:41.000000000 +0000 +++ asciinema-2.0.2/asciinema/urllib_http_adapter.py 2019-01-12 18:58:29.000000000 +0000 @@ -89,6 +89,6 @@ def _parse_headers(self, response): headers = {} for k, v in response.getheaders(): - headers[k] = v + headers[k.lower()] = v return headers diff -Nru asciinema-2.0.1/CHANGELOG.md asciinema-2.0.2/CHANGELOG.md --- asciinema-2.0.1/CHANGELOG.md 2018-04-04 07:05:41.000000000 +0000 +++ asciinema-2.0.2/CHANGELOG.md 2019-01-12 18:58:29.000000000 +0000 @@ -1,5 +1,15 @@ # asciinema changelog +## 2.0.2 (2019-01-12) + +* Official support for Python 3.7 +* Recording is now possible on US-ASCII locale (thanks Jean-Philippe @jpouellet Ouellet!) +* Improved Android support (thanks Fredrik @fornwall Fornwall!) +* Possibility of programatic recording with `asciinema.record_asciicast` function +* Uses new JSON response format added recently to asciinema-server +* Tweaked message about how to stop recording (thanks Bachynin @vanyakosmos Ivan!) +* Added proper description and other metadata to Python package (thanks @Crestwave!) + ## 2.0.1 (2018-04-04) * Fixed example in asciicast v2 format doc (thanks Josh "@anowlcalledjosh" Holland!) diff -Nru asciinema-2.0.1/debian/changelog asciinema-2.0.2/debian/changelog --- asciinema-2.0.1/debian/changelog 2018-12-29 14:24:45.000000000 +0000 +++ asciinema-2.0.2/debian/changelog 2019-01-21 00:51:50.000000000 +0000 @@ -1,6 +1,13 @@ +asciinema (2.0.2-1) unstable; urgency=medium + + * New upstream release (2.0.2-1): + - Update debian/patches. + + -- Josue Ortega Sun, 20 Jan 2019 18:51:50 -0600 + asciinema (2.0.1-2) unstable; urgency=medium - * New maintainer (Closes: #903448) + * New maintainer (Closes: #903438) * Bump Standards-Version to 4.3.0, no changes required * debian/watch: Enable crytpographic verification diff -Nru asciinema-2.0.1/debian/patches/disable-write-pty.patch asciinema-2.0.2/debian/patches/disable-write-pty.patch --- asciinema-2.0.1/debian/patches/disable-write-pty.patch 2018-12-29 14:24:45.000000000 +0000 +++ asciinema-2.0.2/debian/patches/disable-write-pty.patch 2019-01-21 00:51:50.000000000 +0000 @@ -2,19 +2,19 @@ Until there is a way to use pts devices inside build environments this code will cause FTBFS on buildds, Bug: #817236 Author: gustavo panizzo -Last-Update: 2016-10-10 -Origin: vendor ---- a/tests/pty_recorder_test.py -+++ b/tests/pty_recorder_test.py -@@ -32,11 +32,3 @@ class TestPtyRecorder(Test): +Last-Update: 2019-01-20 + +--- a/tests/pty_test.py ++++ b/tests/pty_test.py +@@ -31,11 +31,3 @@ + def os_write(self, fd, data): if fd != pty.STDOUT_FILENO: self.real_os_write(fd, data) - +- - def test_record_command_writes_to_stdout(self): -- pty_recorder = PtyRecorder() - output = FakeStdout() - - command = ['python3', '-c', "import sys; import time; sys.stdout.write(\'foo\'); sys.stdout.flush(); time.sleep(0.01); sys.stdout.write(\'bar\')"] -- pty_recorder.record_command(command, output) +- asciinema.pty.record(command, output) - - assert_equal([b'foo', b'bar'], output.data) diff -Nru asciinema-2.0.1/debian/patches/use-nosetests3.patch asciinema-2.0.2/debian/patches/use-nosetests3.patch --- asciinema-2.0.1/debian/patches/use-nosetests3.patch 2018-12-29 14:24:45.000000000 +0000 +++ asciinema-2.0.2/debian/patches/use-nosetests3.patch 2019-01-21 00:51:50.000000000 +0000 @@ -4,7 +4,7 @@ Origin: vendor --- a/Makefile +++ b/Makefile -@@ -4,7 +4,7 @@ VERSION=`python3 -c "import asciinema; p +@@ -4,7 +4,7 @@ test: test-unit test-integration test-unit: diff -Nru asciinema-2.0.1/.gitignore asciinema-2.0.2/.gitignore --- asciinema-2.0.1/.gitignore 2018-04-04 07:05:41.000000000 +0000 +++ asciinema-2.0.2/.gitignore 2019-01-12 18:58:29.000000000 +0000 @@ -1,3 +1,120 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# custom /dist tmp *.pyc diff -Nru asciinema-2.0.1/README.md asciinema-2.0.2/README.md --- asciinema-2.0.1/README.md 2018-04-04 07:05:41.000000000 +0000 +++ asciinema-2.0.2/README.md 2019-01-12 18:58:29.000000000 +0000 @@ -7,7 +7,7 @@ Terminal session recorder and the best companion of [asciinema.org](https://asciinema.org). -[![demo](https://asciinema.org/a/113463.png)](https://asciinema.org/a/113463?autoplay=1) +[![demo](https://asciinema.org/a/113463.svg)](https://asciinema.org/a/113463?autoplay=1) ## Quick intro @@ -32,7 +32,7 @@ You can pass `-i 2` to `asciinema rec` as well, to set it permanently on a recording. Idle time limiting makes the recordings much more interesting to -watch, try it. +watch. Try it. If you want to watch and share it on the web, upload it: @@ -47,10 +47,10 @@ asciinema rec -You'll be asked to confirm the upload when the recording is done, so nothing is +You'll be asked to confirm the upload when the recording is done. Nothing is sent anywhere without your consent. -These were the basics, but there's much more you can do. The following sections +These are the basics, but there's much more you can do. The following sections cover installation, usage and hosting of the recordings in more detail. ## Installation @@ -71,16 +71,6 @@ X, Linux and FreeBSD. Look for package named `asciinema`. See the [list of available packages](https://asciinema.org/docs/installation). -### Snap supported Linux distros - -If you run [Ubuntu or other snap supported Linux distribution](https://snapcraft.io/docs/core/install) you can -install asciinema with: - - snap install asciinema --classic - -The snap contains all necessary dependencies required to run asciinema, and will -get automatically updated when a new version is pushed to the store. - ### Docker image asciinema Docker image is based on Ubuntu 16.04 and has the latest version of @@ -363,10 +353,10 @@ ## Authors Developed with passion by [Marcin Kulik](http://ku1ik.com) and great open -source [contributors](https://github.com/asciinema/asciinema/contributors) +source [contributors](https://github.com/asciinema/asciinema/contributors). ## License -Copyright © 2011-2017 Marcin Kulik. +Copyright © 2011–2018 Marcin Kulik. All code is licensed under the GPL, v3 or later. See LICENSE file for details. diff -Nru asciinema-2.0.1/setup.py asciinema-2.0.2/setup.py --- asciinema-2.0.1/setup.py 2018-04-04 07:05:41.000000000 +0000 +++ asciinema-2.0.2/setup.py 2019-01-12 18:58:29.000000000 +0000 @@ -7,6 +7,10 @@ url_template = 'https://github.com/asciinema/asciinema/archive/v%s.tar.gz' requirements = [] +test_requirements = ['nose'] + +with open('README.md', encoding='utf8') as file: + long_description = file.read() setup( name='asciinema', @@ -14,6 +18,8 @@ packages=['asciinema', 'asciinema.commands', 'asciinema.asciicast'], license='GNU GPLv3', description='Terminal session recorder', + long_description=long_description, + long_description_content_type='text/markdown', author=asciinema.__author__, author_email='m@ku1ik.com', url='https://asciinema.org', @@ -23,7 +29,15 @@ 'asciinema = asciinema.__main__:main', ], }, + data_files=[('share/doc/asciinema', ['CHANGELOG.md', + 'CODE_OF_CONDUCT.md', + 'CONTRIBUTING.md', + 'README.md', + 'doc/asciicast-v1.md', + 'doc/asciicast-v2.md']), + ('share/man/man1', ['man/asciinema.1'])], install_requires=requirements, + tests_require=test_requirements, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', @@ -37,6 +51,7 @@ 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Topic :: System :: Shells', 'Topic :: Terminals', 'Topic :: Utilities' diff -Nru asciinema-2.0.1/tests/config_test.py asciinema-2.0.2/tests/config_test.py --- asciinema-2.0.1/tests/config_test.py 2018-04-04 07:05:41.000000000 +0000 +++ asciinema-2.0.2/tests/config_test.py 2019-01-12 18:58:29.000000000 +0000 @@ -29,7 +29,7 @@ config.upgrade() install_id = read_install_id(config.install_id_path) - assert re.match('^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}', install_id) + assert re.match('^\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}', install_id) assert_equal(install_id, config.install_id) assert not path.exists(config.config_file_path) diff -Nru asciinema-2.0.1/tests/pty_recorder_test.py asciinema-2.0.2/tests/pty_recorder_test.py --- asciinema-2.0.1/tests/pty_recorder_test.py 2018-04-04 07:05:41.000000000 +0000 +++ asciinema-2.0.2/tests/pty_recorder_test.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,42 +0,0 @@ -import os -import pty - -from nose.tools import assert_equal -from .test_helper import Test - -from asciinema.pty_recorder import PtyRecorder - - -class FakeStdout: - - def __init__(self): - self.data = [] - - def write_stdout(self, data): - self.data.append(data) - - def write_stdin(self, data): - pass - - -class TestPtyRecorder(Test): - - def setUp(self): - self.real_os_write = os.write - os.write = self.os_write - - def tearDown(self): - os.write = self.real_os_write - - def os_write(self, fd, data): - if fd != pty.STDOUT_FILENO: - self.real_os_write(fd, data) - - def test_record_command_writes_to_stdout(self): - pty_recorder = PtyRecorder() - output = FakeStdout() - - command = ['python3', '-c', "import sys; import time; sys.stdout.write(\'foo\'); sys.stdout.flush(); time.sleep(0.01); sys.stdout.write(\'bar\')"] - pty_recorder.record_command(command, output) - - assert_equal([b'foo', b'bar'], output.data) diff -Nru asciinema-2.0.1/tests/pty_test.py asciinema-2.0.2/tests/pty_test.py --- asciinema-2.0.1/tests/pty_test.py 1970-01-01 00:00:00.000000000 +0000 +++ asciinema-2.0.2/tests/pty_test.py 2019-01-12 18:58:29.000000000 +0000 @@ -0,0 +1,41 @@ +import os +import pty + +from nose.tools import assert_equal +from .test_helper import Test + +import asciinema.pty + + +class FakeStdout: + + def __init__(self): + self.data = [] + + def write_stdout(self, data): + self.data.append(data) + + def write_stdin(self, data): + pass + + +class TestRecord(Test): + + def setUp(self): + self.real_os_write = os.write + os.write = self.os_write + + def tearDown(self): + os.write = self.real_os_write + + def os_write(self, fd, data): + if fd != pty.STDOUT_FILENO: + self.real_os_write(fd, data) + + def test_record_command_writes_to_stdout(self): + output = FakeStdout() + + command = ['python3', '-c', "import sys; import time; sys.stdout.write(\'foo\'); sys.stdout.flush(); time.sleep(0.01); sys.stdout.write(\'bar\')"] + asciinema.pty.record(command, output) + + assert_equal([b'foo', b'bar'], output.data) diff -Nru asciinema-2.0.1/.travis.yml asciinema-2.0.2/.travis.yml --- asciinema-2.0.1/.travis.yml 2018-04-04 07:05:41.000000000 +0000 +++ asciinema-2.0.2/.travis.yml 2019-01-12 18:58:29.000000000 +0000 @@ -1,15 +1,17 @@ -sudo: false +sudo: required +dist: xenial language: python python: - "3.4" - "3.5" - "3.6" - - "3.7-dev" + - "3.7" + - "3.8-dev" before_install: - - pip install pep8 + - pip install pycodestyle script: - - find . -name \*.py -exec pep8 --ignore=E501 {} + + - find . -name \*.py -exec pycodestyle --ignore=E501,E402 {} + - make test