├── .gitignore ├── MANIFEST.in ├── README.md ├── VERSION ├── mklsgit.py ├── screenshot.png ├── scripts ├── build.sh ├── packaging.rc └── upload.sh └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE 2 | .idea/ 3 | 4 | # Python 5 | *.py[cod] 6 | __pycache__/ 7 | 8 | # Mac 9 | .DS_Store 10 | 11 | # Pip 12 | dist/ 13 | *.egg-info/ 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include VERSION 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ls-git 2 | 3 | Author: [Michael Kim](http://michaelkim0407.com) 4 | 5 | ***IMPORTANT*** This package has been merged into [mklibpy-bin](https://github.com/MichaelKim0407/mklibpy/tree/master/mklibpy-bin) (v0.8) and is now inactive. Please install that package instead. 6 | 7 | ## Compatibility 8 | 9 | * Does **not** support Python 2 10 | 11 | * Supports Python 3.5 & 3.6 12 | 13 | Previous Python 3 versions may be supported, but has not been tested. Please open a pull request for this README if you tested this package with another version of Python 3. 14 | 15 | * Tested on: 16 | 17 | - MacOS Sierra 10.12.6 + Python 3.6.2 18 | 19 | - Ubuntu Linux 16.04 + Python 3.5.2 20 | 21 | - git-bash (Git for Windows 2.8.0) + Python 3.5.2 (See issue [#3](https://github.com/MichaelKim0407/mk-ls-git/issues/3)) 22 | 23 | ## Installation 24 | 25 | ``` 26 | pip3 install mklsgit 27 | ``` 28 | 29 | ## Usage 30 | 31 | Replace `ls` with `ls-git` and pass arguments as normal. 32 | 33 | If `-l` option is specified, git-branch will be appended if the directory is the root directory of a git repo. 34 | 35 | If in colored mode (`--color` for GNU ls or `-G` for BSD ls), git-branch will also be colored. 36 | 37 | ![Screenshot](screenshot.png) 38 | 39 | ## Known Issues 40 | 41 | Please refer to Issues on GitHub. 42 | 43 | ## License 44 | 45 | MIT 46 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.4 2 | -------------------------------------------------------------------------------- /mklsgit.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import sys 4 | 5 | try: 6 | import pty 7 | except ImportError: 8 | PTY = False 9 | else: 10 | PTY = True 11 | 12 | from mklibpy.common.string import AnyString 13 | from mklibpy.terminal.colored_text import get_text, remove_switch 14 | from mklibpy.util.path import CD 15 | 16 | __author__ = 'Michael' 17 | 18 | TIMEOUT = 0.5 19 | 20 | print("""`mklsgit` has been merged into `mklibpy-bin` (v0.8). 21 | Please uninstall this package and install `mklibpy-bin` instead.""", file=sys.stderr) 22 | 23 | 24 | def system_call(*args, **kwargs): 25 | out = subprocess.check_output(*args, **kwargs) 26 | return out.decode().splitlines(False) 27 | 28 | 29 | if PTY: 30 | def system_call_pty(*args, **kwargs): 31 | """ 32 | Opens a pty for stdout, so that colored output is retained. 33 | """ 34 | master, slave = pty.openpty() 35 | p = subprocess.Popen(*args, **kwargs, stdout=slave) 36 | code = p.wait(timeout=TIMEOUT) 37 | if code != 0: 38 | raise subprocess.CalledProcessError(code, args[0]) 39 | 40 | # echo an empty line so that we can properly break 41 | subprocess.call(['echo', ''], stdout=slave) 42 | 43 | def __gen(): 44 | with os.fdopen(master) as f: 45 | for line in f: 46 | line = line.strip() 47 | if not line: 48 | break 49 | yield line 50 | 51 | return __gen() 52 | 53 | 54 | def is_git_repo(abspath): 55 | path = os.path.join(abspath, ".git") 56 | return os.path.exists(path) and os.path.isdir(path) 57 | 58 | 59 | def get_git_branch(abspath): 60 | with CD(abspath): 61 | for line in system_call(['git', 'branch']): 62 | if not line.startswith("*"): 63 | continue 64 | return line.lstrip("*").strip() 65 | 66 | 67 | class LsGit(object): 68 | def __init__(self, stdout=None): 69 | self.stdout = stdout 70 | if stdout is None: 71 | self.stdout = sys.stdout 72 | 73 | @property 74 | def is_tty(self): 75 | return self.stdout.isatty() 76 | 77 | @property 78 | def is_gnu(self): 79 | try: 80 | system_call(['ls', '--version'], stderr=subprocess.DEVNULL) 81 | except subprocess.CalledProcessError: 82 | return False 83 | else: 84 | return True 85 | 86 | def print(self, *args, **kwargs): 87 | print(*args, **kwargs, file=self.stdout) 88 | 89 | def __call__(self, *args): 90 | LsGitProcess(self, args).run() 91 | 92 | 93 | class LsGitProcess(object): 94 | def __init__(self, parent, args): 95 | self.__parent = parent 96 | self.__args = args 97 | self.__cmd = ['ls'] + list(self.__args) 98 | 99 | self.__flags = None 100 | self.__options = None 101 | self.__dirs = None 102 | self.__cur_dir = None 103 | 104 | self.__parse_args() 105 | 106 | def __parse_args(self): 107 | self.__flags = AnyString([ 108 | arg 109 | for arg in self.__args 110 | if arg.startswith('-') and not arg.startswith('--') 111 | ]) 112 | self.__options = AnyString([ 113 | arg 114 | for arg in self.__args 115 | if arg.startswith('--') 116 | ]) 117 | self.__dirs = [ 118 | arg 119 | for arg in self.__args 120 | if not arg.startswith('-') 121 | ] 122 | 123 | @property 124 | def _l(self): 125 | return 'l' in self.__flags 126 | 127 | @property 128 | def __color(self): 129 | if self.__parent.is_gnu: 130 | if not self.__options.startswith('--color'): 131 | return False 132 | if self.__options == '--color' or self.__options == '--color=always': 133 | return True 134 | elif self.__options == '--color=auto': 135 | return self.__parent.is_tty 136 | else: 137 | return False 138 | 139 | else: 140 | if not self.__parent.is_tty: 141 | return False 142 | return 'G' in self.__flags 143 | 144 | def color(self, text, color=None, mode=None): 145 | if not self.__color: 146 | return text 147 | return get_text(text, color=color, mode=mode) 148 | 149 | def __process_line(self, line): 150 | if line.endswith(':') and line[:-1] in self.__dirs: 151 | self.__cur_dir = line[:-1] 152 | return line 153 | 154 | sp = line.split() 155 | if len(sp) < 9: 156 | return line 157 | 158 | dir = sp[8] 159 | if self.__color: 160 | dir = remove_switch(dir) 161 | 162 | abspath = os.path.abspath(os.path.join(self.__cur_dir, dir)) 163 | if not is_git_repo(abspath): 164 | return line 165 | 166 | branch = get_git_branch(abspath) 167 | return line + self.color(" ({})".format(branch), color='red', mode='bold') 168 | 169 | def __native_call(self): 170 | return subprocess.check_call(self.__cmd, stdout=self.__parent.stdout) 171 | 172 | def __system_call(self): 173 | return system_call(self.__cmd) 174 | 175 | if PTY: 176 | def __system_call_pty(self): 177 | return system_call_pty(self.__cmd) 178 | 179 | def run(self): 180 | if not self._l: 181 | self.__native_call() 182 | return 183 | 184 | if self.__dirs: 185 | self.__cur_dir = self.__dirs[0] 186 | else: 187 | self.__cur_dir = os.getcwd() 188 | 189 | if not PTY: 190 | # See Issue #3 191 | lines = self.__system_call() 192 | workaround_flag = True 193 | elif not self.__color: 194 | lines = self.__system_call() 195 | workaround_flag = False 196 | else: 197 | # This is a workaround for a bug on Mac. See Issue #1 on GitHub 198 | try: 199 | lines = self.__system_call_pty() 200 | workaround_flag = False 201 | except subprocess.TimeoutExpired: 202 | lines = self.__system_call() 203 | workaround_flag = True 204 | 205 | if not workaround_flag: 206 | for line in lines: 207 | self.__parent.print(self.__process_line(line)) 208 | 209 | else: 210 | new_lines = [] 211 | modified_flag = False 212 | for line in lines: 213 | if modified_flag: 214 | self.__parent.print(self.__process_line(line)) 215 | continue 216 | 217 | new_line = self.__process_line(line) 218 | if new_line == line: 219 | new_lines.append(line) 220 | continue 221 | 222 | modified_flag = True 223 | for line0 in new_lines: 224 | self.__parent.print(line0) 225 | self.__parent.print(new_line) 226 | 227 | if not modified_flag: 228 | self.__native_call() 229 | 230 | 231 | def main(args=None): 232 | if args is None: 233 | import sys 234 | args = sys.argv[1:] 235 | 236 | instance = LsGit() 237 | try: 238 | instance(*args) 239 | except subprocess.CalledProcessError as e: 240 | exit(e.returncode) 241 | 242 | 243 | if __name__ == '__main__': 244 | main() 245 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MichaelKim0407/mk-ls-git/72997f56314c73e650b76a62399f750b4668ae92/screenshot.png -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cwd=$(pwd) 4 | 5 | cd $(dirname $0) 6 | 7 | source "packaging.rc" 8 | 9 | cd .. 10 | 11 | if [ -f ${dist} ]; then 12 | echo "Package already exists. Did you forget to increase version number?" 13 | exit 1 14 | fi; 15 | 16 | python3 setup.py sdist 17 | 18 | cd ${cwd} 19 | -------------------------------------------------------------------------------- /scripts/packaging.rc: -------------------------------------------------------------------------------- 1 | version=$(cat ../VERSION) 2 | dist="dist/mklsgit-$version.tar.gz" 3 | -------------------------------------------------------------------------------- /scripts/upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cwd=$(pwd) 4 | 5 | cd $(dirname $0) 6 | 7 | source "packaging.rc" 8 | 9 | cd .. 10 | 11 | if [ ! -f ${dist} ]; then 12 | echo "Package does not exist. Did you forget to build?" 13 | exit 1 14 | fi; 15 | 16 | twine upload ${dist} 17 | 18 | cd ${cwd} 19 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from setuptools import setup 4 | 5 | root_dir = os.path.abspath(os.path.dirname(__file__)) 6 | 7 | with open(os.path.join(root_dir, "VERSION")) as f: 8 | VERSION = f.read().rstrip() 9 | 10 | setup( 11 | name="mklsgit", 12 | 13 | version=VERSION, 14 | 15 | install_requires=[ 16 | 'mklibpy>=0.6' 17 | ], 18 | 19 | py_modules=['mklsgit'], 20 | 21 | entry_points={ 22 | 'console_scripts': [ 23 | 'ls-git=mklsgit:main', 24 | ], 25 | }, 26 | 27 | url="https://github.com/MichaelKim0407/mk-ls-git", 28 | 29 | license="MIT", 30 | 31 | author="Michael Kim", 32 | 33 | author_email="mkim0407@gmail.com", 34 | 35 | description="ls command with git branch", 36 | 37 | classifiers=[ 38 | "Development Status :: 3 - Alpha", 39 | 40 | "Intended Audience :: Developers", 41 | "Intended Audience :: End Users/Desktop", 42 | 43 | "License :: OSI Approved :: MIT License", 44 | 45 | "Programming Language :: Python :: 3.5", 46 | "Programming Language :: Python :: 3.6", 47 | 48 | "Topic :: Software Development :: Libraries", 49 | "Topic :: Terminals", 50 | "Topic :: Utilities", 51 | ] 52 | ) 53 | --------------------------------------------------------------------------------