Skip to content

Instantly share code, notes, and snippets.

@tkfm-yamaguchi
Last active December 28, 2015 00:09
Show Gist options
  • Save tkfm-yamaguchi/7411336 to your computer and use it in GitHub Desktop.
Save tkfm-yamaguchi/7411336 to your computer and use it in GitHub Desktop.
ruby like pathname class for python
# coding: utf-8
import os
from os import path
import re
import shutil
from itertools import chain
DEFAULT_SEPARATOR = "/"
def unixize_separator(target):
"""
Use this to the result of path.normpath and path.join
"""
if os.sep == DEFAULT_SEPARATOR: return target
return target.replace(os.sep, DEFAULT_SEPARATOR)
class Pathname(object):
"""
Pathname represents the name of a file or directory on the filesystem,
but basically not the file itself.
"""
def __init__(self, _path):
if type(_path) == type(self):
self._path = _path.toString()
else:
self._path = unixize_separator(path.normpath(str(_path)))
def __str__(self):
return self._path
def __unicode__(self):
return self._path.encode()
def toString(self):
return str(self)
def to_s(self):
return self.toString()
## Properties whose type is Boolean {{{
def exists(self):
return path.exists(self._path)
def islink(self):
return path.islink(self._path)
def isfile(self):
if self.islink():
return path.isfile(str(self.realpath()))
else:
return path.isfile(self._path)
def isdir(self):
if self.islink():
return path.isfile(str(self.realpath()))
else:
return path.isdir(self._path)
# }}}
## Properties which returns Pathname instance {{{
def dirname(self):
return Pathname(path.dirname(self._path))
def basename(self):
return Pathname(path.basename(self._path))
def extname(self):
return Pathname(path.splitext(self._path)[1])
def realpath(self):
return Pathname(path.realpath(self._path))
def children(self):
return map(lambda p: self.join(p), chain.from_iterable(os.walk(self._path).next()[1:]))
# }}}
## Path operations with antoher path(es) {{{
def relative_path_from(self, start):
self_abs = path.abspath(str(self._path))
start_abs = path.abspath(str(start))
if self_abs == start_abs:
return Pathname(".")
else:
return Pathname(path.relpath(self_abs, start_abs))
def join(self, *descendants):
"""
join the pathnames.
NOTE:
use slash regardless of OS's default separator.
This is intended to use the path in the system
which is according to UNIX like hierarchy separator
(i.e. URL, etc.).
It should be noticed that this is different behavior
from python's default(os.path.join) who uses the
separator depending on the OS's default
(i.e. backslash on windows, slash on *nix)
"""
joined = self._path
for descendant in descendants:
joined = os.path.join(joined, str(descendant))
return Pathname(unixize_separator(joined))
def walk(self, topdown=True, onerror=None, followlinks=False):
for root, dirnames, filenames in os.walk(self._path, topdown, onerror, followlinks):
yield root, dirnames, filenames
def swap_extname(self, new_ext):
if not self.isfile():
raise Exception, "path should be file for the #swap_extname: {0}".format(self)
s_bname = self.basename().to_s()
s_extname = re.escape(self.extname().to_s())
return self.dirname().join( re.sub(s_extname, new_ext, s_bname) )
# }}}
## File system operations, means cp, mkdir ... etc. {{{
def make_symlink(self, old, relative=False):
_old = old
if relative:
_old = Pathname(old).relative_path_from(self.dirname())
return os.symlink(str(_old), str(self._path))
def cp(self, dest):
_dest = Pathname(dest)
if self.isfile():
if _dest.isdir():
return shutil.copy2(self._path, _dest.toString())
else:
return shutil.copyfile(self._path, _dest.toString())
elif self.isdir():
if _dest.isdir():
# copytree throws [Errno 17] if self.basename() and
# _dest.basename() were the same.
# THAT IS WIRED. In order to the common sense,
# copy command (cp) copies the source directory itself
# into the target directory in such case.
# And what the worse thing, python has no method which
# copy the directory to another directory.
# How should we deal with the empty directory?
# They say "use mkdir" ? Sucks.
def copy_recursively_manually(src_base, dest_base=_dest):
_dest_base = dest_base.join(src_base.basename())
_dest_base.mkdir()
for src_child in src_base.children():
_dest_child = _dest_base.join(src_child.basename())
if src_child.isfile():
src_child.cp(_dest_base)
else:
copy_recursively_manually(src_child, _dest_base)
return copy_recursively_manually(self)
else:
raise Exception, "Invalid copy operation: dir to file: '{0}' to '{1}'".format(self._path, _dest.toString())
else:
raise Exception, "#cp was called for the unkown file type; source:{0}, destination:{1}".format(self._path, _dest.toString())
def mkdir(self):
if not self.exists():
os.mkdir(self._path)
return self
def mkdir_p(self):
if not self.exists():
os.makedirs(self._path)
return self
def delete(self):
if self.isfile() or self.islink():
return os.unlink(self._path)
elif self.isdir():
return os.rmdir(self._path)
else:
raise Exception, "Unkown type: {0}".format(self._path)
# }}}
## Utilities which assumes the path is text file {{{
def read(self):
if not self.isfile():
raise Exception, "#read should be called for file but '{0}' looks directory or something".format(self)
return open(str(self), "r").read().decode("utf8")
def write(self, text, mode="w", overwrite=False, encode="utf_8"):
if self.exists() and not overwrite:
raise Exception, "#write was called but file was exists: '{0}'".format(self)
with open(str(self), mode) as f:
return f.write(text.encode(encode))
# }}}
# vim: fdm=marker commentstring=#\ %s
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment