mirror of
https://github.com/asciinema/asciinema.git
synced 2025-12-16 11:48:13 +01:00
No more upload queue
Conflicts: bin/asciiio
This commit is contained in:
committed by
Marcin Kulik
parent
57c55d3c16
commit
1b0d87c8df
2
Makefile
2
Makefile
@@ -5,7 +5,7 @@ bin/asciiio: tmp/asciiio.zip
|
||||
cat tmp/asciiio.zip >> bin/asciiio
|
||||
chmod +x bin/asciiio
|
||||
|
||||
tmp/asciiio.zip: src/__main__.py src/asciicasts.py src/recorders.py src/timed_file.py src/uploader.py src/config.py src/options.py src/cli.py src/asciicast_recorder.py
|
||||
tmp/asciiio.zip: src/__main__.py src/asciicast.py src/recorders.py src/timed_file.py src/uploader.py src/config.py src/options.py src/cli.py
|
||||
mkdir -p tmp
|
||||
rm -rf tmp/asciiio.zip
|
||||
cd src && zip ../tmp/asciiio.zip *.py
|
||||
|
||||
92
src/asciicast.py
Normal file
92
src/asciicast.py
Normal file
@@ -0,0 +1,92 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from timed_file import TimedFile
|
||||
|
||||
|
||||
class Asciicast(object):
|
||||
|
||||
def __init__(self):
|
||||
self.dir_path = tempfile.mkdtemp()
|
||||
|
||||
self.command = None
|
||||
self.title = None
|
||||
self.shell = os.environ['SHELL']
|
||||
self.term = os.environ['TERM']
|
||||
self.username = os.environ['USER']
|
||||
self.uname = get_command_output(['uname', '-srvp'])
|
||||
self.recorded_at = time.time()
|
||||
|
||||
self.stdout_file = TimedFile(self.dir_path + '/stdout')
|
||||
self.stdin_file = TimedFile(self.dir_path + '/stdin')
|
||||
|
||||
@property
|
||||
def metadata_filename(self):
|
||||
return self.dir_path + '/meta.json'
|
||||
|
||||
@property
|
||||
def stdout_data_filename(self):
|
||||
return self.stdout_file.data_filename
|
||||
|
||||
@property
|
||||
def stdout_timing_filename(self):
|
||||
return self.stdout_file.timing_filename
|
||||
|
||||
@property
|
||||
def stdin_data_filename(self):
|
||||
return self.stdin_file.data_filename
|
||||
|
||||
@property
|
||||
def stdin_timing_filename(self):
|
||||
return self.stdin_file.timing_filename
|
||||
|
||||
# def open_files(self):
|
||||
# self.stdout_file.start_timing()
|
||||
# self.stdin_file.start_timing()
|
||||
|
||||
def save(self):
|
||||
self.save_streams()
|
||||
self.save_metadata()
|
||||
|
||||
def save_streams(self):
|
||||
self.stdout_file.close()
|
||||
self.stdin_file.close()
|
||||
|
||||
def save_metadata(self):
|
||||
lines = int(get_command_output(['tput', 'lines']))
|
||||
columns = int(get_command_output(['tput', 'cols']))
|
||||
|
||||
# RFC 2822
|
||||
recorded_at = time.strftime("%a, %d %b %Y %H:%M:%S +0000",
|
||||
time.gmtime(self.recorded_at))
|
||||
data = {
|
||||
'username' : self.username,
|
||||
'user_token' : self.user_token,
|
||||
'duration' : self.duration,
|
||||
'recorded_at': recorded_at,
|
||||
'title' : self.title,
|
||||
'command' : self.command,
|
||||
'shell' : self.shell,
|
||||
'uname' : self.uname,
|
||||
'term' : {
|
||||
'type' : self.term,
|
||||
'lines' : lines,
|
||||
'columns': columns
|
||||
}
|
||||
}
|
||||
|
||||
json_string = json.dumps(data, sort_keys=True, indent=4)
|
||||
with open(self.metadata_filename, 'wb') as f:
|
||||
f.write(json_string + '\n')
|
||||
|
||||
def remove(self):
|
||||
shutil.rmtree(self.dir_path)
|
||||
|
||||
|
||||
def get_command_output(args):
|
||||
process = subprocess.Popen(args, stdout=subprocess.PIPE)
|
||||
return process.communicate()[0].strip()
|
||||
@@ -1,95 +0,0 @@
|
||||
import os
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
from asciicasts import Asciicast
|
||||
from recorders import ProcessRecorder
|
||||
|
||||
|
||||
# def record(command, queue_dir_path):
|
||||
# # id = int(time.time())
|
||||
# # path = "%s/%i" % (queue_dir_path, id)
|
||||
# # asciicast = Asciicast(path)
|
||||
|
||||
# if sys.stdin.isatty():
|
||||
# record_process(command, asciicast.stdout_file)
|
||||
# else:
|
||||
# record_stdin(asciicast.stdout_file)
|
||||
|
||||
|
||||
# def record_stdin(stdout_file, stdin=sys.stdin):
|
||||
|
||||
|
||||
# def record_process(command, stdout_file, stdin_file=None):
|
||||
|
||||
|
||||
def record(queue_dir_path, user_token, options):
|
||||
# id = int(time.time())
|
||||
# path = "%s/%i" % (queue_dir_path, id)
|
||||
# asciicast = Asciicast(path)
|
||||
|
||||
self.command = options.command
|
||||
self.record_input = options.record_input
|
||||
self.always_yes = options.always_yes
|
||||
|
||||
self.recording_start_time = None
|
||||
self.duration = None
|
||||
|
||||
self.record_command()
|
||||
self.save_metadata(user_token=user_token, title=options.title)
|
||||
|
||||
return asciicast
|
||||
|
||||
def record_command(self):
|
||||
self.recording_start_time = time.time()
|
||||
cmd = self.command or os.environ['SHELL'].split()
|
||||
|
||||
stdin_file = None
|
||||
if self.record_input:
|
||||
stdin_file = self.asciicast.stdin_file
|
||||
|
||||
self.asciicast.open_files()
|
||||
|
||||
recorder = ProcessRecorder(cmd, self.asciicast.stdout_file, stdin_file)
|
||||
recorder.run()
|
||||
|
||||
now = time.time()
|
||||
self.duration = now - self.recording_start_time
|
||||
|
||||
self.asciicast.close_files()
|
||||
|
||||
def save_metadata(self):
|
||||
# RFC 2822
|
||||
recorded_at = time.strftime("%a, %d %b %Y %H:%M:%S +0000",
|
||||
time.gmtime(self.recording_start_time))
|
||||
|
||||
command = self.command and ' '.join(self.command)
|
||||
uname = get_command_output(['uname', '-srvp'])
|
||||
username = os.environ['USER']
|
||||
shell = os.environ['SHELL']
|
||||
term = os.environ['TERM']
|
||||
lines = int(get_command_output(['tput', 'lines']))
|
||||
columns = int(get_command_output(['tput', 'cols']))
|
||||
|
||||
data = {
|
||||
'username' : username,
|
||||
'user_token' : self.user_token,
|
||||
'duration' : self.duration,
|
||||
'recorded_at': recorded_at,
|
||||
'title' : self.title,
|
||||
'command' : command,
|
||||
'shell' : shell,
|
||||
'uname' : uname,
|
||||
'term' : {
|
||||
'type' : term,
|
||||
'lines' : lines,
|
||||
'columns': columns
|
||||
}
|
||||
}
|
||||
|
||||
self.asciicast.save_metadata(data)
|
||||
|
||||
|
||||
def get_command_output(args):
|
||||
process = subprocess.Popen(args, stdout=subprocess.PIPE)
|
||||
return process.communicate()[0].strip()
|
||||
@@ -1,59 +0,0 @@
|
||||
import os
|
||||
import glob
|
||||
import shutil
|
||||
import json
|
||||
|
||||
from timed_file import TimedFile
|
||||
|
||||
|
||||
def pending(dir):
|
||||
filenames = glob.glob(dir + '/*/*.time')
|
||||
return [Asciicast(os.path.dirname(f)) for f in filenames]
|
||||
|
||||
|
||||
class Asciicast(object):
|
||||
|
||||
def __init__(self, dir_path):
|
||||
self.dir_path = dir_path
|
||||
|
||||
if not os.path.isdir(self.dir_path):
|
||||
os.makedirs(self.dir_path)
|
||||
|
||||
self.stdout_file = TimedFile(self.dir_path + '/stdout')
|
||||
self.stdin_file = TimedFile(self.dir_path + '/stdin')
|
||||
|
||||
@property
|
||||
def metadata_filename(self):
|
||||
return self.dir_path + '/meta.json'
|
||||
|
||||
@property
|
||||
def stdout_data_filename(self):
|
||||
return self.stdout_file.data_filename
|
||||
|
||||
@property
|
||||
def stdout_timing_filename(self):
|
||||
return self.stdout_file.timing_filename
|
||||
|
||||
@property
|
||||
def stdin_data_filename(self):
|
||||
return self.stdin_file.data_filename
|
||||
|
||||
@property
|
||||
def stdin_timing_filename(self):
|
||||
return self.stdin_file.timing_filename
|
||||
|
||||
def open_files(self):
|
||||
self.stdout_file.start_timing()
|
||||
self.stdin_file.start_timing()
|
||||
|
||||
def close_files(self):
|
||||
self.stdout_file.close()
|
||||
self.stdin_file.close()
|
||||
|
||||
def save_metadata(self, data):
|
||||
json_string = json.dumps(data, sort_keys=True, indent=4)
|
||||
with open(self.metadata_filename, 'wb') as f:
|
||||
f.write(json_string + '\n')
|
||||
|
||||
def destroy(self):
|
||||
shutil.rmtree(self.dir_path)
|
||||
79
src/cli.py
79
src/cli.py
@@ -1,11 +1,11 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
import asciicasts
|
||||
from asciicast import Asciicast
|
||||
from config import Config
|
||||
from options import Options
|
||||
from uploader import Uploader
|
||||
import asciicast_recorder
|
||||
import recorders
|
||||
|
||||
|
||||
SCRIPT_NAME = os.path.basename(sys.argv[0])
|
||||
@@ -19,8 +19,6 @@ def run():
|
||||
|
||||
if action == 'rec':
|
||||
record()
|
||||
elif action == 'upload':
|
||||
upload_all_pending()
|
||||
elif action == 'auth':
|
||||
authenticate()
|
||||
elif action == 'help':
|
||||
@@ -34,44 +32,13 @@ def run():
|
||||
# Actions
|
||||
|
||||
def record():
|
||||
# check_pending()
|
||||
asciicast = record_asciicast()
|
||||
|
||||
# id = int(time.time())
|
||||
# path = "%s/%i" % (queue_dir_path, id)
|
||||
# asciicast = Asciicast(path)
|
||||
|
||||
asciicast = Asciicast()
|
||||
asciicast.command = options.command
|
||||
asciicast.title = options.title
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
if sys.stdin.isatty():
|
||||
record_process(command, asciicast.stdout_file)
|
||||
else:
|
||||
record_stdin(asciicast.stdout_file)
|
||||
|
||||
end_time = time.time()
|
||||
|
||||
asciicast.recorded_at = start_time
|
||||
asciicast.duration = end_time - start_time
|
||||
|
||||
asciicast.save()
|
||||
|
||||
# asciicast = asciicast_recorder.record(
|
||||
# config.queue_dir_path, config.user_token, options
|
||||
# )
|
||||
# asciicast = recorder.record()
|
||||
|
||||
if is_upload_requested():
|
||||
if upload_requested():
|
||||
print '~ Uploading...'
|
||||
upload_asciicast(asciicast)
|
||||
|
||||
|
||||
def upload_all_pending():
|
||||
print 'Uploading pending asciicasts...'
|
||||
for asciicast in pending_asciicasts():
|
||||
upload_asciicast(asciicast)
|
||||
asciicast.remove()
|
||||
|
||||
|
||||
def authenticate():
|
||||
@@ -96,17 +63,31 @@ def handle_unknown_action(action):
|
||||
|
||||
# Helpers
|
||||
|
||||
def check_pending():
|
||||
num = len(pending_asciicasts())
|
||||
if num > 0:
|
||||
print "Warning: %i recorded asciicasts weren't uploaded. " \
|
||||
'Run "%s upload" to upload them or delete them with ' \
|
||||
'"rm -rf %s/*".' \
|
||||
% (num, SCRIPT_NAME, config.queue_dir_path)
|
||||
def record_asciicast():
|
||||
asciicast = Asciicast()
|
||||
|
||||
if sys.stdin.isatty():
|
||||
if options.command:
|
||||
command = options.command
|
||||
is_shell = False
|
||||
else:
|
||||
command = os.environ['SHELL']
|
||||
is_shell = True
|
||||
|
||||
def pending_asciicasts():
|
||||
return asciicasts.pending(config.queue_dir_path)
|
||||
stdin_file = asciicast.stdin_file if options.record_input else None
|
||||
duration = recorders.record_process(command, is_shell,
|
||||
asciicast.stdout_file, stdin_file)
|
||||
else:
|
||||
duration = recorders.record_stream(sys.stdin, asciicast.stdout_file)
|
||||
|
||||
asciicast.user_token = config.user_token
|
||||
asciicast.command = options.command
|
||||
asciicast.title = options.title
|
||||
asciicast.duration = duration
|
||||
|
||||
asciicast.save()
|
||||
|
||||
return asciicast
|
||||
|
||||
|
||||
def upload_asciicast(asciicast):
|
||||
@@ -115,10 +96,9 @@ def upload_asciicast(asciicast):
|
||||
|
||||
if url:
|
||||
print url
|
||||
asciicast.destroy()
|
||||
|
||||
|
||||
def is_upload_requested():
|
||||
def upload_requested():
|
||||
if options.always_yes:
|
||||
return True
|
||||
|
||||
@@ -133,7 +113,6 @@ Asciicast recorder+uploader.
|
||||
|
||||
Actions:
|
||||
rec record asciicast (this is the default when no action given)
|
||||
upload upload recorded (but not uploaded) asciicasts
|
||||
auth authenticate and/or claim recorded asciicasts
|
||||
|
||||
Optional arguments:
|
||||
|
||||
@@ -7,7 +7,6 @@ class Config:
|
||||
def __init__(self):
|
||||
self.base_dir_path = os.path.expanduser("~/.ascii.io")
|
||||
self.config_filename = '%s/config' % self.base_dir_path
|
||||
self.queue_dir_path = '%s/queue' % self.base_dir_path
|
||||
|
||||
self.create_base_dir()
|
||||
self.parse_config_file()
|
||||
|
||||
@@ -32,7 +32,7 @@ class Options:
|
||||
elif opt == '--version':
|
||||
self.action = 'version'
|
||||
elif opt == '-c':
|
||||
self.command = arg.split()
|
||||
self.command = arg
|
||||
elif opt == '-t':
|
||||
self.title = arg
|
||||
elif opt == '-i':
|
||||
|
||||
@@ -1,53 +1,48 @@
|
||||
# TODO REFA
|
||||
|
||||
import os
|
||||
import pty
|
||||
import subprocess
|
||||
import signal
|
||||
import tty
|
||||
import array
|
||||
import fcntl
|
||||
import os
|
||||
import pty
|
||||
import select
|
||||
import select
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import termios
|
||||
import select
|
||||
|
||||
|
||||
import time
|
||||
import select
|
||||
import tty
|
||||
|
||||
|
||||
class StdinRecorder(object):
|
||||
def record_stream(stream, stdout_file):
|
||||
recorder = StreamRecorder(stream, stdout_file)
|
||||
start_time = time.time()
|
||||
recorder.run()
|
||||
end_time = time.time()
|
||||
return end_time - start_time
|
||||
|
||||
def __init__(self, stdout_file):
|
||||
|
||||
class StreamRecorder(object):
|
||||
|
||||
def __init__(self, stream, stdout_file):
|
||||
self.stream = stream
|
||||
self.stdout_file = stdout_file
|
||||
|
||||
def run(self):
|
||||
while 1:
|
||||
line = sys.stdin.readline()
|
||||
line = self.stream.readline()
|
||||
|
||||
if len(line) == 0:
|
||||
break
|
||||
|
||||
self.stdout_file.write(data)
|
||||
self.stdout_file.write(line)
|
||||
print line,
|
||||
|
||||
# descriptor = 0
|
||||
|
||||
# while 1:
|
||||
# try:
|
||||
# rfds, wfds, xfds = select.select([descriptor], [], [])
|
||||
# except select.error, e:
|
||||
# if e[0] == 4: # Interrupted system call.
|
||||
# continue
|
||||
|
||||
# if descriptor in rfds:
|
||||
# data = os.read(descriptor, 1024)
|
||||
|
||||
# if len(data) == 0:
|
||||
# break
|
||||
|
||||
# # self._write_stdout(data)
|
||||
# self.stdout_file.write(data)
|
||||
# # print time.time()
|
||||
# # print len(data)
|
||||
def record_process(command, is_shell, stdout_file, stdin_file=None):
|
||||
recorder = ProcessRecorder(command, is_shell, stdout_file, stdin_file)
|
||||
start_time = time.time()
|
||||
recorder.run()
|
||||
end_time = time.time()
|
||||
return end_time - start_time
|
||||
|
||||
|
||||
class ProcessRecorder(object):
|
||||
@@ -57,22 +52,28 @@ class ProcessRecorder(object):
|
||||
and saves stdin/stderr (and timing) to files.
|
||||
'''
|
||||
|
||||
def __init__(self, command, stdout_file, stdin_file=None):
|
||||
self.command = command
|
||||
def __init__(self, command, is_shell, stdout_file, stdin_file=None):
|
||||
self.command = command.split(' ')
|
||||
self.is_shell = is_shell
|
||||
self.stdout_file = stdout_file
|
||||
self.stdin_file = stdin_file
|
||||
self.master_fd = None
|
||||
|
||||
def run(self):
|
||||
self.reset_terminal()
|
||||
self._write_stdout('~ Asciicast recording started. Hit ^D (that\'s Ctrl+D) or type "exit" to finish.\n\n')
|
||||
|
||||
if self.is_shell:
|
||||
self._write_stdout('~ Asciicast recording started. Hit ^D (that\'s Ctrl+D) or type "exit" to finish.\n\n')
|
||||
|
||||
success = self._spawn()
|
||||
self.reset_terminal()
|
||||
self._write_stdout('~ Asciicast recording finished.\n')
|
||||
|
||||
return success
|
||||
|
||||
def reset_terminal(self):
|
||||
subprocess.call(["reset"])
|
||||
pass
|
||||
# subprocess.call(["reset"])
|
||||
|
||||
def _spawn(self):
|
||||
'''Create a spawned process.
|
||||
|
||||
Reference in New Issue
Block a user