1#!/usr/local/bin/python 2# -*- coding: utf-8 -*- 3# cython: language_level=3, always_allow_keywords=True 4 5## Copyright 2007-2018 by LivingLogic AG, Bayreuth/Germany 6## Copyright 2007-2018 by Walter Dörwald 7## 8## All Rights Reserved 9## 10## See ll/xist/__init__.py for the license 11 12 13""" 14Purpose 15======= 16 17:program:`ucp` is a script that copies files or directories. It is an 18URL-enabled version of the :command:`cp` system command. Via :mod:`ll.url` and 19:mod:`ll.orasql` :program:`ucp` supports ``ssh`` and ``oracle`` URLs. 20 21 22Options 23======= 24 25:program:`ucp` supports the following options: 26 27.. program:: ucp 28 29.. option:: urls 30 31 Two or more URLs. If more than two URLs are given or the last URL refers 32 to an existing directory, the last URL is the target directory. All other 33 sources are copied into this target directory. Otherwise one file is 34 copied to another file. 35 36.. option:: -v <flag>, --verbose <flag> 37 38 Give a report during the copy process about the files copied and their 39 sizes? 40 (Valid flag values are ``false``, ``no``, ``0``, ``true``, ``yes`` or ``1``) 41 42.. option:: -c <mode>, --color <mode> 43 44 Should the output be colored? If ``auto`` is specified (the default) then 45 the output is colored if stdout is a terminal. Valid modes are ``yes``, 46 ``no`` or ``auto``. 47 48.. option:: -u <user>, --user <user> 49 50 A user id or name. If given :program:`ucp` will change the owner of the 51 target files. 52 53.. option:: -g <group>, --group <group> 54 55 A group id or name. If given :program:`ucp` will change the group of the 56 target files. 57 58.. option:: -r <flag>, --recursive <flag> 59 60 Copies files recursively. 61 (Valid flag values are ``false``, ``no``, ``0``, ``true``, ``yes`` or ``1``) 62 63.. option:: -x <flag>, --ignoreerrors <flag> 64 65 Ignores errors occuring during the copy process (otherwise the copy 66 process is aborted). 67 (Valid flag values are ``false``, ``no``, ``0``, ``true``, ``yes`` or ``1``) 68 69.. option:: -i <pattern(s)>, --include <pattern(s)> 70 71 Only copy files that match one of the specified patterns. 72 73.. option:: -e <pattern(s)>, --exclude <pattern(s)> 74 75 Don't copy files that match one of the specified patterns. 76 77.. option:: --enterdir <pattern(s)> 78 79 Only enter directories that match one of the specified patterns. 80 81.. option:: --skipdir <pattern(s)> 82 83 Skip directories that match one of the specified patterns. 84 85.. option:: --ignorecase <flag> 86 87 Perform case-insensitive pattern matching? 88 (Valid flag values are ``false``, ``no``, ``0``, ``true``, ``yes`` or ``1``) 89 90 91Examples 92======== 93 94Copy one file to another: 95 96.. sourcecode:: bash 97 98 $ ucp foo.txt bar.txt 99 100Copy a file into an existing directory: 101 102.. sourcecode:: bash 103 104 $ ucp foo.txt dir/ 105 106Copy multiple files into a new or existing directory (and give a progress 107report): 108 109.. sourcecode:: bash 110 111 $ ucp foo.txt bar.txt baz.txt dir/ -v 112 ucp: foo.txt -> dir/foo.txt (1,114 bytes) 113 ucp: bar.txt -> dir/bar.txt (2,916 bytes) 114 ucp: baz.txt -> dir/baz.txt (35,812 bytes) 115 116Recursively copy the schema objects in an Oracle database to a local directory: 117 118.. sourcecode:: bash 119 120 ucp oracle://user:pwd@oracle.example.org/ db/ -r 121 122Recursively copy the schema objects in an Oracle database to a remote directory: 123 124.. sourcecode:: bash 125 126 ucp oracle://user:pwd@oracle.example.org/ ssh://user@www.example.org/~/db/ -r 127""" 128 129 130import sys, re, argparse, contextlib 131 132from ll import misc, url 133 134try: 135 import astyle 136except ImportError: 137 from ll import astyle 138 139try: 140 from ll import orasql # activate the oracle scheme 141except ImportError: 142 pass 143 144 145__docformat__ = "reStructuredText" 146 147 148def main(args=None): 149 def copyone(urlread, urlwrite): 150 strurlread = str(urlread) 151 if urlread.isdir(): 152 if args.recursive: 153 for u in urlread.walkfiles(include=args.include, exclude=args.exclude, enterdir=args.enterdir, skipdir=args.skipdir, ignorecase=args.ignorecase): 154 copyone(urlread/u, urlwrite/u) 155 else: 156 if args.verbose: 157 msg = astyle.style_default("ucp: ", astyle.style_url(strurlread), astyle.style_warn(" (directory skipped)")) 158 stderr.writeln(msg) 159 else: 160 if args.verbose: 161 msg = astyle.style_default("ucp: ", astyle.style_url(strurlread), " -> ") 162 stderr.write(msg) 163 try: 164 with contextlib.closing(urlread.open("rb")) as fileread: 165 with contextlib.closing(urlwrite.open("wb")) as filewrite: 166 size = 0 167 while True: 168 data = fileread.read(262144) 169 if data: 170 filewrite.write(data) 171 size += len(data) 172 else: 173 break 174 if user or group: 175 urlwrite.chown(user, group) 176 except Exception as exc: 177 if args.ignoreerrors: 178 if args.verbose: 179 exctype = misc.format_class(exc) 180 excmsg = str(exc).replace("\n", " ").strip() 181 msg = astyle.style_error(f" (failed with {exctype}: {excmsg})") 182 stderr.writeln(msg) 183 else: 184 raise 185 else: 186 if args.verbose: 187 msg = astyle.style_default(astyle.style_url(str(urlwrite)), f" ({size:,} bytes)") 188 stderr.writeln(msg) 189 190 p = argparse.ArgumentParser(description="Copies URLs", epilog="For more info see http://python.livinglogic.de/scripts_ucp.html") 191 p.add_argument("urls", metavar="url", help="either one source and one target file, or multiple source files and one target dir", nargs="*", type=url.URL) 192 p.add_argument("-v", "--verbose", dest="verbose", help="Be verbose? (default: %(default)s)", action=misc.FlagAction, default=False) 193 p.add_argument("-c", "--color", dest="color", help="Color output (default: %(default)s)", default="auto", choices=("yes", "no", "auto")) 194 p.add_argument("-u", "--user", dest="user", help="user id or name for target files") 195 p.add_argument("-g", "--group", dest="group", help="group id or name for target files") 196 p.add_argument("-r", "--recursive", dest="recursive", help="Copy stuff recursively? (default: %(default)s)", action=misc.FlagAction, default=False) 197 p.add_argument("-x", "--ignoreerrors", dest="ignoreerrors", help="Ignore errors? (default: %(default)s)", action=misc.FlagAction, default=False) 198 p.add_argument("-i", "--include", dest="include", metavar="PATTERN", help="Include only URLs matching PATTERN", action="append") 199 p.add_argument("-e", "--exclude", dest="exclude", metavar="PATTERN", help="Exclude URLs matching PATTERN", action="append") 200 p.add_argument( "--enterdir", dest="enterdir", metavar="PATTERN", help="Only enter directories matching PATTERN", action="append") 201 p.add_argument( "--skipdir", dest="skipdir", metavar="PATTERN", help="Skip directories matching PATTERN", action="append") 202 p.add_argument( "--ignorecase", dest="ignorecase", help="Perform case-insensitive name matching? (default: %(default)s)", action=misc.FlagAction, default=False) 203 204 args = p.parse_args(args) 205 if len(args.urls) < 2: 206 p.error("need at least one source url and one target url") 207 return 1 208 209 if args.color == "yes": 210 color = True 211 elif args.color == "no": 212 color = False 213 else: 214 color = None 215 stdout = astyle.Stream(sys.stdout, color) 216 stderr = astyle.Stream(sys.stderr, color) 217 218 user = args.user 219 try: 220 user = int(user) 221 except (TypeError, ValueError): 222 pass 223 224 group = args.group 225 try: 226 group = int(group) 227 except (TypeError, ValueError): 228 pass 229 230 with url.Context(): 231 urls = args.urls 232 if len(urls) > 2 or urls[-1].isdir(): # treat target as directory 233 for u in urls[:-1]: 234 copyone(u, urls[-1]/u.file) 235 else: 236 copyone(urls[0], urls[-1]) 237 238 239if __name__ == "__main__": 240 sys.exit(main()) 241