diff --git a/README.md b/README.md index 7b8d2a5..57902fb 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ -### pip-date -- Date your pip packages! +### pip-date - Date your pip packages! -[![PyPI version][1]][2] -[![pypi supported versions][3]][4] +[![pypi supported versions][1]][2] +[![PyPI version][3]][4] [![Maintenance][5]][6] [![GitHub last commit][7]][8] [![Average time to resolve an issue][9]][10] -[1]: https://badge.fury.io/py/pip-date.svg -[2]: https://badge.fury.io/py/pip-date -[3]: https://img.shields.io/pypi/pyversions/pip-date.svg -[4]: https://pypi.python.org/pypi/pip-date +[1]: https://img.shields.io/pypi/pyversions/pip-date.svg +[2]: https://pypi.python.org/pypi/pip-date +[3]: https://badge.fury.io/py/pip-date.svg +[4]: https://badge.fury.io/py/pip-date [5]: https://img.shields.io/badge/Maintained%3F-yes-green.svg [6]: https://GitHub.com/E3V3A/pip-date/graphs/commit-activity [7]: https://img.shields.io/github/last-commit/E3V3A/pip-date.svg @@ -21,7 +21,7 @@ A simple *Python3* CLI tool to show the installation or modification times of al | STATUS: | Version | Date | Maintained? | |:------- |:------- |:---- |:----------- | -| Working | `1.0.1` | 2018-11-26 | YES | +| Working | `1.0.2` | 2018-12-02 | YES | --- @@ -81,8 +81,17 @@ and you don't really know what it is going to do. **Q:** *What else is included?* -There is also a small script called `pipbyday` that will print a simple table with: -`mTime/aTime` + `package-name` + `package-version`, sorted by time. +* A script called **`pip-describe`**, that will do what *pip* doesn't, which is to show +the full text package description from PyPI, for a given *package*. Usually the README. + +* A script called **`pipbyday`**, that will print a simple table with: + `mTime/aTime` + `package-name` + `package-version`, sorted by time. + +* A script called **`pyfileinfo`**, that will show detailed file and date information +for a given file using python's `os.stat` info. + +* A script called **`pyOSinfo`**, that will print a number of *os, system* +and *platform* variables, as seen by your Python interpreter. **Q:** *Will I continue to support this tool?* @@ -94,7 +103,12 @@ something just send me a PR, or at the very least, a detailed code snippet of wh ### Dependencies -None. (Just what you already have: [Python3](https://www.python.org/) and [pip](https://github.com/pypa/pip/).) + +* [requests](https://github.com/requests/requests) - used by `pip-describe` to get PyPI info + +and what you already have: +* [Python3](https://www.python.org/) +* [pip](https://github.com/pypa/pip/). ### Installation @@ -108,7 +122,7 @@ pip install pip-date ``` -**For manual installation:** +**For single file installation:** ```bash cd /usr/bin/ @@ -135,57 +149,6 @@ pip-date # When it's in your PATH --- -### DYI PyPI Packaging - -For your convenience, I will show you how to make a **simple** *python-only-script* pip-installable package like this one. -Just follow these steps. (Don't bother reading elsewhere, because 99.9% of Google's results are already outdated!) -To get started, you have to make sure you have already installed: `setuptools`, `wheel` and `twine`. - -Then you need to register and get credentials for 2 accounts: - -- https://pypi.org/account/register/ -- The **Live** PyPI -- https://test.pypi.org/account/register/ -- The Test PyPI - - -Then Setup and edit: - -- **`~/.pipyrc`** - for adding the Username/Password credentials to `testpypi` and `pypi` repositories -- **`__init__.py`** - for name and version (*but can be empty*) -- **`setup.cfg`** - for distribution Python2/3 compatibility -- **`setup.py`** - for all package details - - -You need to edit the following files: - -```bash -__init__.py # Can be empty -CHANGES.txt # Can be left out -LICENSE.txt -MyPyScript # Your Python Script -README.md -setup.cfg # Python2 or 3 or both? -setup.py # ALL your package details -``` - - -Finally, to create the package, test it and upload it: - -```bash -cd pip-date -python3 setup.py bdist_wheel # create a wheel distribution: pip_date-1.0.0-py3-none-any.whl -twine check dist/* # Check if your dist long-description will render correctly on PyPI -twine upload -r pypitest dist/* # Upload the project to PyPI with the "pypitest" index-server from ~/.pypirc -# check your upload at: -# https://test.pypi.org/project/pip-date/ - -# Make a test-install with: -pip install -i https://test.pypi.org/simple/ pip-date -# If all ok, then delete from test.pypi.org and re-upload to live PyPI -twine upload -r pypi dist/* -``` - ---- - ### References: **Time Stamps** @@ -202,7 +165,8 @@ that it has a Windows FS that need *ctime*, and that anything else should use *m Then we use: `os.path.getctime(pkg_loc)` to get the file time stamp. -For all the gory details, see: [here](https://linuxhandbook.com/file-timestamps/), +For all the gory details, see: +[here](https://linuxhandbook.com/file-timestamps/), [here](https://www.unixtutorial.org/atime-ctime-mtime-in-unix-filesystems/) and [here](https://en.wikipedia.org/wiki/MAC_times). @@ -237,6 +201,13 @@ For all the gory details, see: [here](https://linuxhandbook.com/file-timestamps/ projects, like *numpy*. +--- + +#### Recommeded Similar Tools: + +- **[pip-check](https://github.com/bartTC/pip-check/)** - Check you pip package update status with nice ANSI colored CLI +- **[pip-chill](https://github.com/rbanffy/pip-chill)** - Lists only the dependencies (or not) of installed packages + --- #### Bugs and Warnings @@ -246,12 +217,6 @@ None #### ToDo / Help Needed -- [ ] improve the time stamp (TS) heuristic to use more reliable files to search for -- [ ] improve the package type heuristic to show how a package was installed (*wheel* or *sdist*) -- [ ] add an `Environment` column, to show in what virtual environment a package was installed in. -- [ ] improve highlighting of special package dependencies, such as [these](https://packaging.python.org/key_projects/). - (e.g. Current implementation would for 'pip' also highlight any package with "pip" in it.) - See issues marked [ToDo](https://github.com/E3V3A/pip-date/issues?q=is%3Aopen+is%3Aissue+label%3AToDo). #### Contribution @@ -268,9 +233,10 @@ Feel free to fork, break, fix and contribute. Enjoy! #### License -[![GitHub license][21]][22] +[![GitHub license][21]][22] A license to :sparkling_heart:! +I use `GPLv3` because sharing code modifications is more beneficial for the world. [11]: https://ci.appveyor.com/api/projects/status/github/pip-date/pip-date?branch=master&svg=true [12]: https://ci.appveyor.com/project/pip-date/pip-date diff --git a/pip-date b/pip-date index 16ffb03..8ab2564 100755 --- a/pip-date +++ b/pip-date @@ -1,13 +1,16 @@ #!/usr/bin/env python3 -# pipdate - Show the install date of all pip-installed python3 packages +# pip-date - Show the install date of all pip-installed python3 packages # -*- coding: utf-8 -*- #---------------------------------------------------------------------- +# Author: E:V:A +# Date: 2018-12-01 +# Version: 1.0.2 +# License: GPLv3 +# URL: https://github.com/E3V3A/pip-date/ +#---------------------------------------------------------------------- # ToDo: # [ ] better cehck for cTime when using multiple versions: # usually the first version should be install date... -# [x] Add egg paths: -py3.6.egg-info -# [x] if you are generating a filename from this value you should combine it -# with a call to to_filename() so all dashes ("-"") are replaced by underscores ("_"). See to_filename(). # [/] fix rounding of floats in [a/c/m]Time # [ ] If using Windows FS, then use cTime, if using *nix FS, use mTime ["platform.architecture()"] # @@ -19,9 +22,9 @@ # - [ ] '-v' : THIS program version # - [ ] '-t ' : To highlight packages installed ago # -# References: -# [1] -# [2] +# References: +# [1] https://linuxhandbook.com/file-timestamps/ +# [2] https://www.unixtutorial.org/atime-ctime-mtime-in-unix-filesystems/ #---------------------------------------------------------------------- import re, os, sys, subprocess import site, pkg_resources @@ -29,16 +32,17 @@ from datetime import datetime from datetime import timedelta from time import strftime -__version__ = '1.0.1' -#version = '1.0.1' +__version__ = '1.0.2' #------------------------------------------------ # Text Coloring #------------------------------------------------ # Usage: print (yellow("This is yellow")) def color(text, color_code): - if sys.platform == "win32" and "xterm" not in os.getenv("TERM"): # [xterm, xterm-color, xterm-256color] - return text + + # Temporary fix for issue #2 + #if (sys.platform == "win32") and not ("xterm" in os.getenv("TERM")): # [xterm, xterm-color, xterm-256color] + # return text # for brighter colors, use "1;" in front of "color_code" bright = '' # '1;' return '\x1b[%s%sm%s\x1b[0m' % (bright, color_code, text) @@ -49,7 +53,7 @@ def bgreen(text): return color(text, '1;49;32'); # bright green def orange(text): return color(text, '0;49;91'); # 31 - Hard to look good! (or remains "red") def yellow(text): return color(text, 33) # def blue(text): return color(text, '1;49;34') # bright blue -def purple(text): return color(text, 35) # +def purple(text): return color(text, 35) # aka. magenta def cyan(text): return color(text, '0;49;96') # 36 def white(text): return color(text, '0;49;97'); # bright white @@ -62,7 +66,7 @@ def print_legend(): print( " {} = Bad / Non-Standard Installation Path".format(purple(cc)) ) print( " {} = Possibly Multiple installations (differing file times)".format(yellow(cc)) ) print( " {} = Recently Changed / Installed (in last 7 days)".format(cyan(cc)) ) - print( " {} = Non-PEM-compliant Version string (PEM-0440) | ~/.local install".format(green(cc)) ) + print( " {} = Non-PEM-compliant Version string (PEP-0440) | ~/.local install".format(green(cc)) ) print( " {} = A 'setuptools' dependency package".format(blue(cc)) ) #print( " {} = ERROR".format(white(cc)) ) @@ -99,7 +103,7 @@ def pre2txt(pre): return d[pre] def is_canonical(version): - # Check PEM-0440 Version string compliance: + # Check PEP-0440 Version string compliance: # https://www.python.org/dev/peps/pep-0440/ return re.match(r'^([1-9]\d*!)?(0|[1-9]\d*)(\.(0|[1-9]\d*))*((a|b|rc)(0|[1-9]\d*))?(\.post(0|[1-9]\d*))?(\.dev(0|[1-9]\d*))?$', version) is not None @@ -217,12 +221,8 @@ for d in pkg_resources.working_set : pkg_loc = test_loc(pkg_loc) pcnt += 1 - ##pkg += ["{:20} {:<20} {:<20} {:<16} {:<6} {:3}".format(pkg_name.ljust(20,' '),pkg_ctime,pkg_mtime,pkg_ver,pkg_ins,pkg_pre) ] - #pkg += ["{:20} {:<20} {:<20} {:<16} {:6} {:3} {:<5}".format(pkg_name.ljust(20,' '),pkg_ctime,pkg_mtime,pkg_ver,pkg_ins,pkg_pre,pkg_typ) ] pkg += ["{:20} {:<20} {:<20} {:<16} {:6} {:<4} {:5} {:3}".format(pkg_name.ljust(20,' '),pkg_ctime,pkg_mtime,pkg_ver,pkg_ins,pkg_pre,pkg_typ,pkg_loc) ] -##header_str = "{:20} {:20} {:20} {:16} {:6} {:3}".format('Package'.ljust(20, ' '), 'Installed (cTime)', 'Modified (mTime)', 'Version', 'Type', 'Prec') -#header_str = "{:20} {:20} {:20} {:16} {:6} {:3} {:5}".format('Package'.ljust(20, ' '), 'Installed (cTime)', 'Modified (mTime)', 'Version', 'Inst', 'Prec', 'Type') header_str = "{:20} {:20} {:20} {:16} {:6} {:4} {:5} {:3}".format('Package'.ljust(20, ' '), 'Installed (cTime)', 'Modified (mTime)', 'Version', 'Inst', 'Prec', 'Type ', 'Loc') hlen = len(header_str) @@ -230,7 +230,7 @@ print('\n' + header_str) print("-"*hlen) spkg = sorted(pkg, key=str.lower) -spkg = pkgcol(spkg) # set color to pkg_name +spkg = pkgcol(spkg) print('\n'.join(spkg)) print("-"*hlen) diff --git a/pip-describe b/pip-describe new file mode 100755 index 0000000..4004fe5 --- /dev/null +++ b/pip-describe @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# pip-describe - Show full text package description from PyPI +# -*- coding: utf-8 -*- +#---------------------------------------------------------------------- +# File Name : pip-describe +# Author : E:V:A +# Last Modified : 2018-12-01 +# Version : 1.0.0 +# License : GPLv3 +# URL : https://github.com/E3V3A/pip-date +#---------------------------------------------------------------------- +# NOTE: We don't do the summary as it is already available +# from the `pip search` results. +# [ ] But we can always add CLI switch `-s` for summary +#---------------------------------------------------------------------- +import sys +#import requests as req + +try: + import requests as req +except ModuleNotFoundError as err: + print("\nThis program need the \"requests\" package to work.") + print("Please download and install from:\nhttps://github.com/requests/requests") + print(err) + sys.exit(1) + +__version__ = '1.0.0' + +def usage() : + print("\n Usage: %s \n" % sys.argv[0]) + print(" This will return the full-text package description (usually the README)") + print(" as found on PyPI, for any given .\n") + print(" This script is part of the pip-date package at:") + print(" https://github.com/E3V3A/pip-date/\n") + sys.exit(2) + +narg = len(sys.argv) - 1 +if narg == 1: + pkg = sys.argv[1] +else: + usage() + +url = 'https://pypi.org/pypi/%s/json' % pkg +res = req.get(url) +if res.status_code == 200: + dat = res.json() + #smm = dat['info']['summary'].strip() # (short) description + des = dat['info']['description'].strip() # long_description (often full README) + print("\nPackage Description:") + print("-"*80) + print("%s" % des) + print("-"*80) +else: + print('\nERROR (%s): Package \"%s\" doesn\'t exist!' % (res.status_code,pkg)) + sys.exit(2) diff --git a/pyOSinfo b/pyOSinfo new file mode 100755 index 0000000..e53e354 --- /dev/null +++ b/pyOSinfo @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# pyOSinfo - Show what Python3 thinks about your system environment +# -*- coding: utf-8 -*- +#---------------------------------------------------------------------- +# File Name : pyOSinfo +# Author : E:V:A +# Last Modified : 2018-12-01 +# Version : 1.0.0 +# License : GPLv3 +# URL : https://github.com/E3V3A/pip-date/ +# Description : Show some system, os and platform information as seen by python3 +#---------------------------------------------------------------------- +import os, sys, platform, site +from os.path import join + +__version__ = '1.0.0' + +TRUECOLOR = "\x1b[38;2;255;100;0mTRUECOLOR\x1b[0m" + +print("\nCurrent OS variables as seen by Python3 (%s)\n" % platform.python_version() ) +print('os.getenv(\"TERM\"): %s' % os.getenv("TERM")) # [xterm, xterm-color, xterm-256color] +print('os.name: %s' % os.name ) # [posix, nt, java] +print('os.sys.platform: %s' % os.sys.platform ) # [linux, win32, cygwin, darwin] +print('sys.platform: %s' % sys.platform ) # [linux, win32, cygwin, darwin] +print('TRUECOLOR (orange): %s' % TRUECOLOR ) # TRUECOLOR written in nice orange + +print('\nos.uname:') # sysname, nodename, release, version, machine +print('\tnodename: %s' % os.uname()[1] ) # +print('\tmachine: %s' % os.uname()[4] ) # +print('\tsysname: %s' % os.uname()[0] ) # +print('\trelease: %s' % os.uname()[2] ) # +print('\tversion: %s' % os.uname()[3] ) # + +print('\nplatform:') +print('\tnode: %s' % platform.node() ) +print('\tmachine: %s' % platform.machine() ) +print('\tprocessor: %s' % platform.processor() ) +print('\tsystem: %s' % platform.system() ) +print('\trelease: %s' % platform.release() ) +print('\tversion: %s' % platform.version() ) +print('\tplatform: %s' % platform.platform() ) +print('\n\tuname (6): (%s,%s,%s,%s,%s,%s)' % platform.uname() ) # (system, node, release, version, machine, processor) +print('\tarchitecture (2): (%s,%s)' % platform.architecture() ) # (bits, linkage) +#print('\twin32_ver (4): (%s,%s,%s,%s)' % platform.win32_ver() ) # (release, version, csd, ptype) + +print("\nsite:") +print("\tgetsitepackages[0]: %s" % site.getsitepackages()[0]) +print("\tPREFIXES (2): %s" % site.PREFIXES) +print("\tUSER_SITE: %s" % site.USER_SITE) +print("\tUSER_BASE: %s" % site.USER_BASE) + +print('\nsys.path:\n\t%s\n' % ('\n\t'.join(sys.path)) ) + +sys.exit(0) diff --git a/pyfileinfo b/pyfileinfo new file mode 100755 index 0000000..93b3523 --- /dev/null +++ b/pyfileinfo @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +# pyfileinfo - Get detailed file info from "stat" of file +# -*- coding: utf-8 -*- +#---------------------------------------------------------------------- +# File Name : pyfileinfo +# Author : E:V:A +# Last Modified : 2018-12-01 +# Version : 1.1.0 +# License : GPLv3 +# URL : https://github.com/E3V3A/pip-date +# Description : Show detailed file information for a given file using python's os.stat info +# +# References: +# [1] https://linuxhandbook.com/file-timestamps/ +# [2] https://www.unixtutorial.org/atime-ctime-mtime-in-unix-filesystems/ +#---------------------------------------------------------------------- +import os, sys, stat, platform +import time, getopt + +__version__ = '1.1.0' + +#------------------------------------------------ +# Helper Functions +#------------------------------------------------ +def usage() : + print("\nUsage: %s " % sys.argv[0]) + print("\nOptions:") + print(" -c : For License/Copyright info") + print(" -h, --help : For this help ") + print(" -v, --version : For Version info") + sys.exit() + +def copyright(): + print("\nProgram License: GPLv3\nMaintenance URL: https://github.com/E3V3A/pip-date") + sys.exit() + +def pversion(): + print("\nVersion: %s" % __version__) + sys.exit() + +def print_legend(): + print("\nThe tuple items have the following meanings:") + print(" st_mode: : protection bits") + print(" st_ino : inode number") + print(" st_dev : device") + print(" st_nlink : number of hard links") + print(" st_uid : user ID of owner") + print(" st_gid : group ID of owner") + print(" st_size : file size (bytes)") + print(" st_atime : last access time (seconds since epoch)") + print(" st_mtime : last modification time") + print(" st_ctime : time of: \"creation\" for Linux / \"change\" for Windows") + print() + +#------------------------------------------------ +# CLI arguments +#------------------------------------------------ +narg = len(sys.argv) - 1 + +try: + opts, args = getopt.getopt(sys.argv[1:], ":hvc", ["help", "version"]) +except getopt.GetoptError : + usage() + sys.exit(2) + +if not opts: + if not args or narg > 1: + usage(); + sys.exit(); + elif narg == 1: + filename = args[0] +else: + for opt, arg in opts: + if opt in ("-h", "--help"): usage(); + elif opt in ("-v", "--version"): pversion(); + elif opt == "-c": copyright(); + +#------------------------------------------------ +# MAIN +#------------------------------------------------ +if platform.architecture()[1] == "WindowsPE": + isWinFS = True +else: + isWinFS = False + +try: + fhand = open(filename) +except IsADirectoryError as e: + print ("ERROR: %s" % e) + sys.exit(2) + +count = 0 +for lines in fhand: + count = count + 1 + +fdata = open(filename).read() +t_char = len(fdata) + +try: + file_stats = os.stat(filename) + print ("\nThe os.stat() tuple:\n") + print (file_stats) + print_legend() + +except OSError: + print ("\nNameError : [%s] No such file or directory\n", filename) + sys.exit(2) + +file_info = { + 'fname': filename, + 'fsize': file_stats[stat.ST_SIZE], + 'no_of_lines':count, + 't_char':t_char, + 'f_lm' : time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(file_stats[stat.ST_MTIME])), + 'f_la' : time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(file_stats[stat.ST_ATIME])), + 'f_ct' : time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(file_stats[stat.ST_CTIME])), +} + +print (" File Name : ", file_info['fname']) +print (" File Type : ", "directory" if stat.S_ISDIR(file_stats[stat.ST_MODE]) else "\"normal\" file") +print (" File Size : ", file_info['fsize'] , " (bytes)") +print (" Total Lines : ", file_info['no_of_lines']) +print (" Total Chars : ", file_info['t_char']) +print() + +if isWinFS: + # powershell.exe -Command "Get-Item CHANGES.txt | Format-List" + # Poweshell: .CreationTime = .LastAccessTime + # Poweshell: .LastWriteTime + print("Using WindowsPE") + print (" ctime: OS change time : ", file_info['f_ct'], " (PS: n/a)") + print (" mtime: user modified : ", file_info['f_lm'], " (PS: .LastWriteTime)") + print (" atime: creation time : ", file_info['f_la'], " (PS: .CreationTime = .LastAccessTime)") +else: + print("Using a Linux based OS?") + print (" ctime: creation time : ", file_info['f_ct']) + print (" mtime: last modified : ", file_info['f_lm']) + print (" atime: last accessed : ", file_info['f_la']) + +sys.exit(0) diff --git a/setup.py b/setup.py index 48aef79..4704d36 100644 --- a/setup.py +++ b/setup.py @@ -18,20 +18,22 @@ def readme(): setup( name = 'pip-date', - version = '1.0.1', + version = '1.0.2', author = 'E:V:A', author_email = 'xdae3v3a@gmail.com', - description = 'Show the installation/modification times of all your pip packages', - long_description = 'A simple Python3 CLI tool to show the installation or modification times of all your pip packages.', + description = 'Show the installation/modification times of all your pip packages and other tools', + long_description = 'A light CLI tool-set to show the installation or modification times of all your pip packages.', long_description_content_type='text/plain', #long_description = readme(), #long_description_content_type = 'text/markdown', license='LICENSE.txt', url = 'https://github.com/e3v3a/pip-date/', packages = find_packages(), - #scripts=['pip-date/pip-date', 'pip-date/pipbyday'], - scripts=['pip-date', 'pipbyday'], - keywords = 'pip date package setuptools wheel egg', + scripts=['pip-date', 'pip-describe', 'pipbyday', 'pyfileinfo', 'pyOSinfo'], + keywords = 'pip date package management setuptools wheel egg stat os', + install_requires=[ + 'requests', + ], python_requires = '>=3', classifiers=[ #'Private :: Do Not Upload',