├── .gitignore ├── LICENSE ├── README.md ├── _version.py ├── command.py ├── how.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | .idea 12 | .vscode 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | .static_storage/ 58 | .media/ 59 | local_settings.py 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Environments 87 | .env 88 | .venv 89 | env/ 90 | venv/ 91 | ENV/ 92 | env.bak/ 93 | venv.bak/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019~now chenjiandongx 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Linux logo 3 |

4 | 5 |

📝 how

6 |

7 | Impressive Linux commands cheat sheet. 8 |

9 | 10 |

11 | 12 | PyPI - Python Version 13 | 14 | 15 | PyPI - Python Version 16 | 17 | 18 | MIT License 19 | 20 |

21 | 22 | ### 💡 IDEA 23 | 24 | Linux 是每位开发者必备的技能,如何高效地掌握 Linux 命令就成为一件很重要的事了。[jaywcjlove/linux-command](https://github.com/jaywcjlove/linux-command) 项目收集和整理了 500+ 的 Linux 命令使用文档,不过缺少了一个命令行版本,`how` 决定来填补这个空缺。 25 | 26 | * Golang 版本: [chenjiandongx/pls](https://github.com/chenjiandongx/pls) 27 | 28 | ### 🔰 安装 29 | 30 | **pip 安装** 31 | ```bash 32 | $ pip install how 33 | ``` 34 | 35 | **源码安装** 36 | ```bash 37 | $ git clone https://github.com/chenjiandongx/how.git 38 | $ cd how 39 | $ pip install -r requirements.txt 40 | $ python setup.py install 41 | ``` 42 | 43 | ### 📏 使用 44 | 45 | ```bash 46 | $ how 47 | usage: how [-h] [-i] [-v] [COMMAND [COMMAND ...]] 48 | 49 | Lovely Linux commands cheat sheet. 50 | 51 | positional arguments: 52 | COMMAND the puzzling command 53 | 54 | optional arguments: 55 | -h, --help show this help message and exit 56 | -i, --init initialize all commands 57 | -v, --version displays the current version of `how` 58 | ``` 59 | 60 | > Note: 建议第一次使用 `how` 时先初始化所有的命令文档,`how -i`,该命令会将 https://github.com/jaywcjlove/linux-command 的 .md 文档下载到 `~/.command` 本地路径下。不过这个操作不是必须的,因为如果 `how some-command` 在本地路径中查询不到的话,会尝试先向远程地址下载。 61 | 62 | ### 🔖 示例 63 | 64 | 初始化所有文档,同时也是更新所有文档的命令 65 | ```shell 66 | $ how -i 67 | Initializing commands: 96/562 68 | ``` 69 | 70 | 查询如何使用 `man` 命令 71 | ```shell 72 | $ how man 73 | # man 74 | 75 | 查看 Linux 中的指令帮助 76 | 77 | ## 补充说明 78 | 79 | man 命令 是 Linux 下的帮助指令,通过 man 指令可以查看 80 | Linux 中的指令帮助、配置文件帮助和编程帮助等信息。 81 | 82 | ### 语法 83 | 84 | man(选项)(参数) 85 | 86 | ### 选项 87 | 88 | -a:在所有的 man 帮助手册中搜索; 89 | -f:等价于 whatis 指令,显示给定关键字的简短描述信息; 90 | -P:指定内容时使用分页程序; 91 | -M:指定 man 手册搜索的路径。 92 | 93 | ### 参数 94 | 95 | - 数字:指定从哪本 man 手册中搜索帮助; 96 | - 关键字:指定要搜索帮助的关键字。 97 | 98 | ### 数字代表内容 99 | 100 | 1:用户在 shell 环境可操作的命令或执行文件; 101 | 2:系统内核可调用的函数与工具等 102 | 3:一些常用的函数(function) 与函数库(library),大部分为 C 103 | 的函数库(libc) 104 | 4:设备文件说明,通常在/dev 下的文件 105 | 5:配置文件或某些文件格式 106 | 6:游戏(games) 107 | 7:惯例与协议等,如 Linux 文件系统,网络协议,ASCII code 108 | 等说明 109 | 8:系统管理员可用的管理命令 110 | 9:跟 kernel 有关的文件 111 | 112 | ### 实例 113 | 114 | 我们输入 man ls,它会在最左上角显示“LS(1)”,在这里,“LS”表示手 115 | 册名称,而“(1)”表示该手册位于第一节章,同样,我们输 man 116 | ifconfig 117 | 它会在最左上角显示“IFCONFIG(8)”。也可以这样输入命令:“man 118 | [章节号] 手册名称”。 119 | 120 | man 是按照手册的章节号的顺序进行搜索的,比如: 121 | 122 | man sleep 123 | 124 | 只会显示 sleep 命令的手册,如果想查看库函数 sleep,就要输入: 125 | 126 | man 3 sleep 127 | ``` 128 | 129 | ### 📅 Changelog 130 | 131 | #### V0.1.1 - 2020-05-11 132 | 133 | * Updated: 更新命令列表 134 | 135 | #### V0.1.0 - 2019-07-28 136 | 137 | * Alpha: 第一个正式版发布 138 | 139 | ### 📃 LICENSE 140 | 141 | MIT [©chenjiandongx](https://github.com/chenjiandongx) 142 | -------------------------------------------------------------------------------- /_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.1" 2 | __author__ = "chenjiandongx" 3 | -------------------------------------------------------------------------------- /command.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | COMMAND = [ 4 | "ab", 5 | "accept", 6 | "ack", 7 | "alias", 8 | "apachectl", 9 | "apk", 10 | "apropos", 11 | "apt-get", 12 | "apt-key", 13 | "apt-sortpkgs", 14 | "aptitude", 15 | "ar", 16 | "arch", 17 | "arj", 18 | "arp", 19 | "arpd", 20 | "arping", 21 | "arptables", 22 | "arpwatch", 23 | "as", 24 | "at", 25 | "atop", 26 | "atq", 27 | "atrm", 28 | "awk", 29 | "axel", 30 | "badblocks", 31 | "basename", 32 | "batch", 33 | "bc", 34 | "bg", 35 | "bind", 36 | "blkid", 37 | "blockdev", 38 | "bmodinfo", 39 | "break", 40 | "builtin", 41 | "bunzip2", 42 | "bye", 43 | "bzcat", 44 | "bzcmp", 45 | "bzdiff", 46 | "bzgrep", 47 | "bzip2", 48 | "bzip2recover", 49 | "bzless", 50 | "bzmore", 51 | "cal", 52 | "cancel", 53 | "cat", 54 | "cd", 55 | "cdrecord", 56 | "chage", 57 | "chattr", 58 | "chcon", 59 | "chfn", 60 | "chgrp", 61 | "chkconfig", 62 | "chmod", 63 | "chown", 64 | "chpasswd", 65 | "chroot", 66 | "chsh", 67 | "cksum", 68 | "clear", 69 | "clock", 70 | "clockdiff", 71 | "cmp", 72 | "col", 73 | "colrm", 74 | "comm", 75 | "command", 76 | "compress", 77 | "consoletype", 78 | "continue", 79 | "convertquota", 80 | "cp", 81 | "cpio", 82 | "crontab", 83 | "csplit", 84 | "cu", 85 | "cupsdisable", 86 | "cupsenable", 87 | "curl", 88 | "cut", 89 | "date", 90 | "dd", 91 | "declare", 92 | "depmod", 93 | "df", 94 | "dhclient", 95 | "dhcpd", 96 | "dhcrelay", 97 | "diff", 98 | "diff3", 99 | "diffstat", 100 | "dig", 101 | "dircolors", 102 | "dirname", 103 | "dirs", 104 | "disown", 105 | "dmesg", 106 | "dmidecode", 107 | "dnf", 108 | "dnsdomainname", 109 | "domainname", 110 | "dos2unix", 111 | "dpkg-deb", 112 | "dpkg-divert", 113 | "dpkg-preconfigure", 114 | "dpkg-query", 115 | "dpkg-reconfigure", 116 | "dpkg-split", 117 | "dpkg-statoverride", 118 | "dpkg-trigger", 119 | "dpkg", 120 | "dris", 121 | "dstat", 122 | "du", 123 | "dump", 124 | "e2fsck", 125 | "e2label", 126 | "echo", 127 | "ed", 128 | "edquota", 129 | "egrep", 130 | "eject", 131 | "elinks", 132 | "elm", 133 | "emacs", 134 | "enable", 135 | "env", 136 | "ethtool", 137 | "ex", 138 | "exec", 139 | "exit", 140 | "expand", 141 | "export", 142 | "exportfs", 143 | "expr", 144 | "false", 145 | "fc", 146 | "fdisk", 147 | "fg", 148 | "fgrep", 149 | "file", 150 | "find", 151 | "findfs", 152 | "finger", 153 | "firewall-cmd", 154 | "fishshell", 155 | "fmt", 156 | "fold", 157 | "fping", 158 | "free", 159 | "fsck", 160 | "ftp", 161 | "ftpcount", 162 | "ftpshut", 163 | "ftptop", 164 | "ftpwho", 165 | "fuser", 166 | "gcc", 167 | "gcov", 168 | "gdb", 169 | "get_module", 170 | "getenforce", 171 | "getsebool", 172 | "git", 173 | "gpasswd", 174 | "gpm", 175 | "grep", 176 | "groupadd", 177 | "groupdel", 178 | "groupmod", 179 | "groups", 180 | "grpck", 181 | "grpconv", 182 | "grpunconv", 183 | "grub", 184 | "gunzip", 185 | "gzexe", 186 | "gzip", 187 | "halt", 188 | "hdparm", 189 | "head", 190 | "help", 191 | "hexdump", 192 | "history", 193 | "host", 194 | "hostid", 195 | "hostname", 196 | "hping3", 197 | "htdigest", 198 | "htop", 199 | "htpasswd", 200 | "hwclock", 201 | "iconv", 202 | "id", 203 | "ifcfg", 204 | "ifconfig", 205 | "ifdown", 206 | "ifstat", 207 | "iftop", 208 | "ifup", 209 | "indent", 210 | "info", 211 | "init", 212 | "inotifywait", 213 | "insmod", 214 | "install", 215 | "iostat", 216 | "iotop", 217 | "ip", 218 | "ip6tables-restore", 219 | "ip6tables-save", 220 | "ip6tables", 221 | "ipcalc", 222 | "ipcrm", 223 | "ipcs", 224 | "iperf", 225 | "iptables-restore", 226 | "iptables-save", 227 | "iptables", 228 | "iptraf", 229 | "iptstate", 230 | "ispell", 231 | "jed", 232 | "jobs", 233 | "joe", 234 | "join", 235 | "jwhois", 236 | "kernelversion", 237 | "kexec", 238 | "kill", 239 | "killall", 240 | "last", 241 | "lastb", 242 | "lastlog", 243 | "ld", 244 | "ldconfig", 245 | "ldd", 246 | "less", 247 | "let", 248 | "lftp", 249 | "lftpget", 250 | "lha", 251 | "lilo", 252 | "ln", 253 | "lnstat", 254 | "local", 255 | "locate", 256 | "logger", 257 | "login", 258 | "logname", 259 | "logout", 260 | "logrotate", 261 | "logsave", 262 | "logwatch", 263 | "look", 264 | "losetup", 265 | "lp", 266 | "lpadmin", 267 | "lpc", 268 | "lpq", 269 | "lpr", 270 | "lprm", 271 | "lpstat", 272 | "ls", 273 | "lsattr", 274 | "lsb_release", 275 | "lsblk", 276 | "lscpu", 277 | "lsmod", 278 | "lsof", 279 | "lspci", 280 | "lsusb", 281 | "ltrace", 282 | "lvcreate", 283 | "lvdisplay", 284 | "lvextend", 285 | "lvreduce", 286 | "lvremove", 287 | "lvresize", 288 | "lvscan", 289 | "lynx", 290 | "mail", 291 | "mailq", 292 | "mailstat", 293 | "make", 294 | "man", 295 | "mapfile", 296 | "md5sum", 297 | "mesg", 298 | "mii-tool", 299 | "mkbootdisk", 300 | "mkdir", 301 | "mke2fs", 302 | "mkfs", 303 | "mkinitrd", 304 | "mkisofs", 305 | "mknod", 306 | "mkswap", 307 | "mktemp", 308 | "modprobe", 309 | "more", 310 | "mount", 311 | "mpstat", 312 | "mtools", 313 | "mv", 314 | "mysql", 315 | "mysqladmin", 316 | "mysqldump", 317 | "mysqlimport", 318 | "mysqlshow", 319 | "named-checkzone", 320 | "nano", 321 | "nc", 322 | "ncftp", 323 | "nethogs", 324 | "netstat", 325 | "newusers", 326 | "nfsstat", 327 | "ngrep", 328 | "nice", 329 | "nisdomainname", 330 | "nl", 331 | "nm", 332 | "nmap", 333 | "nmcli", 334 | "nohup", 335 | "nologin", 336 | "nslookup", 337 | "ntpdate", 338 | "ntsysv", 339 | "objdump", 340 | "od", 341 | "openssl", 342 | "parted", 343 | "partprobe", 344 | "passwd", 345 | "paste", 346 | "patch", 347 | "pathchk", 348 | "perl", 349 | "pfctl", 350 | "pgrep", 351 | "php", 352 | "pico", 353 | "pidof", 354 | "pigz", 355 | "ping", 356 | "pkill", 357 | "pmap", 358 | "popd", 359 | "poweroff", 360 | "ppp-off", 361 | "pr", 362 | "printf", 363 | "protoize", 364 | "ps", 365 | "pssh", 366 | "pstack", 367 | "pstree", 368 | "pushd", 369 | "pv", 370 | "pvchange", 371 | "pvck", 372 | "pvcreate", 373 | "pvdisplay", 374 | "pvremove", 375 | "pvs", 376 | "pvscan", 377 | "pwck", 378 | "pwconv", 379 | "pwd", 380 | "pwunconv", 381 | "quota", 382 | "quotacheck", 383 | "quotaoff", 384 | "quotaon", 385 | "rcconf", 386 | "rcp", 387 | "read", 388 | "readelf", 389 | "readonly", 390 | "reboot", 391 | "reject", 392 | "rename", 393 | "renice", 394 | "repquota", 395 | "resize", 396 | "restore", 397 | "restorecon", 398 | "return", 399 | "rev", 400 | "rexec", 401 | "rlogin", 402 | "rm", 403 | "rmdir", 404 | "rmmod", 405 | "route", 406 | "rpm", 407 | "rpm2cpio", 408 | "rpmbuild", 409 | "rpmdb", 410 | "rpmquery", 411 | "rpmsign", 412 | "rpmverify", 413 | "rsh", 414 | "rsync", 415 | "runlevel", 416 | "sar", 417 | "scp", 418 | "screen", 419 | "sed", 420 | "seinfo", 421 | "semanage", 422 | "sendmail", 423 | "seq", 424 | "service", 425 | "sesearch", 426 | "set", 427 | "setfacl", 428 | "setpci", 429 | "setsebool", 430 | "setsid", 431 | "sftp-server", 432 | "sftp", 433 | "sh", 434 | "shift", 435 | "shopt", 436 | "showmount", 437 | "shuf", 438 | "shutdown", 439 | "skill", 440 | "slabtop", 441 | "sleep", 442 | "slocate", 443 | "smbclient", 444 | "smbpasswd", 445 | "sort", 446 | "source", 447 | "speedtest-cli", 448 | "spell", 449 | "split", 450 | "squid", 451 | "squidclient", 452 | "ss", 453 | "ssh-add", 454 | "ssh-agent", 455 | "ssh-copy-id", 456 | "ssh-keygen", 457 | "ssh-keyscan", 458 | "ssh", 459 | "sshd", 460 | "startx", 461 | "stat", 462 | "strace", 463 | "strings", 464 | "stty", 465 | "su", 466 | "sudo", 467 | "sum", 468 | "supervisord", 469 | "suspend", 470 | "swapoff", 471 | "swapon", 472 | "sync", 473 | "sysctl", 474 | "syslog", 475 | "systemctl", 476 | "systool", 477 | "tac", 478 | "tail", 479 | "tailf", 480 | "talk", 481 | "tar", 482 | "tcpdump", 483 | "tcpreplay", 484 | "tee", 485 | "telint", 486 | "telnet", 487 | "tempfile", 488 | "test", 489 | "tftp", 490 | "time", 491 | "times", 492 | "tload", 493 | "tmux", 494 | "top", 495 | "touch", 496 | "tput", 497 | "tr", 498 | "tracepath", 499 | "traceroute", 500 | "trap", 501 | "tree", 502 | "true", 503 | "tty", 504 | "type", 505 | "ulimit", 506 | "umask", 507 | "umount", 508 | "unalias", 509 | "uname", 510 | "unarj", 511 | "uncompress", 512 | "unexpand", 513 | "uniq", 514 | "unlink", 515 | "unprotoize", 516 | "unrar", 517 | "unset", 518 | "unzip", 519 | "updatedb", 520 | "uptime", 521 | "useradd", 522 | "userdel", 523 | "usermod", 524 | "usernetctl", 525 | "users", 526 | "uucico", 527 | "uupick", 528 | "uuto", 529 | "vdfuse", 530 | "vgchange", 531 | "vgconvert", 532 | "vgcreate", 533 | "vgdisplay", 534 | "vgextend", 535 | "vgreduce", 536 | "vgremove", 537 | "vgrename", 538 | "vgscan", 539 | "vi", 540 | "vmstat", 541 | "volname", 542 | "w", 543 | "wait", 544 | "wall", 545 | "watch", 546 | "wc", 547 | "wget", 548 | "whatis", 549 | "whereis", 550 | "which", 551 | "who", 552 | "whoami", 553 | "write", 554 | "xargs", 555 | "xauth", 556 | "xclip", 557 | "xhost", 558 | "xinit", 559 | "xlsatoms", 560 | "xlsclients", 561 | "xlsfonts", 562 | "xset", 563 | "xz", 564 | "yes", 565 | "ypdomainname", 566 | "yum", 567 | "zcat", 568 | "zfore", 569 | "zip", 570 | "zipinfo", 571 | "zipsplit", 572 | "znew", 573 | ] 574 | 575 | 576 | def _fetch_commands(): 577 | url = "https://unpkg.com/linux-command/dist/data.json" 578 | commands = requests.get(url).json() 579 | for cmd in commands: 580 | print(cmd) 581 | -------------------------------------------------------------------------------- /how.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import re 4 | import textwrap 5 | from multiprocessing.dummy import Pool 6 | 7 | import commonmark 8 | import huepy 9 | import requests 10 | from tenacity import retry, stop_after_attempt 11 | 12 | from _version import __version__ 13 | from command import COMMAND 14 | 15 | TAG_HEADING = "heading" 16 | TAG_ITEM = "item" 17 | TAG_CODE_BLOCK = "code_block" 18 | TAG_LIST = "list" 19 | TAG_PARAGRAPH = "paragraph" 20 | TAG_HTML_BLOCK = "html_block" 21 | TAG_STRONG = "strong" 22 | TAG_TEXT = "text" 23 | TAG_BLOCK_QUOTE = "block_quote" 24 | 25 | EXT = ".md" 26 | MAX_WIDTH = 40 27 | MAX_CONCURRENCY = 8 28 | 29 | COMMAND_DIR = os.path.join(os.path.expanduser("~"), ".command") 30 | FILE_URL = "https://unpkg.com/linux-command/command/{}.md" 31 | 32 | 33 | class Tag: 34 | def __init__(self, t, literal, level): 35 | self.t = t 36 | self.literal = literal 37 | self.level = level 38 | 39 | def __repr__(self): 40 | return "[ ]".format( 41 | self.t, self.literal, self.level 42 | ) 43 | 44 | 45 | def docs_need_space(docs): 46 | left = re.compile(r"([a-zA-Z0-9)])([\u4e00-\u9fa5])") 47 | right = re.compile(r"([\u4e00-\u9fa5])([a-zA-Z0-9\[])") 48 | return re.sub(right, r"\1 \2", re.sub(left, r"\1 \2", docs)) 49 | 50 | 51 | def wrap_text(text): 52 | return textwrap.fill(text=text, width=MAX_WIDTH) + "\n" 53 | 54 | 55 | def highlight(text: str, keyword: str): 56 | return re.sub( 57 | keyword, 58 | "\33[0m" + "\33[93m" + keyword + "\33[0m" + "\33[37m", 59 | text, 60 | flags=re.IGNORECASE, 61 | ) 62 | 63 | 64 | def get_content(command): 65 | file_path = os.path.join(COMMAND_DIR, command + EXT) 66 | if not os.path.exists(file_path): 67 | download_file(command) 68 | if not os.path.exists(file_path): 69 | return "" 70 | with open(file_path, "r", encoding="utf8") as f: 71 | return f.read() 72 | 73 | 74 | def how_to_use(command): 75 | content = get_content(command) 76 | if not content: 77 | print("Sorry: could not find the `{}` command".format(command)) 78 | return 79 | 80 | parse = commonmark.Parser() 81 | ast = parse.parse(content) 82 | 83 | tags = [] 84 | for obj, entering in ast.walker(): 85 | if not entering or obj.t == TAG_HTML_BLOCK: 86 | continue 87 | tags.append(Tag(obj.t, obj.literal, obj.level)) 88 | 89 | tag_length, out = len(tags), "" 90 | for i, tag in enumerate(tags): 91 | if i < tag_length - 1: 92 | if tag.t == TAG_HEADING: 93 | tag.literal = huepy.bold("#" * tag.level + " ") 94 | if tag.t == TAG_TEXT: 95 | if tags[i + 1].t in (TAG_PARAGRAPH, TAG_HEADING, TAG_CODE_BLOCK): 96 | tag.literal = tag.literal + "\n" * 2 97 | if tags[i + 1].t == TAG_ITEM: 98 | tag.literal = tag.literal + "\n" + "- " 99 | if tags[i + 1].t == TAG_LIST: 100 | tag.literal = tag.literal + "\n" * 2 + "- " 101 | if tags[i + 1].t == TAG_BLOCK_QUOTE: 102 | tag.literal = tag.literal + "\n" * 2 + "> " 103 | if tag.t == TAG_CODE_BLOCK: 104 | tag.literal = tag.literal + "\n" 105 | 106 | if tag.literal: 107 | out += tag.literal 108 | doc = [wrap_text(d) for d in docs_need_space(out).strip().split("\n")] 109 | print(highlight("".join(doc), command)) 110 | 111 | 112 | @retry(stop=stop_after_attempt(3)) 113 | def download_file(command): 114 | if not os.path.exists(COMMAND_DIR): 115 | os.makedirs(COMMAND_DIR) 116 | 117 | url = FILE_URL.format(command) 118 | response = requests.get(url) 119 | if response.status_code >= 400: 120 | return 121 | 122 | file_path = os.path.join(COMMAND_DIR, command + EXT) 123 | with open(file_path, "w+", encoding="utf8") as f: 124 | f.write(response.text) 125 | 126 | 127 | cnt = 0 128 | 129 | 130 | def download_file_progress(command): 131 | download_file(command) 132 | global cnt 133 | cnt += 1 134 | print("\rInitializing commands: {}/{} ".format(cnt, len(COMMAND)), end="") 135 | 136 | 137 | def init_command(): 138 | # speed up 139 | p = Pool(MAX_CONCURRENCY) 140 | p.map(download_file_progress, COMMAND) 141 | p.close() 142 | p.join() 143 | print() 144 | 145 | 146 | def get_parser(): 147 | parser = argparse.ArgumentParser( 148 | description="Impressive Linux commands cheat sheet." 149 | ) 150 | parser.add_argument( 151 | "command", metavar="COMMAND", type=str, nargs="*", help="the puzzling command" 152 | ) 153 | parser.add_argument( 154 | "-i", "--init", action="store_true", help="initialize all commands" 155 | ) 156 | parser.add_argument( 157 | "-v", 158 | "--version", 159 | action="store_true", 160 | help="displays the current version of `how`", 161 | ) 162 | return parser 163 | 164 | 165 | def command_line_runner(): 166 | parser = get_parser() 167 | args = vars(parser.parse_args()) 168 | 169 | command = "".join(args["command"]).lower() 170 | 171 | if args["version"]: 172 | print(huepy.info("how " + __version__)) 173 | return 174 | 175 | if args["init"]: 176 | init_command() 177 | return 178 | 179 | if not args["command"]: 180 | parser.print_help() 181 | return 182 | 183 | how_to_use(command) 184 | 185 | 186 | if __name__ == "__main__": 187 | command_line_runner() 188 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | commonmark 2 | huepy 3 | requests 4 | tenacity 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from shutil import rmtree 4 | 5 | from setuptools import Command, find_packages, setup 6 | 7 | # $ python setup.py upload 8 | 9 | __title__ = "how" 10 | __description__ = "Impressive Linux commands cheat sheet." 11 | __url__ = "https://github.com/chenjiandongx/how" 12 | __author_email__ = "chenjiandongx@qq.com" 13 | __license__ = "MIT" 14 | 15 | __requires__ = ["commonmark", "huepy", "requests", "tenacity"] 16 | __keywords__ = ["python", "linux-command", "cheat sheet"] 17 | __modules__ = ["how", "command", "_version"] 18 | 19 | # Load the package's _version.py module as a dictionary. 20 | here = os.path.abspath(os.path.dirname(__file__)) 21 | about = {} 22 | with open(os.path.join(here, "_version.py")) as f: 23 | exec(f.read(), about) 24 | 25 | 26 | __version__ = about["__version__"] 27 | 28 | 29 | class UploadCommand(Command): 30 | description = "Build and publish the package." 31 | user_options = [] 32 | 33 | @staticmethod 34 | def status(s): 35 | print("✨✨ {0}".format(s)) 36 | 37 | def initialize_options(self): 38 | pass 39 | 40 | def finalize_options(self): 41 | pass 42 | 43 | def run(self): 44 | try: 45 | self.status("Removing previous builds…") 46 | rmtree(os.path.join(here, "dist")) 47 | rmtree(os.path.join(here, "build")) 48 | rmtree(os.path.join(here, "{0}.egg-info".format(__title__))) 49 | except OSError: 50 | pass 51 | 52 | self.status("Building Source and Wheel distribution…") 53 | os.system("{0} setup.py bdist_wheel".format(sys.executable)) 54 | 55 | self.status("Uploading the package to PyPI via Twine…") 56 | os.system("twine upload dist/*") 57 | 58 | self.status("Pushing git tags…") 59 | os.system('git tag -a v{0} -m "release version v{0}"'.format(__version__)) 60 | os.system("git push origin v{0}".format(__version__)) 61 | 62 | sys.exit() 63 | 64 | 65 | setup( 66 | name=__title__, 67 | version=__version__, 68 | description=__description__, 69 | url=__url__, 70 | author=about["__author__"], 71 | author_email=__author_email__, 72 | license=__license__, 73 | packages=find_packages(exclude=("test",)), 74 | keywords=__keywords__, 75 | py_modules=__modules__, 76 | install_requires=__requires__, 77 | zip_safe=False, 78 | include_package_data=True, 79 | classifiers=[ 80 | "Development Status :: 4 - Beta", 81 | "Environment :: Console", 82 | "Intended Audience :: Developers", 83 | "License :: OSI Approved :: MIT License", 84 | "Operating System :: OS Independent", 85 | "Programming Language :: Python", 86 | "Programming Language :: Python :: 3.6", 87 | "Programming Language :: Python :: 3.7", 88 | "Topic :: Software Development :: Libraries", 89 | ], 90 | cmdclass={"upload": UploadCommand}, 91 | entry_points={"console_scripts": ["how=how:command_line_runner"]}, 92 | ) 93 | --------------------------------------------------------------------------------