From b9649e823f4ade8a996628c73c47d46f7f23e2be Mon Sep 17 00:00:00 2001 From: Adrian Petrescu Date: Wed, 20 Jul 2016 15:24:38 -0400 Subject: [PATCH] Add an `edit` command This allows in-place editing of files to a tmp file that is then transparently uploaded to S3. Much easier than s4cmd get + vim + s4cmd put. --- s4cmd.py | 35 ++++++++++++++++++++++++++++++++++- setup.py | 2 +- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/s4cmd.py b/s4cmd.py index bcf1c7b..709cc06 100755 --- a/s4cmd.py +++ b/s4cmd.py @@ -22,7 +22,8 @@ """ import sys, os, re, optparse, multiprocessing, fnmatch, time, hashlib, errno, pytz -import logging, traceback, types, threading, random, socket, shlex, datetime, json +import logging, tempfile, traceback, types, threading, random, socket, shlex, datetime, json +from subprocess import call IS_PYTHON2 = sys.version_info[0] == 2 @@ -61,6 +62,7 @@ def cmp(a, b): S3_SECRET_KEY_NAME = "S3_SECRET_KEY" S4CMD_ENV_KEY = "S4CMD_OPTS" +EDITOR = os.environ.get('EDITOR', 'vim') ## ## Utility classes @@ -246,6 +248,10 @@ def get_fixed_path(self): fi.append(p) return PATH_SEP.join(fi) + def get_file_name(self): + '''Get the final path component, without the directory''' + return self.path.split(PATH_SEP)[-1] + @staticmethod def combine(proto, bucket, path): '''Combine each component and general a S3 url string, no path normalization @@ -1652,6 +1658,33 @@ def cat_handler(self, args): self.s3handler().print_files(source) + @log_calls + def edit_handler(self, args): + '''Handler for edit command''' + self.opt.force = True + self.opt.ignore_empty_source = True + + self.validate('cmd|s3', args) + source = args[1] + # We append the old filename so that the editor can apply things like syntax highlighting if appropriate + target = os.path.join(tempfile.gettempdir(), hashlib.md5(source).hexdigest() + '-' + S3URL(source).get_file_name()) + + TEMP_FILES.add(target) + + self.s3handler().get_files(source, target) + # In case the file doesn't already exist on S3, just create an empty file to fill in. + open(target, 'a').close() + + old_content_hash = LocalMD5Cache(target).get_md5() + call([EDITOR, target]) + new_content_hash = LocalMD5Cache(target).get_md5() + + if old_content_hash == new_content_hash: + message('No changes detected') + else: + self.s3handler().put_files(target, source) + + @log_calls def dsync_handler(self, args): '''Handler for dsync command.''' diff --git a/setup.py b/setup.py index 1e1d820..9f4fc71 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ __author__ = "Chou-han Yang" __copyright__ = "Copyright 2014 BloomReach, Inc." __license__ = "http://www.apache.org/licenses/LICENSE-2.0" -__version__ = "2.1.0" +__version__ = "2.2.0" __maintainer__ = __author__ __status__ = "Development"