├── fu_core ├── test │ ├── __init__.py │ ├── info.txt │ ├── Makefile │ ├── api_test.py │ ├── color_test.py │ ├── platform_test.py │ ├── sample.json │ └── dotfile_test.py ├── __init__.py ├── platform_utils.py ├── terminalColor.py ├── config.py ├── cache.py └── api.py ├── setup.py ├── LICENSE.txt ├── .gitignore ├── README.md ├── fu └── argparse.py /fu_core/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fu_core/__init__.py: -------------------------------------------------------------------------------- 1 | """fu: CommandLineFu CLI """ 2 | __version__ = "0.0.9" 3 | import terminalColor 4 | import api 5 | import platform_utils 6 | import config 7 | -------------------------------------------------------------------------------- /fu_core/test/info.txt: -------------------------------------------------------------------------------- 1 | To ensure everything is working, you can run the following test 2 | 3 | $ make color platform dotfile api 4 | 5 | IF successful, each test will run and print the output 6 | -------------------------------------------------------------------------------- /fu_core/test/Makefile: -------------------------------------------------------------------------------- 1 | color: 2 | python color_test.py 3 | 4 | api: 5 | python api_test.py stream youtube 6 | 7 | platform: 8 | python platform_test.py "words i want on the clipboard" 9 | 10 | dotfile: 11 | python dotfile_test.py 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /fu_core/test/api_test.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Test Script for commandlinefu api module 4 | """ 5 | 6 | import os 7 | import sys 8 | 9 | # Add to sys.path and then import 10 | sys.path.insert(0, os.path.abspath("..")) 11 | from api import API 12 | 13 | print "\nAPI test ------------------------\n" 14 | 15 | api = API( sys.argv[1:] ) 16 | api.load() 17 | api.display( True, 3 ,True) 18 | 19 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | 3 | from setuptools import setup 4 | 5 | setup( 6 | name="fu", 7 | version="0.1.1", 8 | description="Commandline Interface for commandlinefu.com", 9 | author="Samir Ahmed", 10 | author_email="samir@samir-ahmed.com", 11 | url="http://github.com/samirahmed/fu", 12 | packages=['fu_core'], 13 | scripts=['fu'], 14 | ) 15 | -------------------------------------------------------------------------------- /fu_core/test/color_test.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Test Script for terminal Color module 4 | """ 5 | 6 | import os 7 | import sys 8 | 9 | # Add to sys.path and then import 10 | sys.path.insert(0, os.path.abspath("..")) 11 | from terminalColor import color 12 | 13 | print "\nColor Test --------------------\n" 14 | # Test all will call all functions 15 | # expect to get a print ouf of different colors 16 | color.testAll('Terminal Color Test') 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /fu_core/test/platform_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | # Add to sys.path and then import 5 | sys.path.insert(0, os.path.abspath("..")) 6 | from platform_utils import system 7 | 8 | print "\nPlatform Utility Test --------------\n" 9 | if len(sys.argv) > 1: 10 | my_system = system() 11 | if my_system.copy( sys.argv[1] ): 12 | print "Copied to clipboard : " + sys.argv[1] 13 | else : 14 | print "Oops! Unable too copy!" 15 | print "Looks like you have a " + my_system.name + ". Please ensure you have " + my_system.copy_command 16 | my_system.open("http://www.commandlinefu.com") 17 | else : 18 | print "usage: copy.py " 19 | 20 | 21 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Samir Ahmed 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ################### 3 | # PYTHON SPECIFIC # 4 | ################### 5 | *.mo 6 | *.egg-info 7 | *.egg 8 | *.EGG 9 | *.EGG-INFO 10 | bin 11 | build 12 | develop-eggs 13 | downloads 14 | eggs 15 | fake-eggs 16 | parts 17 | dist 18 | .installed.cfg 19 | .mr.developer.cfg 20 | .hg 21 | .bzr 22 | .svn 23 | *.pyc 24 | *.pyo 25 | *.tmp* 26 | 27 | 28 | # Installer logs 29 | pip-log.txt 30 | 31 | # Unit test / coverage reports 32 | .coverage 33 | .tox 34 | 35 | #Translations 36 | *.mo 37 | 38 | #Mr Developer 39 | .mr.developer.cfg 40 | 41 | #################### 42 | # GITHUB SUGGESTED # 43 | #################### 44 | 45 | # Compiled source # 46 | ################### 47 | *.com 48 | *.class 49 | *.dll 50 | *.exe 51 | *.o 52 | *.so 53 | 54 | # Packages # 55 | ############ 56 | # it's better to unpack these files and commit the raw source 57 | # git has its own built in compression methods 58 | *.7z 59 | *.dmg 60 | *.gz 61 | *.iso 62 | *.jar 63 | *.rar 64 | *.tar 65 | *.zip 66 | 67 | # Logs and databases # 68 | ###################### 69 | *.log 70 | *.sql 71 | *.sqlite 72 | 73 | # OS generated files # 74 | ###################### 75 | .DS_Store* 76 | ehthumbs.db 77 | Icon? 78 | Thumbs.db 79 | 80 | # Notes and planning files # 81 | ############################ 82 | fu.md 83 | installRecords.txt 84 | 85 | # Package files # 86 | src/fu.zip 87 | fu 88 | -------------------------------------------------------------------------------- /fu_core/platform_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Platform Interface utility 3 | 4 | """ 5 | import webbrowser 6 | import subprocess 7 | import commands 8 | import platform 9 | import shlex 10 | from terminalColor import color 11 | 12 | class system: 13 | 14 | def __init__(self): 15 | 16 | info = set( [ ss.lower() for ss in platform.uname() ]) 17 | 18 | # determine the type of platform 19 | 20 | if 'cygwin' in info: 21 | self.name = 'windows' 22 | self.copy_command = 'clip' 23 | elif 'darwin' in info: 24 | self.name = 'mac' 25 | self.copy_command = 'pbcopy' 26 | else : 27 | self.name = 'linux' 28 | self.copy_command = 'xclip -selection clipboard' 29 | 30 | """Copy given string into system clipboard.""" 31 | def copy(self,string): 32 | 33 | # Assuming it works, we try and execute the function 34 | worked = True 35 | try: 36 | subprocess.Popen(shlex.split(self.copy_command), stdin=subprocess.PIPE).communicate(str(unicode(string))) 37 | except Exception, why: 38 | 39 | # If it doesn't work return flase 40 | worked = False 41 | print "%s: %s. The %s command failed" % ( color.cyan('fu'), color.fail('ERROR'), self.copy_command ) 42 | return worked 43 | 44 | 45 | """ Open the command in the clipboard """ 46 | def open(self,string): 47 | 48 | # We will attempt to open the url 49 | try: 50 | webbrowser.open(string) 51 | except: 52 | print "%s: %s. \n\t%s" % (color.cyan('fu'), color.fail('Unable to Open Browser' ) , string) 53 | 54 | def paste(self): 55 | """Returns system clipboard contents.""" 56 | try: 57 | return unicode(commands.getoutput('pbpaste')) 58 | except Exception, why: 59 | raise XcodeNotFound 60 | -------------------------------------------------------------------------------- /fu_core/terminalColor.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Using ANSI Colors to print to terminal 3 | 4 | Adapted from code taken from Blenders bcolor.py 5 | Samir Ahmed 2012 6 | ''' 7 | 8 | class color: 9 | HEADER = '\033[95m' 10 | OKBLUE = '\033[94m' 11 | OKGREEN = '\033[92m' 12 | WARNING = '\033[93m' 13 | FAIL = '\033[91m' 14 | ENDC = '\033[0m' 15 | 16 | CYAN = "\033[36m" 17 | MAGENTA = "\033[35m" 18 | RED = "\033[31m" 19 | YELLOW = "\033[33m" 20 | 21 | def disable(self): 22 | self.HEADER = '' 23 | self.OKBLUE = '' 24 | self.OKGREEN = '' 25 | self.WARNING = '' 26 | self.FAIL = '' 27 | self.ENDC = '' 28 | 29 | @staticmethod 30 | def cyan(string): 31 | return ''.join([color.CYAN, string, color.ENDC]) 32 | 33 | @staticmethod 34 | def magenta(string): 35 | return ''.join([color.MAGENTA, string, color.ENDC]) 36 | 37 | @staticmethod 38 | def red(string): 39 | return ''.join([color.RED, string, color.ENDC]) 40 | 41 | @staticmethod 42 | def yellow(string): 43 | return ''.join([color.YELLOW, string, color.ENDC]) 44 | 45 | @staticmethod 46 | def header(string): 47 | return ''.join([color.HEADER, string, color.ENDC]) 48 | 49 | @staticmethod 50 | def fail(string): 51 | return ''.join([color.FAIL, string, color.ENDC]) 52 | 53 | @staticmethod 54 | def warning(string): 55 | return ''.join([color.WARNING, string, color.ENDC]) 56 | 57 | @staticmethod 58 | def blue(string): 59 | return ''.join([color.OKBLUE, string, color.ENDC]) 60 | 61 | @staticmethod 62 | def green(string): 63 | return ''.join([color.OKGREEN, string, color.ENDC]) 64 | 65 | @staticmethod 66 | def testAll( string ): 67 | print "HEADER :\t %s" % color.header( string ) 68 | print "OKBLUE :\t %s" % color.blue( string) 69 | print "OKGREEN :\t %s" % color.green(string) 70 | print "CYAN :\t %s" % color.cyan(string) 71 | print "RED :\t %s" % color.red(string) 72 | print "MAGENTA :\t %s" % color.magenta(string) 73 | print "YELLOW :\t %s" % color.yellow( string) 74 | print "WARNING :\t %s" % color.warning( string) 75 | print "FAIL :\t %s" % color.fail( string) 76 | 77 | 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [CommandlineFu.com]: http://www.commandlinefu.com/commands/browse 2 | [wiki]: https://github.com/samirahmed/fu/wiki/ 3 | 4 | # What is Fu? 5 | 6 | A simple commandline utility for querying commandlinefu.com 7 | 8 | ## What is commandlinefu.com 9 | 10 | [CommandlineFu.com] is an awesome website written by David Winterbottom 11 | It has a collection of neat commandline one liners for the unix shell 12 | 13 | ## Why Fu ? 14 | 15 | Fu makes commandlinefu more accessible because I find that I use 16 | [CommandlineFu.com] most when actually in the commandline 17 | 18 | ## How do I use it? 19 | 20 | It's easy to install, all you need to do is download and make 21 | 22 | ``` 23 | $ git clone git://github.com/samirahmed/fu.git 24 | $ cd fu/ 25 | $ sudo make install 26 | ``` 27 | 28 | Or you just use bash alias if you are working under shell 29 | ``` 30 | $ cd ~/workspace 31 | $ git clone git://github.com/samirahmed/fu.git 32 | $ cd fu/ 33 | $ pwd 34 | $ /usr/me/workspace/fu 35 | $ vi ~/.bashrc | vim ~/.bash_profile 36 | $ # add alias line <<| alias fu="python /usr/me/workspace/fu/fu" 37 | $ 38 | $ source ~/.bashrc | source ~/.bashrc 39 | $ fu netstat 40 | ``` 41 | 42 | If that last step doesn't work you can use the setup.py 43 | 44 | $ sudo python setup.py install --record installRecords.txt 45 | 46 | You can query fu by adding any search terms as arguments 47 | 48 | For example if you want to search for how to "send binary mail attachment" 49 | 50 | $ fu send binary mail attachment 51 | 1 # Send email with one or more binary attachments 52 | echo "Body goes here" | mutt -s "A subject" -a /path/to/file.tar.gz recipient@example.com 53 | 54 | 2 # Send a binary file as an attachment to an email 55 | uuencode archive.tar.gz archive.tar.gz | mail -s "Emailing: archive.tar.gz" user@example.com 56 | 57 | 498ms total:2 58 | 59 | ### Dependencies and Fixes 60 | 61 | It is possible you might not have some dependencies like argparse installed. 62 | 63 | $ sudo easy_install argparse 64 | 65 | See the [wiki] for more help 66 | 67 | ## Uninstalling 68 | 69 | To remove fu you can do automagically with 70 | 71 | ``` 72 | $ cd fu/ 73 | $ sudo make uninstall 74 | ``` 75 | 76 | Or you can do it manually by finding the files from your installRecords.txt file and removing them 77 | 78 | One will be in your PYTHONPATH, the other in your /usr/local/ path probably 79 | 80 | ## Questions? 81 | 82 | There is a wiki for more examples and info about usage. 83 | 84 | Feel free to contribute if you have more ideas! 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /fu_core/test/sample.json: -------------------------------------------------------------------------------- 1 | [{"id":"1405","command":"i=\"8uyxVmdaJ-w\";mplayer -fs $(curl -s \"http:\/\/www.youtube.com\/get_video_info?&video_id=$i\" | echo -e $(sed 's\/%\/\\\\x\/g;s\/.*\\(v[0-9]\\.lscache.*\\)\/http:\\\/\\\/\\1\/g') | grep -oP '^[^|,]*')","summary":"Stream YouTube URL directly to mplayer.","votes":"48","url":"http:\/\/www.commandlinefu.com\/commands\/view\/1405\/stream-youtube-url-directly-to-mplayer."},{"id":"6689","command":"yt () mplayer -fs -quiet $(youtube-dl -g \"$1\")","summary":"Stream YouTube URL directly to MPlayer","votes":"13","url":"http:\/\/www.commandlinefu.com\/commands\/view\/6689\/stream-youtube-url-directly-to-mplayer"},{"id":"3006","command":"id=\"dMH0bHeiRNg\";mplayer -fs http:\/\/youtube.com\/get_video.php?video_id=$id\\&t=$(curl -s http:\/\/www.youtube.com\/watch?v=$id | sed -n 's\/.*, \"t\": \"\\([^\"]*\\)\", .*\/\\1\/p')","summary":"Stream YouTube URL directly to mplayer","votes":"12","url":"http:\/\/www.commandlinefu.com\/commands\/view\/3006\/stream-youtube-url-directly-to-mplayer"},{"id":"6596","command":"ID=52DnUo6wJto;mplayer -fs $(echo \"http:\/\/youtube.com\/get_video.php?&video_id=$ID$(wget -qO - 'http:\/\/youtube.com\/watch?v='$ID | perl -ne 'print $1.\"&asv=\" if \/^.*(&t=.*?)&.*$\/; print \"&fmt=\".$1 if \/^.*&fmt_map=(22).*$\/')\")","summary":"Stream YouTube URL directly to mplayer.","votes":"6","url":"http:\/\/www.commandlinefu.com\/commands\/view\/6596\/stream-youtube-url-directly-to-mplayer."},{"id":"4120","command":"mencoder -sub heading.ssa -subpos 0 -subfont-text-scale 4 -utf8 -oac copy -ovc lavc -lavcopts vcodec=mpeg4 -vf scale=320:-2,expand=:240:::1 -ffourcc xvid -o output.avi dvd.avi","summary":"DVD to YouTube ready watermarked MPEG-4 AVI file using mencoder (step 2)","votes":"3","url":"http:\/\/www.commandlinefu.com\/commands\/view\/4120\/dvd-to-youtube-ready-watermarked-mpeg-4-avi-file-using-mencoder-step-2"},{"id":"4994","command":"url=\"$my_url\";file=$(youtube-dl -s -e $url);wget -q -O - `youtube-dl -b -g $url`| ffmpeg -i - -f mp3 -vn -acodec libmp3lame - > \"$file.mp3\"","summary":"Play music from youtube without download","votes":"3","url":"http:\/\/www.commandlinefu.com\/commands\/view\/4994\/play-music-from-youtube-without-download"},{"id":"8866","command":"mplayer -fs -cookies -cookies-file \/tmp\/cookie.txt $(youtube-dl -g --cookies \/tmp\/cookie.txt \"http:\/\/www.youtube.com\/watch?v=PTOSvEX-YeY\")","summary":"Stream YouTube URL directly to mplayer.","votes":"1","url":"http:\/\/www.commandlinefu.com\/commands\/view\/8866\/stream-youtube-url-directly-to-mplayer."},{"id":"6914","command":"wget `youtube-dl -g 'http:\/\/www.youtube.com\/watch?v=-S3O9qi2E2U'` -O - | tee -a parachute-ending.flv | mplayer -cache 8192 -","summary":"Stream and save Youtube video","votes":"0","url":"http:\/\/www.commandlinefu.com\/commands\/view\/6914\/stream-and-save-youtube-video"}] -------------------------------------------------------------------------------- /fu_core/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | ''' dotfile is a class for managing the local dotfile storage 5 | 6 | saves a file called, '.fu' to your home directory 7 | the file is format a json file 8 | 9 | { 10 | result : Last Search Result 11 | last : Last Copied command 12 | history : History of used commands 13 | } 14 | 15 | the entire dot file is rewritten on every write 16 | 17 | ''' 18 | class dotfile: 19 | 20 | ''' Initialize the dotfile ''' 21 | def __init__(self): 22 | self.path = os.path.join(os.getenv("HOME"),'.fu') 23 | self.__load() 24 | self.history_size = 30 25 | 26 | ''' Get the history of all the copied command ''' 27 | def history(self): 28 | return self._history 29 | 30 | ''' Get the command that was copied''' 31 | def last(self): 32 | return self._last 33 | 34 | ''' Get the last search result ''' 35 | def result(self): 36 | return str(self._result) 37 | 38 | ''' Save the search result dotfile ''' 39 | def save_result(self,string): 40 | self._result = string 41 | 42 | ''' Copy will add the string to be copied to the dotfile ''' 43 | def save_copy(self,string): 44 | self._last = string 45 | self.__record(string) 46 | 47 | ''' Record will add a command to the history ''' 48 | def __record(self,string): 49 | 50 | # If we are at capacity, remove 51 | if len(self._history) >= self.history_size : 52 | self._history = self.history[:used_size] 53 | 54 | # Prepend to the history 55 | self._history.insert(0,string) 56 | 57 | ''' Private file for loading the dotfile ''' 58 | def __load(self): 59 | 60 | # If the file doesn't exist make it 61 | if not os.path.isfile(self.path): 62 | self.__make() 63 | # Read the file name 64 | fid = open(self.path, 'r') 65 | raw = fid.read() 66 | fid.close() 67 | 68 | # Check if we have the json objects we are looking for 69 | self._storage = json.loads(raw) 70 | 71 | if 'result' in self._storage : 72 | self._result = str (self._storage['result']) 73 | else : 74 | self._result = "" 75 | 76 | if 'last' in self._storage: 77 | self._last = self._storage['last'] 78 | else : 79 | self._last = "" 80 | 81 | if 'history' in self._storage : 82 | self._history = self._storage['history'] 83 | else : 84 | self._history = []; 85 | 86 | ''' Private helper for making an empty json file ''' 87 | def __make(self): 88 | fid = open( self.path ,'w') 89 | fid.write("{}") 90 | fid.close() 91 | 92 | def save(self): 93 | savefile = json.dumps({ 'result' : self._result, 'history' : self._history , 'last' : self._last } ) 94 | fid = open(self.path, 'w') 95 | fid.write(savefile) 96 | fid.close() 97 | 98 | -------------------------------------------------------------------------------- /fu_core/test/dotfile_test.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Test Script for dotfile config module 4 | """ 5 | 6 | import os 7 | import sys 8 | import json 9 | # Add to sys.path and then import 10 | sys.path.insert(0, os.path.abspath("..")) 11 | from config import dotfile 12 | 13 | 14 | samplefile = open('sample.json','r') 15 | sample = samplefile.read() 16 | samplefile.close() 17 | 18 | def add_response( string ): 19 | # Add the sample response to the dot file 20 | dfile = dotfile() 21 | dfile.save_result(string) 22 | dfile.save() 23 | print "Wrote Sample JSON to dotfile" 24 | 25 | 26 | def create(): 27 | 28 | filepath =os.path.join(os.getenv('HOME'),'.fu') 29 | 30 | # Put some crap in the file 31 | os.remove(filepath) 32 | 33 | # Create a dot file test 34 | dfile = dotfile() 35 | exists = os.path.isfile(filepath) 36 | print "File Exists? " + str(exists) 37 | dfile.save() 38 | 39 | def read_results( original): 40 | # Read from the dotfile and see if the sample json is there 41 | dfile = dotfile() 42 | saved = dfile.result() 43 | dfile.save() 44 | if saved == original: 45 | print "File Read is same as original!" 46 | else : 47 | print "Oops! File Read is not the samea as the original" 48 | print "Original : " + str( type(original) ) 49 | print original 50 | print "=================================================" 51 | print "Saved : " + str( type( saved )) 52 | print saved 53 | 54 | def add_command( ): 55 | # Load the dotfile, get the command 56 | dfile = dotfile() 57 | saved = dfile.result() 58 | command = json.loads(saved)[0]['command'] 59 | dfile.save_copy(command) 60 | print "Saving copy command" 61 | dfile.save() 62 | 63 | return command 64 | 65 | 66 | def read_command( string ): 67 | 68 | # Load the dotfile, get the command 69 | dfile = dotfile() 70 | command = dfile.last() 71 | dfile.save() 72 | 73 | if command == string : 74 | print "Command Saved and Loaded Successfully? Yes" 75 | else : 76 | print "Oops! Copied command was not the same!" 77 | 78 | def read_history(command): 79 | 80 | # Read history from dot file 81 | dfile = dotfile() 82 | history = dfile.history()[0] 83 | if history == command : 84 | print "History Save Loaded Succesful? True" 85 | else: 86 | print "History Save Loaded Succesful? False" 87 | 88 | print "\nDotfile Tests --------------------------\n" 89 | 90 | # Test creating a dotfile 91 | create() 92 | 93 | # Test Adding and removing json string results from a dotfile 94 | add_response(sample) 95 | read_results(sample) 96 | 97 | # Test Adding and removing last copied commands to the dotfile 98 | command = add_command() 99 | read_command(command) 100 | 101 | # Test Saving and loading history to the dotfile 102 | read_history(command) 103 | 104 | -------------------------------------------------------------------------------- /fu_core/cache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | ################################################################################ 4 | # Name : cache.py # 5 | # Brief : Fu can be really slow, local cache is necessary with 2 requires. # 6 | # 1. show local results directly, if there are. # 7 | # 2. update results, when it is possible (backend) # 8 | # # 9 | # Url : http://stackoverflow.com/questions/3358770/ # 10 | # python-dictionary-is-thread-safe # 11 | # Author : Chunqi SHI # 12 | ################################################################################ 13 | 14 | import time, sys, os 15 | # from os.path import expanduser 16 | import cPickle as pickle 17 | 18 | 19 | def pkl_dump(obj, pklfile): 20 | if os.path.exists(os.path.dirname(pklfile)): 21 | fp = open(pklfile, 'wb') 22 | try: 23 | pickle.dump(obj, fp) 24 | except IOError as err: 25 | pass 26 | finally: 27 | fp.close() 28 | 29 | def pkl_load(pklfile): 30 | if os.path.exists(pklfile): 31 | fp = open(pklfile, 'rb') 32 | try: 33 | return pickle.load(fp) 34 | except IOError as err: 35 | # print err 36 | pass 37 | finally: 38 | fp.close() 39 | 40 | 41 | class TimedResultDict(object): 42 | ### default 1 week, since web site will not update a lot between 1 week. ### 43 | def __init__(self, outdaterange=604800, pickfile='.fu_cached_pickle'): 44 | ### query:(result, time) map ### 45 | self.resultdict = {} 46 | self.outdaterange = outdaterange 47 | ### self.outdaterange = 5 ### Test 48 | ### http://stackoverflow.com/questions/4028904/how-to-get-the-home-directory-in-python ### 49 | self.pickfile = os.path.abspath(os.path.join(os.path.expanduser('~'), pickfile)) 50 | 51 | @staticmethod 52 | def load(pickfile='.fu_cached_pickle'): 53 | pickfile = os.path.abspath(os.path.join(os.path.expanduser('~'), pickfile)) 54 | trd = pkl_load(pickfile) 55 | if trd is None: 56 | trd = TimedResultDict(pickfile) 57 | else: 58 | trd.pickfile = pickfile 59 | return trd 60 | 61 | def dump(self): 62 | pkl_dump(self,self.pickfile) 63 | 64 | def is_outdate(self, querytime): 65 | return time.time() - querytime > self.outdaterange 66 | 67 | def put(self, query, result): 68 | ### only if valid result (simple test) ### 69 | if sys.getsizeof(result) > 21: 70 | self.resultdict[query] = result 71 | 72 | def get_and_update(self, query, func_update, *args, **kwargs): 73 | rst = self.get(query) 74 | if rst is None: 75 | rst = func_update(*args, **kwargs) 76 | self.put(query, (rst, time.time())) 77 | ### put and dump 78 | self.dump() 79 | return rst 80 | 81 | def get(self, query): 82 | if self.resultdict.has_key(query): 83 | (res, tim) = self.resultdict[query] 84 | if self.is_outdate(tim): 85 | return None 86 | else: 87 | return res 88 | else: 89 | return None 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /fu_core/api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | ''' 4 | The CommandLineFu API is documented at 5 | -> http://www.commandlinefu.com/site/api/ 6 | 7 | Website and API are managed by David Winterbottom. 8 | The API documentation states that all request must be in the following form 9 | http://www.commandlinefu.com/commands/// 10 | 11 | This file will use it as follows 12 | 13 | -format = json 14 | -command-set = matching/ssh/c3No/sort-by-votes/ - Search results for the query 'ssh' (note that the final segment is a base64-encoding of the search query) 15 | 16 | Written by Samir Ahmed 2012 17 | ''' 18 | 19 | import sys 20 | import json 21 | import urllib2 22 | import base64 23 | from terminalColor import color 24 | from cache import TimedResultDict 25 | 26 | class API: 27 | ''' A Wrapper Class for the commandlinefu.com API ''' 28 | def __init__(self, query_terms): 29 | # Store the search terms 30 | self.search_terms = query_terms 31 | 32 | # Create the query terms from a list of terms 33 | self.query = " ".join(query_terms) 34 | 35 | # Make and store a base64 encoded term 36 | self.query_b64 = base64.b64encode( self.query ) 37 | 38 | # Generate the url from the query and query_b64 39 | self.url = "http://www.commandlinefu.com/commands/matching/" + self.query + "/" + self.query_b64 + "/sort-by-votes/json/" 40 | 41 | # Encode the spaces too 42 | self.url = self.url.replace(" ","%20") 43 | 44 | # Enable cache 45 | self.cache = TimedResultDict.load() 46 | 47 | def load_with_cache( self ): 48 | self.response_json = self.cache.get_and_update(self.query, self.load) 49 | self.raw_json = json.dumps(self.response_json) 50 | return self.response_json 51 | 52 | def load( self ) : 53 | try : 54 | # Make request to the commandline fu API, split response by lines and print 55 | ### In case of proxy requirement ### 56 | ## 57 | # os.environ['http_proxy'] = '$hostname:$port' 58 | # os.environ['no_proxy'] = '$hostname' 59 | # 60 | urllib2.install_opener( 61 | urllib2.build_opener( 62 | urllib2.ProxyHandler() 63 | ) 64 | ) 65 | headers = { 66 | 'User-Agent': r'Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.7', 67 | 'Accept-Encoding':'gzip, deflate' 68 | } 69 | req = urllib2.Request(self.url, headers=headers) 70 | results = urllib2.urlopen(req) 71 | response_text = results.read() 72 | if 'gzip' in results.headers.get('Content-Encoding', ''): 73 | import zlib 74 | response_text = zlib.decompress(response_text, 16+zlib.MAX_WBITS) 75 | import re 76 | m = re.search(r'\[.*\]', response_text) ### make sure only json array. 77 | response_text = m.group(0) 78 | self.raw_json = response_text 79 | # Create json file and return 80 | self.response_json = json.loads(response_text) 81 | return self.response_json 82 | 83 | except urllib2.URLError, e: 84 | 85 | # In the event we have an Error we inform the user with/ colors 86 | errorStr = "%s: %s. Unable to connect to commandlinefu.com" %(color.cyan("fu"), color.red("ERROR") ) 87 | sys.exit(errorStr) 88 | 89 | ''' print api response function ''' 90 | def display ( self, isVerbose, count , showAll ) : 91 | 92 | self.total = len(self.response_json) 93 | 94 | # Check if we have any results, if inform user and exit 95 | if ( self.total <1 ) : 96 | 97 | # Print with some red 98 | print color.cyan('fu') + ": No Results Matching Query" 99 | sys.exit(0) 100 | 101 | display_count = self.total if showAll else min(self.total,count) 102 | 103 | num = 1 104 | 105 | # Print each response 106 | for result in self.response_json[: display_count]: 107 | 108 | # Extract the summary and commands 109 | summary = result['summary'] 110 | command = color.green(result['command']) 111 | 112 | # Highlight any of the search terms 113 | summary = self.__highlight( summary ) 114 | 115 | # Highlight any of a 116 | print ' %s\t# ' % color.cyan(str(num)) , summary 117 | print '\t', command 118 | 119 | if isVerbose : 120 | print '\tURL: ' , result['url'] 121 | print '\tvotes: ', result['votes'] 122 | print "" 123 | num += 1 124 | self.display_count = display_count 125 | 126 | ''' 127 | Highlight will find loosely matching search terms and color them 128 | 129 | Do a find and replace for strings for possible permutations 130 | Replace of exact matches 131 | Replace all capitalized matches 132 | Replace all whitespace bounded terms with any capitalization 133 | ''' 134 | def __highlight( self, sentence ): 135 | 136 | 137 | # Straight forward find and replace 138 | for words in self.search_terms: 139 | 140 | sentence = sentence.replace(words,color.yellow(words)) 141 | sentence = sentence.replace(words.capitalize(),color.yellow(words.capitalize())) 142 | 143 | # In case of difficult capitalization, e.g YouTube, we make a set terms 144 | query_set = set( [ terms.lower() for terms in self.search_terms ]) 145 | 146 | # Check if words match in the lower case, if so color the original word 147 | for words in sentence.split(): 148 | if words.lower() in query_set: 149 | sentence = sentence.replace(words,color.yellow(words)) 150 | 151 | return sentence 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /fu: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | ''' 4 | fu.py is a script that servers a commandline interface 5 | for commandlinefu.com via its api. 6 | 7 | This script, handles searching, open/copying previous queries 8 | etc... 9 | 10 | Samir Ahmed 2011 11 | 12 | ''' 13 | 14 | import os 15 | import json 16 | import sys 17 | import base64 18 | import time 19 | import argparse 20 | import fu_core 21 | from fu_core.terminalColor import color 22 | from fu_core.api import API 23 | from fu_core.platform_utils import system 24 | from fu_core.config import dotfile 25 | 26 | # Start time 27 | start = int(round(time.time() * 1000)) 28 | 29 | 30 | ''' 31 | Parse: Uses argparse module for parsing commandline arguments and building the help menu 32 | 33 | Options available include.... 34 | open : open, followed by a number 'i' will open the url associated with the 'ith' result of the last search in a browser 35 | copy : copy, like open, but will copy the ith result of the last search to the clipboard 36 | number : will set the number of results to display 37 | verbose : will show all the information 38 | all : will show all results, overrides numbers 39 | query-terms : The terms to be searched. 40 | 41 | ''' 42 | def parse(): 43 | 44 | # Create an argument parser for use dealing with the various command line arguments 45 | descriptionStr = "%s is a command line iterface for the commandlinefu.com. You can use fu to tap into the conventional CLI wisdom of the internet " %(color.cyan("fu")) 46 | 47 | parser = argparse.ArgumentParser( prog = "fu", description = descriptionStr, add_help = True) 48 | parser.add_argument("-o","--open", dest ="open_index",action="store", default = 0 , type = int, help = "Opens specifed command number in default browser i.e fu -o 3") 49 | parser.add_argument("-c","--copy", dest ="copy_index", action = "store" , default = 0, type = int , help = "Copies specified comamnd number to clipboard i.e fu -c 3" ) 50 | parser.add_argument("-n","--number", action="store" ,dest = "display_count" , default = 3, type = int , help = " The number of search results to display, default is 3"); 51 | parser.add_argument("-a","--all",dest = "showAll", action = "store_true", default = False, help = "Display all the search results" ) 52 | parser.add_argument("-v", "--verbose",dest = "verbose", action = "store_true", default = False, help = "Show vote counts and url") 53 | parser.add_argument("query_terms" , nargs=argparse.REMAINDER) 54 | 55 | args = parser.parse_args() 56 | 57 | # Check we have just a single number, if so, we void the search, and set the number to the copy index 58 | if len( args.query_terms) == 1 : 59 | try: 60 | num = int(args.query_terms[0]) 61 | args.copy_index = num 62 | except ValueError: 63 | pass 64 | 65 | return args 66 | 67 | ''' 68 | Executes the open procedure 69 | ''' 70 | def do_open( index, results ): 71 | 72 | try : 73 | 74 | # Extract the command, get a handle of the user's os and launch the url in a browser 75 | url = json.loads(results)[index-1]['url'] 76 | user_system = system() 77 | user_system.open(url) 78 | 79 | # notify the user 80 | print "%s Opening %s" %(color.cyan('fu!'), color.yellow(url) ) 81 | 82 | except : 83 | # catch invalid index 84 | print "%s: %s invalid index number!" % ( color.cyan('fu'), color.fail('Error') ) 85 | 86 | 87 | ''' 88 | Executes the copy procedures 89 | ''' 90 | def do_copy(index, results): 91 | 92 | try: 93 | # extract the command, get a handle to user's clipboard and copy the command 94 | command = json.loads(results)[index-1]['command'] 95 | user_system = system() 96 | copy_worked = user_system.copy(command) 97 | 98 | if copy_worked: 99 | # notify the user 100 | print "%s Copied to Clipboard!\n\t%s" % (color.cyan('fu'), color.green(command) ) 101 | 102 | except: 103 | # catch invalid index 104 | print "%s: %s invalid index number!" % ( color.cyan('fu'), color.fail('Error') ) 105 | 106 | ''' 107 | Does a search on comandline fu.com 108 | ''' 109 | def search( query_terms, verbose, count, showAll ): 110 | 111 | # Make a new instance of API 112 | api = API(query_terms) 113 | 114 | # Load API 115 | # api.load() 116 | api.load_with_cache() 117 | 118 | # Print results 119 | api.display( verbose, count, showAll ) 120 | 121 | # Print meta data 122 | duration = int(round(time.time() * 1000)) - start 123 | print "\t%dms total:%d" % ( duration, api.total ) 124 | 125 | # Return the json results to be saved 126 | return api.raw_json 127 | 128 | ''' 129 | Main script for interfacting dotfile, open, copy and search functions 130 | and parsing the input args. 131 | ''' 132 | def main() : 133 | 134 | #Parse commandline arguments 135 | args = parse() 136 | 137 | # Open dotfile 138 | dfile = dotfile() 139 | 140 | if args.copy_index > 0 : 141 | 142 | # Load the results from the dotfile and make open 143 | results_json = dfile.result() 144 | command = do_copy( args.copy_index , results_json ) 145 | dfile.save_copy(command) 146 | 147 | elif args.open_index > 0: 148 | 149 | # Load the files from the dotfile and make copy 150 | results_json = dfile.result() 151 | do_open(args.open_index , results_json ) 152 | 153 | elif len(args.query_terms) > 0: 154 | 155 | # Otherwise we pass the arguments along to the api, and display them 156 | results_json = search( args.query_terms ,args.verbose, args.display_count , args.showAll ) 157 | 158 | # Save the results to dotfile 159 | dfile.save_result(results_json) 160 | 161 | # Save and close the dotfile 162 | dfile.save() 163 | 164 | if __name__ == "__main__": 165 | 166 | if len(sys.argv) == 1 : 167 | print "%s: Incorrect usage, please include search terms. See 'fu --help' " % ( color.cyan("fu") ) 168 | else : 169 | main( ) 170 | -------------------------------------------------------------------------------- /argparse.py: -------------------------------------------------------------------------------- 1 | # Author: Steven J. Bethard . 2 | 3 | """Command-line parsing library 4 | 5 | This module is an optparse-inspired command-line parsing library that: 6 | 7 | - handles both optional and positional arguments 8 | - produces highly informative usage messages 9 | - supports parsers that dispatch to sub-parsers 10 | 11 | The following is a simple usage example that sums integers from the 12 | command-line and writes the result to a file:: 13 | 14 | parser = argparse.ArgumentParser( 15 | description='sum the integers at the command line') 16 | parser.add_argument( 17 | 'integers', metavar='int', nargs='+', type=int, 18 | help='an integer to be summed') 19 | parser.add_argument( 20 | '--log', default=sys.stdout, type=argparse.FileType('w'), 21 | help='the file where the sum should be written') 22 | args = parser.parse_args() 23 | args.log.write('%s' % sum(args.integers)) 24 | args.log.close() 25 | 26 | The module contains the following public classes: 27 | 28 | - ArgumentParser -- The main entry point for command-line parsing. As the 29 | example above shows, the add_argument() method is used to populate 30 | the parser with actions for optional and positional arguments. Then 31 | the parse_args() method is invoked to convert the args at the 32 | command-line into an object with attributes. 33 | 34 | - ArgumentError -- The exception raised by ArgumentParser objects when 35 | there are errors with the parser's actions. Errors raised while 36 | parsing the command-line are caught by ArgumentParser and emitted 37 | as command-line messages. 38 | 39 | - FileType -- A factory for defining types of files to be created. As the 40 | example above shows, instances of FileType are typically passed as 41 | the type= argument of add_argument() calls. 42 | 43 | - Action -- The base class for parser actions. Typically actions are 44 | selected by passing strings like 'store_true' or 'append_const' to 45 | the action= argument of add_argument(). However, for greater 46 | customization of ArgumentParser actions, subclasses of Action may 47 | be defined and passed as the action= argument. 48 | 49 | - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter, 50 | ArgumentDefaultsHelpFormatter -- Formatter classes which 51 | may be passed as the formatter_class= argument to the 52 | ArgumentParser constructor. HelpFormatter is the default, 53 | RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser 54 | not to change the formatting for help text, and 55 | ArgumentDefaultsHelpFormatter adds information about argument defaults 56 | to the help. 57 | 58 | All other classes in this module are considered implementation details. 59 | (Also note that HelpFormatter and RawDescriptionHelpFormatter are only 60 | considered public as object names -- the API of the formatter objects is 61 | still considered an implementation detail.) 62 | """ 63 | 64 | __version__ = '1.1' 65 | __all__ = [ 66 | 'ArgumentParser', 67 | 'ArgumentError', 68 | 'ArgumentTypeError', 69 | 'FileType', 70 | 'HelpFormatter', 71 | 'ArgumentDefaultsHelpFormatter', 72 | 'RawDescriptionHelpFormatter', 73 | 'RawTextHelpFormatter', 74 | 'Namespace', 75 | 'Action', 76 | 'ONE_OR_MORE', 77 | 'OPTIONAL', 78 | 'PARSER', 79 | 'REMAINDER', 80 | 'SUPPRESS', 81 | 'ZERO_OR_MORE', 82 | ] 83 | 84 | 85 | import collections as _collections 86 | import copy as _copy 87 | import os as _os 88 | import re as _re 89 | import sys as _sys 90 | import textwrap as _textwrap 91 | 92 | from gettext import gettext as _ 93 | 94 | 95 | def _callable(obj): 96 | return hasattr(obj, '__call__') or hasattr(obj, '__bases__') 97 | 98 | 99 | SUPPRESS = '==SUPPRESS==' 100 | 101 | OPTIONAL = '?' 102 | ZERO_OR_MORE = '*' 103 | ONE_OR_MORE = '+' 104 | PARSER = 'A...' 105 | REMAINDER = '...' 106 | _UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args' 107 | 108 | # ============================= 109 | # Utility functions and classes 110 | # ============================= 111 | 112 | class _AttributeHolder(object): 113 | """Abstract base class that provides __repr__. 114 | 115 | The __repr__ method returns a string in the format:: 116 | ClassName(attr=name, attr=name, ...) 117 | The attributes are determined either by a class-level attribute, 118 | '_kwarg_names', or by inspecting the instance __dict__. 119 | """ 120 | 121 | def __repr__(self): 122 | type_name = type(self).__name__ 123 | arg_strings = [] 124 | for arg in self._get_args(): 125 | arg_strings.append(repr(arg)) 126 | for name, value in self._get_kwargs(): 127 | arg_strings.append('%s=%r' % (name, value)) 128 | return '%s(%s)' % (type_name, ', '.join(arg_strings)) 129 | 130 | def _get_kwargs(self): 131 | return sorted(self.__dict__.items()) 132 | 133 | def _get_args(self): 134 | return [] 135 | 136 | 137 | def _ensure_value(namespace, name, value): 138 | if getattr(namespace, name, None) is None: 139 | setattr(namespace, name, value) 140 | return getattr(namespace, name) 141 | 142 | 143 | # =============== 144 | # Formatting Help 145 | # =============== 146 | 147 | class HelpFormatter(object): 148 | """Formatter for generating usage messages and argument help strings. 149 | 150 | Only the name of this class is considered a public API. All the methods 151 | provided by the class are considered an implementation detail. 152 | """ 153 | 154 | def __init__(self, 155 | prog, 156 | indent_increment=2, 157 | max_help_position=24, 158 | width=None): 159 | 160 | # default setting for width 161 | if width is None: 162 | try: 163 | width = int(_os.environ['COLUMNS']) 164 | except (KeyError, ValueError): 165 | width = 80 166 | width -= 2 167 | 168 | self._prog = prog 169 | self._indent_increment = indent_increment 170 | self._max_help_position = max_help_position 171 | self._max_help_position = min(max_help_position, 172 | max(width - 20, indent_increment * 2)) 173 | self._width = width 174 | 175 | self._current_indent = 0 176 | self._level = 0 177 | self._action_max_length = 0 178 | 179 | self._root_section = self._Section(self, None) 180 | self._current_section = self._root_section 181 | 182 | self._whitespace_matcher = _re.compile(r'\s+') 183 | self._long_break_matcher = _re.compile(r'\n\n\n+') 184 | 185 | # =============================== 186 | # Section and indentation methods 187 | # =============================== 188 | def _indent(self): 189 | self._current_indent += self._indent_increment 190 | self._level += 1 191 | 192 | def _dedent(self): 193 | self._current_indent -= self._indent_increment 194 | assert self._current_indent >= 0, 'Indent decreased below 0.' 195 | self._level -= 1 196 | 197 | class _Section(object): 198 | 199 | def __init__(self, formatter, parent, heading=None): 200 | self.formatter = formatter 201 | self.parent = parent 202 | self.heading = heading 203 | self.items = [] 204 | 205 | def format_help(self): 206 | # format the indented section 207 | if self.parent is not None: 208 | self.formatter._indent() 209 | join = self.formatter._join_parts 210 | for func, args in self.items: 211 | func(*args) 212 | item_help = join([func(*args) for func, args in self.items]) 213 | if self.parent is not None: 214 | self.formatter._dedent() 215 | 216 | # return nothing if the section was empty 217 | if not item_help: 218 | return '' 219 | 220 | # add the heading if the section was non-empty 221 | if self.heading is not SUPPRESS and self.heading is not None: 222 | current_indent = self.formatter._current_indent 223 | heading = '%*s%s:\n' % (current_indent, '', self.heading) 224 | else: 225 | heading = '' 226 | 227 | # join the section-initial newline, the heading and the help 228 | return join(['\n', heading, item_help, '\n']) 229 | 230 | def _add_item(self, func, args): 231 | self._current_section.items.append((func, args)) 232 | 233 | # ======================== 234 | # Message building methods 235 | # ======================== 236 | def start_section(self, heading): 237 | self._indent() 238 | section = self._Section(self, self._current_section, heading) 239 | self._add_item(section.format_help, []) 240 | self._current_section = section 241 | 242 | def end_section(self): 243 | self._current_section = self._current_section.parent 244 | self._dedent() 245 | 246 | def add_text(self, text): 247 | if text is not SUPPRESS and text is not None: 248 | self._add_item(self._format_text, [text]) 249 | 250 | def add_usage(self, usage, actions, groups, prefix=None): 251 | if usage is not SUPPRESS: 252 | args = usage, actions, groups, prefix 253 | self._add_item(self._format_usage, args) 254 | 255 | def add_argument(self, action): 256 | if action.help is not SUPPRESS: 257 | 258 | # find all invocations 259 | get_invocation = self._format_action_invocation 260 | invocations = [get_invocation(action)] 261 | for subaction in self._iter_indented_subactions(action): 262 | invocations.append(get_invocation(subaction)) 263 | 264 | # update the maximum item length 265 | invocation_length = max([len(s) for s in invocations]) 266 | action_length = invocation_length + self._current_indent 267 | self._action_max_length = max(self._action_max_length, 268 | action_length) 269 | 270 | # add the item to the list 271 | self._add_item(self._format_action, [action]) 272 | 273 | def add_arguments(self, actions): 274 | for action in actions: 275 | self.add_argument(action) 276 | 277 | # ======================= 278 | # Help-formatting methods 279 | # ======================= 280 | def format_help(self): 281 | help = self._root_section.format_help() 282 | if help: 283 | help = self._long_break_matcher.sub('\n\n', help) 284 | help = help.strip('\n') + '\n' 285 | return help 286 | 287 | def _join_parts(self, part_strings): 288 | return ''.join([part 289 | for part in part_strings 290 | if part and part is not SUPPRESS]) 291 | 292 | def _format_usage(self, usage, actions, groups, prefix): 293 | if prefix is None: 294 | prefix = _('usage: ') 295 | 296 | # if usage is specified, use that 297 | if usage is not None: 298 | usage = usage % dict(prog=self._prog) 299 | 300 | # if no optionals or positionals are available, usage is just prog 301 | elif usage is None and not actions: 302 | usage = '%(prog)s' % dict(prog=self._prog) 303 | 304 | # if optionals and positionals are available, calculate usage 305 | elif usage is None: 306 | prog = '%(prog)s' % dict(prog=self._prog) 307 | 308 | # split optionals from positionals 309 | optionals = [] 310 | positionals = [] 311 | for action in actions: 312 | if action.option_strings: 313 | optionals.append(action) 314 | else: 315 | positionals.append(action) 316 | 317 | # build full usage string 318 | format = self._format_actions_usage 319 | action_usage = format(optionals + positionals, groups) 320 | usage = ' '.join([s for s in [prog, action_usage] if s]) 321 | 322 | # wrap the usage parts if it's too long 323 | text_width = self._width - self._current_indent 324 | if len(prefix) + len(usage) > text_width: 325 | 326 | # break usage into wrappable parts 327 | part_regexp = r'\(.*?\)+|\[.*?\]+|\S+' 328 | opt_usage = format(optionals, groups) 329 | pos_usage = format(positionals, groups) 330 | opt_parts = _re.findall(part_regexp, opt_usage) 331 | pos_parts = _re.findall(part_regexp, pos_usage) 332 | assert ' '.join(opt_parts) == opt_usage 333 | assert ' '.join(pos_parts) == pos_usage 334 | 335 | # helper for wrapping lines 336 | def get_lines(parts, indent, prefix=None): 337 | lines = [] 338 | line = [] 339 | if prefix is not None: 340 | line_len = len(prefix) - 1 341 | else: 342 | line_len = len(indent) - 1 343 | for part in parts: 344 | if line_len + 1 + len(part) > text_width and line: 345 | lines.append(indent + ' '.join(line)) 346 | line = [] 347 | line_len = len(indent) - 1 348 | line.append(part) 349 | line_len += len(part) + 1 350 | if line: 351 | lines.append(indent + ' '.join(line)) 352 | if prefix is not None: 353 | lines[0] = lines[0][len(indent):] 354 | return lines 355 | 356 | # if prog is short, follow it with optionals or positionals 357 | if len(prefix) + len(prog) <= 0.75 * text_width: 358 | indent = ' ' * (len(prefix) + len(prog) + 1) 359 | if opt_parts: 360 | lines = get_lines([prog] + opt_parts, indent, prefix) 361 | lines.extend(get_lines(pos_parts, indent)) 362 | elif pos_parts: 363 | lines = get_lines([prog] + pos_parts, indent, prefix) 364 | else: 365 | lines = [prog] 366 | 367 | # if prog is long, put it on its own line 368 | else: 369 | indent = ' ' * len(prefix) 370 | parts = opt_parts + pos_parts 371 | lines = get_lines(parts, indent) 372 | if len(lines) > 1: 373 | lines = [] 374 | lines.extend(get_lines(opt_parts, indent)) 375 | lines.extend(get_lines(pos_parts, indent)) 376 | lines = [prog] + lines 377 | 378 | # join lines into usage 379 | usage = '\n'.join(lines) 380 | 381 | # prefix with 'usage:' 382 | return '%s%s\n\n' % (prefix, usage) 383 | 384 | def _format_actions_usage(self, actions, groups): 385 | # find group indices and identify actions in groups 386 | group_actions = set() 387 | inserts = {} 388 | for group in groups: 389 | try: 390 | start = actions.index(group._group_actions[0]) 391 | except ValueError: 392 | continue 393 | else: 394 | end = start + len(group._group_actions) 395 | if actions[start:end] == group._group_actions: 396 | for action in group._group_actions: 397 | group_actions.add(action) 398 | if not group.required: 399 | if start in inserts: 400 | inserts[start] += ' [' 401 | else: 402 | inserts[start] = '[' 403 | inserts[end] = ']' 404 | else: 405 | if start in inserts: 406 | inserts[start] += ' (' 407 | else: 408 | inserts[start] = '(' 409 | inserts[end] = ')' 410 | for i in range(start + 1, end): 411 | inserts[i] = '|' 412 | 413 | # collect all actions format strings 414 | parts = [] 415 | for i, action in enumerate(actions): 416 | 417 | # suppressed arguments are marked with None 418 | # remove | separators for suppressed arguments 419 | if action.help is SUPPRESS: 420 | parts.append(None) 421 | if inserts.get(i) == '|': 422 | inserts.pop(i) 423 | elif inserts.get(i + 1) == '|': 424 | inserts.pop(i + 1) 425 | 426 | # produce all arg strings 427 | elif not action.option_strings: 428 | part = self._format_args(action, action.dest) 429 | 430 | # if it's in a group, strip the outer [] 431 | if action in group_actions: 432 | if part[0] == '[' and part[-1] == ']': 433 | part = part[1:-1] 434 | 435 | # add the action string to the list 436 | parts.append(part) 437 | 438 | # produce the first way to invoke the option in brackets 439 | else: 440 | option_string = action.option_strings[0] 441 | 442 | # if the Optional doesn't take a value, format is: 443 | # -s or --long 444 | if action.nargs == 0: 445 | part = '%s' % option_string 446 | 447 | # if the Optional takes a value, format is: 448 | # -s ARGS or --long ARGS 449 | else: 450 | default = action.dest.upper() 451 | args_string = self._format_args(action, default) 452 | part = '%s %s' % (option_string, args_string) 453 | 454 | # make it look optional if it's not required or in a group 455 | if not action.required and action not in group_actions: 456 | part = '[%s]' % part 457 | 458 | # add the action string to the list 459 | parts.append(part) 460 | 461 | # insert things at the necessary indices 462 | for i in sorted(inserts, reverse=True): 463 | parts[i:i] = [inserts[i]] 464 | 465 | # join all the action items with spaces 466 | text = ' '.join([item for item in parts if item is not None]) 467 | 468 | # clean up separators for mutually exclusive groups 469 | open = r'[\[(]' 470 | close = r'[\])]' 471 | text = _re.sub(r'(%s) ' % open, r'\1', text) 472 | text = _re.sub(r' (%s)' % close, r'\1', text) 473 | text = _re.sub(r'%s *%s' % (open, close), r'', text) 474 | text = _re.sub(r'\(([^|]*)\)', r'\1', text) 475 | text = text.strip() 476 | 477 | # return the text 478 | return text 479 | 480 | def _format_text(self, text): 481 | if '%(prog)' in text: 482 | text = text % dict(prog=self._prog) 483 | text_width = max(self._width - self._current_indent, 11) 484 | indent = ' ' * self._current_indent 485 | return self._fill_text(text, text_width, indent) + '\n\n' 486 | 487 | def _format_action(self, action): 488 | # determine the required width and the entry label 489 | help_position = min(self._action_max_length + 2, 490 | self._max_help_position) 491 | help_width = max(self._width - help_position, 11) 492 | action_width = help_position - self._current_indent - 2 493 | action_header = self._format_action_invocation(action) 494 | 495 | # ho nelp; start on same line and add a final newline 496 | if not action.help: 497 | tup = self._current_indent, '', action_header 498 | action_header = '%*s%s\n' % tup 499 | 500 | # short action name; start on the same line and pad two spaces 501 | elif len(action_header) <= action_width: 502 | tup = self._current_indent, '', action_width, action_header 503 | action_header = '%*s%-*s ' % tup 504 | indent_first = 0 505 | 506 | # long action name; start on the next line 507 | else: 508 | tup = self._current_indent, '', action_header 509 | action_header = '%*s%s\n' % tup 510 | indent_first = help_position 511 | 512 | # collect the pieces of the action help 513 | parts = [action_header] 514 | 515 | # if there was help for the action, add lines of help text 516 | if action.help: 517 | help_text = self._expand_help(action) 518 | help_lines = self._split_lines(help_text, help_width) 519 | parts.append('%*s%s\n' % (indent_first, '', help_lines[0])) 520 | for line in help_lines[1:]: 521 | parts.append('%*s%s\n' % (help_position, '', line)) 522 | 523 | # or add a newline if the description doesn't end with one 524 | elif not action_header.endswith('\n'): 525 | parts.append('\n') 526 | 527 | # if there are any sub-actions, add their help as well 528 | for subaction in self._iter_indented_subactions(action): 529 | parts.append(self._format_action(subaction)) 530 | 531 | # return a single string 532 | return self._join_parts(parts) 533 | 534 | def _format_action_invocation(self, action): 535 | if not action.option_strings: 536 | metavar, = self._metavar_formatter(action, action.dest)(1) 537 | return metavar 538 | 539 | else: 540 | parts = [] 541 | 542 | # if the Optional doesn't take a value, format is: 543 | # -s, --long 544 | if action.nargs == 0: 545 | parts.extend(action.option_strings) 546 | 547 | # if the Optional takes a value, format is: 548 | # -s ARGS, --long ARGS 549 | else: 550 | default = action.dest.upper() 551 | args_string = self._format_args(action, default) 552 | for option_string in action.option_strings: 553 | parts.append('%s %s' % (option_string, args_string)) 554 | 555 | return ', '.join(parts) 556 | 557 | def _metavar_formatter(self, action, default_metavar): 558 | if action.metavar is not None: 559 | result = action.metavar 560 | elif action.choices is not None: 561 | choice_strs = [str(choice) for choice in action.choices] 562 | result = '{%s}' % ','.join(choice_strs) 563 | else: 564 | result = default_metavar 565 | 566 | def format(tuple_size): 567 | if isinstance(result, tuple): 568 | return result 569 | else: 570 | return (result, ) * tuple_size 571 | return format 572 | 573 | def _format_args(self, action, default_metavar): 574 | get_metavar = self._metavar_formatter(action, default_metavar) 575 | if action.nargs is None: 576 | result = '%s' % get_metavar(1) 577 | elif action.nargs == OPTIONAL: 578 | result = '[%s]' % get_metavar(1) 579 | elif action.nargs == ZERO_OR_MORE: 580 | result = '[%s [%s ...]]' % get_metavar(2) 581 | elif action.nargs == ONE_OR_MORE: 582 | result = '%s [%s ...]' % get_metavar(2) 583 | elif action.nargs == REMAINDER: 584 | result = '...' 585 | elif action.nargs == PARSER: 586 | result = '%s ...' % get_metavar(1) 587 | else: 588 | formats = ['%s' for _ in range(action.nargs)] 589 | result = ' '.join(formats) % get_metavar(action.nargs) 590 | return result 591 | 592 | def _expand_help(self, action): 593 | params = dict(vars(action), prog=self._prog) 594 | for name in list(params): 595 | if params[name] is SUPPRESS: 596 | del params[name] 597 | for name in list(params): 598 | if hasattr(params[name], '__name__'): 599 | params[name] = params[name].__name__ 600 | if params.get('choices') is not None: 601 | choices_str = ', '.join([str(c) for c in params['choices']]) 602 | params['choices'] = choices_str 603 | return self._get_help_string(action) % params 604 | 605 | def _iter_indented_subactions(self, action): 606 | try: 607 | get_subactions = action._get_subactions 608 | except AttributeError: 609 | pass 610 | else: 611 | self._indent() 612 | for subaction in get_subactions(): 613 | yield subaction 614 | self._dedent() 615 | 616 | def _split_lines(self, text, width): 617 | text = self._whitespace_matcher.sub(' ', text).strip() 618 | return _textwrap.wrap(text, width) 619 | 620 | def _fill_text(self, text, width, indent): 621 | text = self._whitespace_matcher.sub(' ', text).strip() 622 | return _textwrap.fill(text, width, initial_indent=indent, 623 | subsequent_indent=indent) 624 | 625 | def _get_help_string(self, action): 626 | return action.help 627 | 628 | 629 | class RawDescriptionHelpFormatter(HelpFormatter): 630 | """Help message formatter which retains any formatting in descriptions. 631 | 632 | Only the name of this class is considered a public API. All the methods 633 | provided by the class are considered an implementation detail. 634 | """ 635 | 636 | def _fill_text(self, text, width, indent): 637 | return ''.join([indent + line for line in text.splitlines(True)]) 638 | 639 | 640 | class RawTextHelpFormatter(RawDescriptionHelpFormatter): 641 | """Help message formatter which retains formatting of all help text. 642 | 643 | Only the name of this class is considered a public API. All the methods 644 | provided by the class are considered an implementation detail. 645 | """ 646 | 647 | def _split_lines(self, text, width): 648 | return text.splitlines() 649 | 650 | 651 | class ArgumentDefaultsHelpFormatter(HelpFormatter): 652 | """Help message formatter which adds default values to argument help. 653 | 654 | Only the name of this class is considered a public API. All the methods 655 | provided by the class are considered an implementation detail. 656 | """ 657 | 658 | def _get_help_string(self, action): 659 | help = action.help 660 | if '%(default)' not in action.help: 661 | if action.default is not SUPPRESS: 662 | defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] 663 | if action.option_strings or action.nargs in defaulting_nargs: 664 | help += ' (default: %(default)s)' 665 | return help 666 | 667 | 668 | # ===================== 669 | # Options and Arguments 670 | # ===================== 671 | 672 | def _get_action_name(argument): 673 | if argument is None: 674 | return None 675 | elif argument.option_strings: 676 | return '/'.join(argument.option_strings) 677 | elif argument.metavar not in (None, SUPPRESS): 678 | return argument.metavar 679 | elif argument.dest not in (None, SUPPRESS): 680 | return argument.dest 681 | else: 682 | return None 683 | 684 | 685 | class ArgumentError(Exception): 686 | """An error from creating or using an argument (optional or positional). 687 | 688 | The string value of this exception is the message, augmented with 689 | information about the argument that caused it. 690 | """ 691 | 692 | def __init__(self, argument, message): 693 | self.argument_name = _get_action_name(argument) 694 | self.message = message 695 | 696 | def __str__(self): 697 | if self.argument_name is None: 698 | format = '%(message)s' 699 | else: 700 | format = 'argument %(argument_name)s: %(message)s' 701 | return format % dict(message=self.message, 702 | argument_name=self.argument_name) 703 | 704 | 705 | class ArgumentTypeError(Exception): 706 | """An error from trying to convert a command line string to a type.""" 707 | pass 708 | 709 | 710 | # ============== 711 | # Action classes 712 | # ============== 713 | 714 | class Action(_AttributeHolder): 715 | """Information about how to convert command line strings to Python objects. 716 | 717 | Action objects are used by an ArgumentParser to represent the information 718 | needed to parse a single argument from one or more strings from the 719 | command line. The keyword arguments to the Action constructor are also 720 | all attributes of Action instances. 721 | 722 | Keyword Arguments: 723 | 724 | - option_strings -- A list of command-line option strings which 725 | should be associated with this action. 726 | 727 | - dest -- The name of the attribute to hold the created object(s) 728 | 729 | - nargs -- The number of command-line arguments that should be 730 | consumed. By default, one argument will be consumed and a single 731 | value will be produced. Other values include: 732 | - N (an integer) consumes N arguments (and produces a list) 733 | - '?' consumes zero or one arguments 734 | - '*' consumes zero or more arguments (and produces a list) 735 | - '+' consumes one or more arguments (and produces a list) 736 | Note that the difference between the default and nargs=1 is that 737 | with the default, a single value will be produced, while with 738 | nargs=1, a list containing a single value will be produced. 739 | 740 | - const -- The value to be produced if the option is specified and the 741 | option uses an action that takes no values. 742 | 743 | - default -- The value to be produced if the option is not specified. 744 | 745 | - type -- A callable that accepts a single string argument, and 746 | returns the converted value. The standard Python types str, int, 747 | float, and complex are useful examples of such callables. If None, 748 | str is used. 749 | 750 | - choices -- A container of values that should be allowed. If not None, 751 | after a command-line argument has been converted to the appropriate 752 | type, an exception will be raised if it is not a member of this 753 | collection. 754 | 755 | - required -- True if the action must always be specified at the 756 | command line. This is only meaningful for optional command-line 757 | arguments. 758 | 759 | - help -- The help string describing the argument. 760 | 761 | - metavar -- The name to be used for the option's argument with the 762 | help string. If None, the 'dest' value will be used as the name. 763 | """ 764 | 765 | def __init__(self, 766 | option_strings, 767 | dest, 768 | nargs=None, 769 | const=None, 770 | default=None, 771 | type=None, 772 | choices=None, 773 | required=False, 774 | help=None, 775 | metavar=None): 776 | self.option_strings = option_strings 777 | self.dest = dest 778 | self.nargs = nargs 779 | self.const = const 780 | self.default = default 781 | self.type = type 782 | self.choices = choices 783 | self.required = required 784 | self.help = help 785 | self.metavar = metavar 786 | 787 | def _get_kwargs(self): 788 | names = [ 789 | 'option_strings', 790 | 'dest', 791 | 'nargs', 792 | 'const', 793 | 'default', 794 | 'type', 795 | 'choices', 796 | 'help', 797 | 'metavar', 798 | ] 799 | return [(name, getattr(self, name)) for name in names] 800 | 801 | def __call__(self, parser, namespace, values, option_string=None): 802 | raise NotImplementedError(_('.__call__() not defined')) 803 | 804 | 805 | class _StoreAction(Action): 806 | 807 | def __init__(self, 808 | option_strings, 809 | dest, 810 | nargs=None, 811 | const=None, 812 | default=None, 813 | type=None, 814 | choices=None, 815 | required=False, 816 | help=None, 817 | metavar=None): 818 | if nargs == 0: 819 | raise ValueError('nargs for store actions must be > 0; if you ' 820 | 'have nothing to store, actions such as store ' 821 | 'true or store const may be more appropriate') 822 | if const is not None and nargs != OPTIONAL: 823 | raise ValueError('nargs must be %r to supply const' % OPTIONAL) 824 | super(_StoreAction, self).__init__( 825 | option_strings=option_strings, 826 | dest=dest, 827 | nargs=nargs, 828 | const=const, 829 | default=default, 830 | type=type, 831 | choices=choices, 832 | required=required, 833 | help=help, 834 | metavar=metavar) 835 | 836 | def __call__(self, parser, namespace, values, option_string=None): 837 | setattr(namespace, self.dest, values) 838 | 839 | 840 | class _StoreConstAction(Action): 841 | 842 | def __init__(self, 843 | option_strings, 844 | dest, 845 | const, 846 | default=None, 847 | required=False, 848 | help=None, 849 | metavar=None): 850 | super(_StoreConstAction, self).__init__( 851 | option_strings=option_strings, 852 | dest=dest, 853 | nargs=0, 854 | const=const, 855 | default=default, 856 | required=required, 857 | help=help) 858 | 859 | def __call__(self, parser, namespace, values, option_string=None): 860 | setattr(namespace, self.dest, self.const) 861 | 862 | 863 | class _StoreTrueAction(_StoreConstAction): 864 | 865 | def __init__(self, 866 | option_strings, 867 | dest, 868 | default=False, 869 | required=False, 870 | help=None): 871 | super(_StoreTrueAction, self).__init__( 872 | option_strings=option_strings, 873 | dest=dest, 874 | const=True, 875 | default=default, 876 | required=required, 877 | help=help) 878 | 879 | 880 | class _StoreFalseAction(_StoreConstAction): 881 | 882 | def __init__(self, 883 | option_strings, 884 | dest, 885 | default=True, 886 | required=False, 887 | help=None): 888 | super(_StoreFalseAction, self).__init__( 889 | option_strings=option_strings, 890 | dest=dest, 891 | const=False, 892 | default=default, 893 | required=required, 894 | help=help) 895 | 896 | 897 | class _AppendAction(Action): 898 | 899 | def __init__(self, 900 | option_strings, 901 | dest, 902 | nargs=None, 903 | const=None, 904 | default=None, 905 | type=None, 906 | choices=None, 907 | required=False, 908 | help=None, 909 | metavar=None): 910 | if nargs == 0: 911 | raise ValueError('nargs for append actions must be > 0; if arg ' 912 | 'strings are not supplying the value to append, ' 913 | 'the append const action may be more appropriate') 914 | if const is not None and nargs != OPTIONAL: 915 | raise ValueError('nargs must be %r to supply const' % OPTIONAL) 916 | super(_AppendAction, self).__init__( 917 | option_strings=option_strings, 918 | dest=dest, 919 | nargs=nargs, 920 | const=const, 921 | default=default, 922 | type=type, 923 | choices=choices, 924 | required=required, 925 | help=help, 926 | metavar=metavar) 927 | 928 | def __call__(self, parser, namespace, values, option_string=None): 929 | items = _copy.copy(_ensure_value(namespace, self.dest, [])) 930 | items.append(values) 931 | setattr(namespace, self.dest, items) 932 | 933 | 934 | class _AppendConstAction(Action): 935 | 936 | def __init__(self, 937 | option_strings, 938 | dest, 939 | const, 940 | default=None, 941 | required=False, 942 | help=None, 943 | metavar=None): 944 | super(_AppendConstAction, self).__init__( 945 | option_strings=option_strings, 946 | dest=dest, 947 | nargs=0, 948 | const=const, 949 | default=default, 950 | required=required, 951 | help=help, 952 | metavar=metavar) 953 | 954 | def __call__(self, parser, namespace, values, option_string=None): 955 | items = _copy.copy(_ensure_value(namespace, self.dest, [])) 956 | items.append(self.const) 957 | setattr(namespace, self.dest, items) 958 | 959 | 960 | class _CountAction(Action): 961 | 962 | def __init__(self, 963 | option_strings, 964 | dest, 965 | default=None, 966 | required=False, 967 | help=None): 968 | super(_CountAction, self).__init__( 969 | option_strings=option_strings, 970 | dest=dest, 971 | nargs=0, 972 | default=default, 973 | required=required, 974 | help=help) 975 | 976 | def __call__(self, parser, namespace, values, option_string=None): 977 | new_count = _ensure_value(namespace, self.dest, 0) + 1 978 | setattr(namespace, self.dest, new_count) 979 | 980 | 981 | class _HelpAction(Action): 982 | 983 | def __init__(self, 984 | option_strings, 985 | dest=SUPPRESS, 986 | default=SUPPRESS, 987 | help=None): 988 | super(_HelpAction, self).__init__( 989 | option_strings=option_strings, 990 | dest=dest, 991 | default=default, 992 | nargs=0, 993 | help=help) 994 | 995 | def __call__(self, parser, namespace, values, option_string=None): 996 | parser.print_help() 997 | parser.exit() 998 | 999 | 1000 | class _VersionAction(Action): 1001 | 1002 | def __init__(self, 1003 | option_strings, 1004 | version=None, 1005 | dest=SUPPRESS, 1006 | default=SUPPRESS, 1007 | help="show program's version number and exit"): 1008 | super(_VersionAction, self).__init__( 1009 | option_strings=option_strings, 1010 | dest=dest, 1011 | default=default, 1012 | nargs=0, 1013 | help=help) 1014 | self.version = version 1015 | 1016 | def __call__(self, parser, namespace, values, option_string=None): 1017 | version = self.version 1018 | if version is None: 1019 | version = parser.version 1020 | formatter = parser._get_formatter() 1021 | formatter.add_text(version) 1022 | parser.exit(message=formatter.format_help()) 1023 | 1024 | 1025 | class _SubParsersAction(Action): 1026 | 1027 | class _ChoicesPseudoAction(Action): 1028 | 1029 | def __init__(self, name, help): 1030 | sup = super(_SubParsersAction._ChoicesPseudoAction, self) 1031 | sup.__init__(option_strings=[], dest=name, help=help) 1032 | 1033 | def __init__(self, 1034 | option_strings, 1035 | prog, 1036 | parser_class, 1037 | dest=SUPPRESS, 1038 | help=None, 1039 | metavar=None): 1040 | 1041 | self._prog_prefix = prog 1042 | self._parser_class = parser_class 1043 | self._name_parser_map = _collections.OrderedDict() 1044 | self._choices_actions = [] 1045 | 1046 | super(_SubParsersAction, self).__init__( 1047 | option_strings=option_strings, 1048 | dest=dest, 1049 | nargs=PARSER, 1050 | choices=self._name_parser_map, 1051 | help=help, 1052 | metavar=metavar) 1053 | 1054 | def add_parser(self, name, **kwargs): 1055 | # set prog from the existing prefix 1056 | if kwargs.get('prog') is None: 1057 | kwargs['prog'] = '%s %s' % (self._prog_prefix, name) 1058 | 1059 | # create a pseudo-action to hold the choice help 1060 | if 'help' in kwargs: 1061 | help = kwargs.pop('help') 1062 | choice_action = self._ChoicesPseudoAction(name, help) 1063 | self._choices_actions.append(choice_action) 1064 | 1065 | # create the parser and add it to the map 1066 | parser = self._parser_class(**kwargs) 1067 | self._name_parser_map[name] = parser 1068 | return parser 1069 | 1070 | def _get_subactions(self): 1071 | return self._choices_actions 1072 | 1073 | def __call__(self, parser, namespace, values, option_string=None): 1074 | parser_name = values[0] 1075 | arg_strings = values[1:] 1076 | 1077 | # set the parser name if requested 1078 | if self.dest is not SUPPRESS: 1079 | setattr(namespace, self.dest, parser_name) 1080 | 1081 | # select the parser 1082 | try: 1083 | parser = self._name_parser_map[parser_name] 1084 | except KeyError: 1085 | tup = parser_name, ', '.join(self._name_parser_map) 1086 | msg = _('unknown parser %r (choices: %s)') % tup 1087 | raise ArgumentError(self, msg) 1088 | 1089 | # parse all the remaining options into the namespace 1090 | # store any unrecognized options on the object, so that the top 1091 | # level parser can decide what to do with them 1092 | namespace, arg_strings = parser.parse_known_args(arg_strings, namespace) 1093 | if arg_strings: 1094 | vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, []) 1095 | getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings) 1096 | 1097 | 1098 | # ============== 1099 | # Type classes 1100 | # ============== 1101 | 1102 | class FileType(object): 1103 | """Factory for creating file object types 1104 | 1105 | Instances of FileType are typically passed as type= arguments to the 1106 | ArgumentParser add_argument() method. 1107 | 1108 | Keyword Arguments: 1109 | - mode -- A string indicating how the file is to be opened. Accepts the 1110 | same values as the builtin open() function. 1111 | - bufsize -- The file's desired buffer size. Accepts the same values as 1112 | the builtin open() function. 1113 | """ 1114 | 1115 | def __init__(self, mode='r', bufsize=-1): 1116 | self._mode = mode 1117 | self._bufsize = bufsize 1118 | 1119 | def __call__(self, string): 1120 | # the special argument "-" means sys.std{in,out} 1121 | if string == '-': 1122 | if 'r' in self._mode: 1123 | return _sys.stdin 1124 | elif 'w' in self._mode: 1125 | return _sys.stdout 1126 | else: 1127 | msg = _('argument "-" with mode %r') % self._mode 1128 | raise ValueError(msg) 1129 | 1130 | # all other arguments are used as file names 1131 | try: 1132 | return open(string, self._mode, self._bufsize) 1133 | except IOError as e: 1134 | message = _("can't open '%s': %s") 1135 | raise ArgumentTypeError(message % (string, e)) 1136 | 1137 | def __repr__(self): 1138 | args = self._mode, self._bufsize 1139 | args_str = ', '.join(repr(arg) for arg in args if arg != -1) 1140 | return '%s(%s)' % (type(self).__name__, args_str) 1141 | 1142 | # =========================== 1143 | # Optional and Positional Parsing 1144 | # =========================== 1145 | 1146 | class Namespace(_AttributeHolder): 1147 | """Simple object for storing attributes. 1148 | 1149 | Implements equality by attribute names and values, and provides a simple 1150 | string representation. 1151 | """ 1152 | 1153 | def __init__(self, **kwargs): 1154 | for name in kwargs: 1155 | setattr(self, name, kwargs[name]) 1156 | 1157 | __hash__ = None 1158 | 1159 | def __eq__(self, other): 1160 | return vars(self) == vars(other) 1161 | 1162 | def __ne__(self, other): 1163 | return not (self == other) 1164 | 1165 | def __contains__(self, key): 1166 | return key in self.__dict__ 1167 | 1168 | 1169 | class _ActionsContainer(object): 1170 | 1171 | def __init__(self, 1172 | description, 1173 | prefix_chars, 1174 | argument_default, 1175 | conflict_handler): 1176 | super(_ActionsContainer, self).__init__() 1177 | 1178 | self.description = description 1179 | self.argument_default = argument_default 1180 | self.prefix_chars = prefix_chars 1181 | self.conflict_handler = conflict_handler 1182 | 1183 | # set up registries 1184 | self._registries = {} 1185 | 1186 | # register actions 1187 | self.register('action', None, _StoreAction) 1188 | self.register('action', 'store', _StoreAction) 1189 | self.register('action', 'store_const', _StoreConstAction) 1190 | self.register('action', 'store_true', _StoreTrueAction) 1191 | self.register('action', 'store_false', _StoreFalseAction) 1192 | self.register('action', 'append', _AppendAction) 1193 | self.register('action', 'append_const', _AppendConstAction) 1194 | self.register('action', 'count', _CountAction) 1195 | self.register('action', 'help', _HelpAction) 1196 | self.register('action', 'version', _VersionAction) 1197 | self.register('action', 'parsers', _SubParsersAction) 1198 | 1199 | # raise an exception if the conflict handler is invalid 1200 | self._get_handler() 1201 | 1202 | # action storage 1203 | self._actions = [] 1204 | self._option_string_actions = {} 1205 | 1206 | # groups 1207 | self._action_groups = [] 1208 | self._mutually_exclusive_groups = [] 1209 | 1210 | # defaults storage 1211 | self._defaults = {} 1212 | 1213 | # determines whether an "option" looks like a negative number 1214 | self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$') 1215 | 1216 | # whether or not there are any optionals that look like negative 1217 | # numbers -- uses a list so it can be shared and edited 1218 | self._has_negative_number_optionals = [] 1219 | 1220 | # ==================== 1221 | # Registration methods 1222 | # ==================== 1223 | def register(self, registry_name, value, object): 1224 | registry = self._registries.setdefault(registry_name, {}) 1225 | registry[value] = object 1226 | 1227 | def _registry_get(self, registry_name, value, default=None): 1228 | return self._registries[registry_name].get(value, default) 1229 | 1230 | # ================================== 1231 | # Namespace default accessor methods 1232 | # ================================== 1233 | def set_defaults(self, **kwargs): 1234 | self._defaults.update(kwargs) 1235 | 1236 | # if these defaults match any existing arguments, replace 1237 | # the previous default on the object with the new one 1238 | for action in self._actions: 1239 | if action.dest in kwargs: 1240 | action.default = kwargs[action.dest] 1241 | 1242 | def get_default(self, dest): 1243 | for action in self._actions: 1244 | if action.dest == dest and action.default is not None: 1245 | return action.default 1246 | return self._defaults.get(dest, None) 1247 | 1248 | 1249 | # ======================= 1250 | # Adding argument actions 1251 | # ======================= 1252 | def add_argument(self, *args, **kwargs): 1253 | """ 1254 | add_argument(dest, ..., name=value, ...) 1255 | add_argument(option_string, option_string, ..., name=value, ...) 1256 | """ 1257 | 1258 | # if no positional args are supplied or only one is supplied and 1259 | # it doesn't look like an option string, parse a positional 1260 | # argument 1261 | chars = self.prefix_chars 1262 | if not args or len(args) == 1 and args[0][0] not in chars: 1263 | if args and 'dest' in kwargs: 1264 | raise ValueError('dest supplied twice for positional argument') 1265 | kwargs = self._get_positional_kwargs(*args, **kwargs) 1266 | 1267 | # otherwise, we're adding an optional argument 1268 | else: 1269 | kwargs = self._get_optional_kwargs(*args, **kwargs) 1270 | 1271 | # if no default was supplied, use the parser-level default 1272 | if 'default' not in kwargs: 1273 | dest = kwargs['dest'] 1274 | if dest in self._defaults: 1275 | kwargs['default'] = self._defaults[dest] 1276 | elif self.argument_default is not None: 1277 | kwargs['default'] = self.argument_default 1278 | 1279 | # create the action object, and add it to the parser 1280 | action_class = self._pop_action_class(kwargs) 1281 | if not _callable(action_class): 1282 | raise ValueError('unknown action "%s"' % (action_class,)) 1283 | action = action_class(**kwargs) 1284 | 1285 | # raise an error if the action type is not callable 1286 | type_func = self._registry_get('type', action.type, action.type) 1287 | if not _callable(type_func): 1288 | raise ValueError('%r is not callable' % (type_func,)) 1289 | 1290 | # raise an error if the metavar does not match the type 1291 | if hasattr(self, "_get_formatter"): 1292 | try: 1293 | self._get_formatter()._format_args(action, None) 1294 | except TypeError: 1295 | raise ValueError("length of metavar tuple does not match nargs") 1296 | 1297 | return self._add_action(action) 1298 | 1299 | def add_argument_group(self, *args, **kwargs): 1300 | group = _ArgumentGroup(self, *args, **kwargs) 1301 | self._action_groups.append(group) 1302 | return group 1303 | 1304 | def add_mutually_exclusive_group(self, **kwargs): 1305 | group = _MutuallyExclusiveGroup(self, **kwargs) 1306 | self._mutually_exclusive_groups.append(group) 1307 | return group 1308 | 1309 | def _add_action(self, action): 1310 | # resolve any conflicts 1311 | self._check_conflict(action) 1312 | 1313 | # add to actions list 1314 | self._actions.append(action) 1315 | action.container = self 1316 | 1317 | # index the action by any option strings it has 1318 | for option_string in action.option_strings: 1319 | self._option_string_actions[option_string] = action 1320 | 1321 | # set the flag if any option strings look like negative numbers 1322 | for option_string in action.option_strings: 1323 | if self._negative_number_matcher.match(option_string): 1324 | if not self._has_negative_number_optionals: 1325 | self._has_negative_number_optionals.append(True) 1326 | 1327 | # return the created action 1328 | return action 1329 | 1330 | def _remove_action(self, action): 1331 | self._actions.remove(action) 1332 | 1333 | def _add_container_actions(self, container): 1334 | # collect groups by titles 1335 | title_group_map = {} 1336 | for group in self._action_groups: 1337 | if group.title in title_group_map: 1338 | msg = _('cannot merge actions - two groups are named %r') 1339 | raise ValueError(msg % (group.title)) 1340 | title_group_map[group.title] = group 1341 | 1342 | # map each action to its group 1343 | group_map = {} 1344 | for group in container._action_groups: 1345 | 1346 | # if a group with the title exists, use that, otherwise 1347 | # create a new group matching the container's group 1348 | if group.title not in title_group_map: 1349 | title_group_map[group.title] = self.add_argument_group( 1350 | title=group.title, 1351 | description=group.description, 1352 | conflict_handler=group.conflict_handler) 1353 | 1354 | # map the actions to their new group 1355 | for action in group._group_actions: 1356 | group_map[action] = title_group_map[group.title] 1357 | 1358 | # add container's mutually exclusive groups 1359 | # NOTE: if add_mutually_exclusive_group ever gains title= and 1360 | # description= then this code will need to be expanded as above 1361 | for group in container._mutually_exclusive_groups: 1362 | mutex_group = self.add_mutually_exclusive_group( 1363 | required=group.required) 1364 | 1365 | # map the actions to their new mutex group 1366 | for action in group._group_actions: 1367 | group_map[action] = mutex_group 1368 | 1369 | # add all actions to this container or their group 1370 | for action in container._actions: 1371 | group_map.get(action, self)._add_action(action) 1372 | 1373 | def _get_positional_kwargs(self, dest, **kwargs): 1374 | # make sure required is not specified 1375 | if 'required' in kwargs: 1376 | msg = _("'required' is an invalid argument for positionals") 1377 | raise TypeError(msg) 1378 | 1379 | # mark positional arguments as required if at least one is 1380 | # always required 1381 | if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]: 1382 | kwargs['required'] = True 1383 | if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs: 1384 | kwargs['required'] = True 1385 | 1386 | # return the keyword arguments with no option strings 1387 | return dict(kwargs, dest=dest, option_strings=[]) 1388 | 1389 | def _get_optional_kwargs(self, *args, **kwargs): 1390 | # determine short and long option strings 1391 | option_strings = [] 1392 | long_option_strings = [] 1393 | for option_string in args: 1394 | # error on strings that don't start with an appropriate prefix 1395 | if not option_string[0] in self.prefix_chars: 1396 | msg = _('invalid option string %r: ' 1397 | 'must start with a character %r') 1398 | tup = option_string, self.prefix_chars 1399 | raise ValueError(msg % tup) 1400 | 1401 | # strings starting with two prefix characters are long options 1402 | option_strings.append(option_string) 1403 | if option_string[0] in self.prefix_chars: 1404 | if len(option_string) > 1: 1405 | if option_string[1] in self.prefix_chars: 1406 | long_option_strings.append(option_string) 1407 | 1408 | # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' 1409 | dest = kwargs.pop('dest', None) 1410 | if dest is None: 1411 | if long_option_strings: 1412 | dest_option_string = long_option_strings[0] 1413 | else: 1414 | dest_option_string = option_strings[0] 1415 | dest = dest_option_string.lstrip(self.prefix_chars) 1416 | if not dest: 1417 | msg = _('dest= is required for options like %r') 1418 | raise ValueError(msg % option_string) 1419 | dest = dest.replace('-', '_') 1420 | 1421 | # return the updated keyword arguments 1422 | return dict(kwargs, dest=dest, option_strings=option_strings) 1423 | 1424 | def _pop_action_class(self, kwargs, default=None): 1425 | action = kwargs.pop('action', default) 1426 | return self._registry_get('action', action, action) 1427 | 1428 | def _get_handler(self): 1429 | # determine function from conflict handler string 1430 | handler_func_name = '_handle_conflict_%s' % self.conflict_handler 1431 | try: 1432 | return getattr(self, handler_func_name) 1433 | except AttributeError: 1434 | msg = _('invalid conflict_resolution value: %r') 1435 | raise ValueError(msg % self.conflict_handler) 1436 | 1437 | def _check_conflict(self, action): 1438 | 1439 | # find all options that conflict with this option 1440 | confl_optionals = [] 1441 | for option_string in action.option_strings: 1442 | if option_string in self._option_string_actions: 1443 | confl_optional = self._option_string_actions[option_string] 1444 | confl_optionals.append((option_string, confl_optional)) 1445 | 1446 | # resolve any conflicts 1447 | if confl_optionals: 1448 | conflict_handler = self._get_handler() 1449 | conflict_handler(action, confl_optionals) 1450 | 1451 | def _handle_conflict_error(self, action, conflicting_actions): 1452 | message = _('conflicting option string(s): %s') 1453 | conflict_string = ', '.join([option_string 1454 | for option_string, action 1455 | in conflicting_actions]) 1456 | raise ArgumentError(action, message % conflict_string) 1457 | 1458 | def _handle_conflict_resolve(self, action, conflicting_actions): 1459 | 1460 | # remove all conflicting options 1461 | for option_string, action in conflicting_actions: 1462 | 1463 | # remove the conflicting option 1464 | action.option_strings.remove(option_string) 1465 | self._option_string_actions.pop(option_string, None) 1466 | 1467 | # if the option now has no option string, remove it from the 1468 | # container holding it 1469 | if not action.option_strings: 1470 | action.container._remove_action(action) 1471 | 1472 | 1473 | class _ArgumentGroup(_ActionsContainer): 1474 | 1475 | def __init__(self, container, title=None, description=None, **kwargs): 1476 | # add any missing keyword arguments by checking the container 1477 | update = kwargs.setdefault 1478 | update('conflict_handler', container.conflict_handler) 1479 | update('prefix_chars', container.prefix_chars) 1480 | update('argument_default', container.argument_default) 1481 | super_init = super(_ArgumentGroup, self).__init__ 1482 | super_init(description=description, **kwargs) 1483 | 1484 | # group attributes 1485 | self.title = title 1486 | self._group_actions = [] 1487 | 1488 | # share most attributes with the container 1489 | self._registries = container._registries 1490 | self._actions = container._actions 1491 | self._option_string_actions = container._option_string_actions 1492 | self._defaults = container._defaults 1493 | self._has_negative_number_optionals = \ 1494 | container._has_negative_number_optionals 1495 | self._mutually_exclusive_groups = container._mutually_exclusive_groups 1496 | 1497 | def _add_action(self, action): 1498 | action = super(_ArgumentGroup, self)._add_action(action) 1499 | self._group_actions.append(action) 1500 | return action 1501 | 1502 | def _remove_action(self, action): 1503 | super(_ArgumentGroup, self)._remove_action(action) 1504 | self._group_actions.remove(action) 1505 | 1506 | 1507 | class _MutuallyExclusiveGroup(_ArgumentGroup): 1508 | 1509 | def __init__(self, container, required=False): 1510 | super(_MutuallyExclusiveGroup, self).__init__(container) 1511 | self.required = required 1512 | self._container = container 1513 | 1514 | def _add_action(self, action): 1515 | if action.required: 1516 | msg = _('mutually exclusive arguments must be optional') 1517 | raise ValueError(msg) 1518 | action = self._container._add_action(action) 1519 | self._group_actions.append(action) 1520 | return action 1521 | 1522 | def _remove_action(self, action): 1523 | self._container._remove_action(action) 1524 | self._group_actions.remove(action) 1525 | 1526 | 1527 | class ArgumentParser(_AttributeHolder, _ActionsContainer): 1528 | """Object for parsing command line strings into Python objects. 1529 | 1530 | Keyword Arguments: 1531 | - prog -- The name of the program (default: sys.argv[0]) 1532 | - usage -- A usage message (default: auto-generated from arguments) 1533 | - description -- A description of what the program does 1534 | - epilog -- Text following the argument descriptions 1535 | - parents -- Parsers whose arguments should be copied into this one 1536 | - formatter_class -- HelpFormatter class for printing help messages 1537 | - prefix_chars -- Characters that prefix optional arguments 1538 | - fromfile_prefix_chars -- Characters that prefix files containing 1539 | additional arguments 1540 | - argument_default -- The default value for all arguments 1541 | - conflict_handler -- String indicating how to handle conflicts 1542 | - add_help -- Add a -h/-help option 1543 | """ 1544 | 1545 | def __init__(self, 1546 | prog=None, 1547 | usage=None, 1548 | description=None, 1549 | epilog=None, 1550 | version=None, 1551 | parents=[], 1552 | formatter_class=HelpFormatter, 1553 | prefix_chars='-', 1554 | fromfile_prefix_chars=None, 1555 | argument_default=None, 1556 | conflict_handler='error', 1557 | add_help=True): 1558 | 1559 | if version is not None: 1560 | import warnings 1561 | warnings.warn( 1562 | """The "version" argument to ArgumentParser is deprecated. """ 1563 | """Please use """ 1564 | """"add_argument(..., action='version', version="N", ...)" """ 1565 | """instead""", DeprecationWarning) 1566 | 1567 | superinit = super(ArgumentParser, self).__init__ 1568 | superinit(description=description, 1569 | prefix_chars=prefix_chars, 1570 | argument_default=argument_default, 1571 | conflict_handler=conflict_handler) 1572 | 1573 | # default setting for prog 1574 | if prog is None: 1575 | prog = _os.path.basename(_sys.argv[0]) 1576 | 1577 | self.prog = prog 1578 | self.usage = usage 1579 | self.epilog = epilog 1580 | self.version = version 1581 | self.formatter_class = formatter_class 1582 | self.fromfile_prefix_chars = fromfile_prefix_chars 1583 | self.add_help = add_help 1584 | 1585 | add_group = self.add_argument_group 1586 | self._positionals = add_group(_('positional arguments')) 1587 | self._optionals = add_group(_('optional arguments')) 1588 | self._subparsers = None 1589 | 1590 | # register types 1591 | def identity(string): 1592 | return string 1593 | self.register('type', None, identity) 1594 | 1595 | # add help and version arguments if necessary 1596 | # (using explicit default to override global argument_default) 1597 | default_prefix = '-' if '-' in prefix_chars else prefix_chars[0] 1598 | if self.add_help: 1599 | self.add_argument( 1600 | default_prefix+'h', default_prefix*2+'help', 1601 | action='help', default=SUPPRESS, 1602 | help=_('show this help message and exit')) 1603 | if self.version: 1604 | self.add_argument( 1605 | default_prefix+'v', default_prefix*2+'version', 1606 | action='version', default=SUPPRESS, 1607 | version=self.version, 1608 | help=_("show program's version number and exit")) 1609 | 1610 | # add parent arguments and defaults 1611 | for parent in parents: 1612 | self._add_container_actions(parent) 1613 | try: 1614 | defaults = parent._defaults 1615 | except AttributeError: 1616 | pass 1617 | else: 1618 | self._defaults.update(defaults) 1619 | 1620 | # ======================= 1621 | # Pretty __repr__ methods 1622 | # ======================= 1623 | def _get_kwargs(self): 1624 | names = [ 1625 | 'prog', 1626 | 'usage', 1627 | 'description', 1628 | 'version', 1629 | 'formatter_class', 1630 | 'conflict_handler', 1631 | 'add_help', 1632 | ] 1633 | return [(name, getattr(self, name)) for name in names] 1634 | 1635 | # ================================== 1636 | # Optional/Positional adding methods 1637 | # ================================== 1638 | def add_subparsers(self, **kwargs): 1639 | if self._subparsers is not None: 1640 | self.error(_('cannot have multiple subparser arguments')) 1641 | 1642 | # add the parser class to the arguments if it's not present 1643 | kwargs.setdefault('parser_class', type(self)) 1644 | 1645 | if 'title' in kwargs or 'description' in kwargs: 1646 | title = _(kwargs.pop('title', 'subcommands')) 1647 | description = _(kwargs.pop('description', None)) 1648 | self._subparsers = self.add_argument_group(title, description) 1649 | else: 1650 | self._subparsers = self._positionals 1651 | 1652 | # prog defaults to the usage message of this parser, skipping 1653 | # optional arguments and with no "usage:" prefix 1654 | if kwargs.get('prog') is None: 1655 | formatter = self._get_formatter() 1656 | positionals = self._get_positional_actions() 1657 | groups = self._mutually_exclusive_groups 1658 | formatter.add_usage(self.usage, positionals, groups, '') 1659 | kwargs['prog'] = formatter.format_help().strip() 1660 | 1661 | # create the parsers action and add it to the positionals list 1662 | parsers_class = self._pop_action_class(kwargs, 'parsers') 1663 | action = parsers_class(option_strings=[], **kwargs) 1664 | self._subparsers._add_action(action) 1665 | 1666 | # return the created parsers action 1667 | return action 1668 | 1669 | def _add_action(self, action): 1670 | if action.option_strings: 1671 | self._optionals._add_action(action) 1672 | else: 1673 | self._positionals._add_action(action) 1674 | return action 1675 | 1676 | def _get_optional_actions(self): 1677 | return [action 1678 | for action in self._actions 1679 | if action.option_strings] 1680 | 1681 | def _get_positional_actions(self): 1682 | return [action 1683 | for action in self._actions 1684 | if not action.option_strings] 1685 | 1686 | # ===================================== 1687 | # Command line argument parsing methods 1688 | # ===================================== 1689 | def parse_args(self, args=None, namespace=None): 1690 | args, argv = self.parse_known_args(args, namespace) 1691 | if argv: 1692 | msg = _('unrecognized arguments: %s') 1693 | self.error(msg % ' '.join(argv)) 1694 | return args 1695 | 1696 | def parse_known_args(self, args=None, namespace=None): 1697 | if args is None: 1698 | # args default to the system args 1699 | args = _sys.argv[1:] 1700 | else: 1701 | # make sure that args are mutable 1702 | args = list(args) 1703 | 1704 | # default Namespace built from parser defaults 1705 | if namespace is None: 1706 | namespace = Namespace() 1707 | 1708 | # add any action defaults that aren't present 1709 | for action in self._actions: 1710 | if action.dest is not SUPPRESS: 1711 | if not hasattr(namespace, action.dest): 1712 | if action.default is not SUPPRESS: 1713 | setattr(namespace, action.dest, action.default) 1714 | 1715 | # add any parser defaults that aren't present 1716 | for dest in self._defaults: 1717 | if not hasattr(namespace, dest): 1718 | setattr(namespace, dest, self._defaults[dest]) 1719 | 1720 | # parse the arguments and exit if there are any errors 1721 | try: 1722 | namespace, args = self._parse_known_args(args, namespace) 1723 | if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR): 1724 | args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) 1725 | delattr(namespace, _UNRECOGNIZED_ARGS_ATTR) 1726 | return namespace, args 1727 | except ArgumentError: 1728 | err = _sys.exc_info()[1] 1729 | self.error(str(err)) 1730 | 1731 | def _parse_known_args(self, arg_strings, namespace): 1732 | # replace arg strings that are file references 1733 | if self.fromfile_prefix_chars is not None: 1734 | arg_strings = self._read_args_from_files(arg_strings) 1735 | 1736 | # map all mutually exclusive arguments to the other arguments 1737 | # they can't occur with 1738 | action_conflicts = {} 1739 | for mutex_group in self._mutually_exclusive_groups: 1740 | group_actions = mutex_group._group_actions 1741 | for i, mutex_action in enumerate(mutex_group._group_actions): 1742 | conflicts = action_conflicts.setdefault(mutex_action, []) 1743 | conflicts.extend(group_actions[:i]) 1744 | conflicts.extend(group_actions[i + 1:]) 1745 | 1746 | # find all option indices, and determine the arg_string_pattern 1747 | # which has an 'O' if there is an option at an index, 1748 | # an 'A' if there is an argument, or a '-' if there is a '--' 1749 | option_string_indices = {} 1750 | arg_string_pattern_parts = [] 1751 | arg_strings_iter = iter(arg_strings) 1752 | for i, arg_string in enumerate(arg_strings_iter): 1753 | 1754 | # all args after -- are non-options 1755 | if arg_string == '--': 1756 | arg_string_pattern_parts.append('-') 1757 | for arg_string in arg_strings_iter: 1758 | arg_string_pattern_parts.append('A') 1759 | 1760 | # otherwise, add the arg to the arg strings 1761 | # and note the index if it was an option 1762 | else: 1763 | option_tuple = self._parse_optional(arg_string) 1764 | if option_tuple is None: 1765 | pattern = 'A' 1766 | else: 1767 | option_string_indices[i] = option_tuple 1768 | pattern = 'O' 1769 | arg_string_pattern_parts.append(pattern) 1770 | 1771 | # join the pieces together to form the pattern 1772 | arg_strings_pattern = ''.join(arg_string_pattern_parts) 1773 | 1774 | # converts arg strings to the appropriate and then takes the action 1775 | seen_actions = set() 1776 | seen_non_default_actions = set() 1777 | 1778 | def take_action(action, argument_strings, option_string=None): 1779 | seen_actions.add(action) 1780 | argument_values = self._get_values(action, argument_strings) 1781 | 1782 | # error if this argument is not allowed with other previously 1783 | # seen arguments, assuming that actions that use the default 1784 | # value don't really count as "present" 1785 | if argument_values is not action.default: 1786 | seen_non_default_actions.add(action) 1787 | for conflict_action in action_conflicts.get(action, []): 1788 | if conflict_action in seen_non_default_actions: 1789 | msg = _('not allowed with argument %s') 1790 | action_name = _get_action_name(conflict_action) 1791 | raise ArgumentError(action, msg % action_name) 1792 | 1793 | # take the action if we didn't receive a SUPPRESS value 1794 | # (e.g. from a default) 1795 | if argument_values is not SUPPRESS: 1796 | action(self, namespace, argument_values, option_string) 1797 | 1798 | # function to convert arg_strings into an optional action 1799 | def consume_optional(start_index): 1800 | 1801 | # get the optional identified at this index 1802 | option_tuple = option_string_indices[start_index] 1803 | action, option_string, explicit_arg = option_tuple 1804 | 1805 | # identify additional optionals in the same arg string 1806 | # (e.g. -xyz is the same as -x -y -z if no args are required) 1807 | match_argument = self._match_argument 1808 | action_tuples = [] 1809 | while True: 1810 | 1811 | # if we found no optional action, skip it 1812 | if action is None: 1813 | extras.append(arg_strings[start_index]) 1814 | return start_index + 1 1815 | 1816 | # if there is an explicit argument, try to match the 1817 | # optional's string arguments to only this 1818 | if explicit_arg is not None: 1819 | arg_count = match_argument(action, 'A') 1820 | 1821 | # if the action is a single-dash option and takes no 1822 | # arguments, try to parse more single-dash options out 1823 | # of the tail of the option string 1824 | chars = self.prefix_chars 1825 | if arg_count == 0 and option_string[1] not in chars: 1826 | action_tuples.append((action, [], option_string)) 1827 | char = option_string[0] 1828 | option_string = char + explicit_arg[0] 1829 | new_explicit_arg = explicit_arg[1:] or None 1830 | optionals_map = self._option_string_actions 1831 | if option_string in optionals_map: 1832 | action = optionals_map[option_string] 1833 | explicit_arg = new_explicit_arg 1834 | else: 1835 | msg = _('ignored explicit argument %r') 1836 | raise ArgumentError(action, msg % explicit_arg) 1837 | 1838 | # if the action expect exactly one argument, we've 1839 | # successfully matched the option; exit the loop 1840 | elif arg_count == 1: 1841 | stop = start_index + 1 1842 | args = [explicit_arg] 1843 | action_tuples.append((action, args, option_string)) 1844 | break 1845 | 1846 | # error if a double-dash option did not use the 1847 | # explicit argument 1848 | else: 1849 | msg = _('ignored explicit argument %r') 1850 | raise ArgumentError(action, msg % explicit_arg) 1851 | 1852 | # if there is no explicit argument, try to match the 1853 | # optional's string arguments with the following strings 1854 | # if successful, exit the loop 1855 | else: 1856 | start = start_index + 1 1857 | selected_patterns = arg_strings_pattern[start:] 1858 | arg_count = match_argument(action, selected_patterns) 1859 | stop = start + arg_count 1860 | args = arg_strings[start:stop] 1861 | action_tuples.append((action, args, option_string)) 1862 | break 1863 | 1864 | # add the Optional to the list and return the index at which 1865 | # the Optional's string args stopped 1866 | assert action_tuples 1867 | for action, args, option_string in action_tuples: 1868 | take_action(action, args, option_string) 1869 | return stop 1870 | 1871 | # the list of Positionals left to be parsed; this is modified 1872 | # by consume_positionals() 1873 | positionals = self._get_positional_actions() 1874 | 1875 | # function to convert arg_strings into positional actions 1876 | def consume_positionals(start_index): 1877 | # match as many Positionals as possible 1878 | match_partial = self._match_arguments_partial 1879 | selected_pattern = arg_strings_pattern[start_index:] 1880 | arg_counts = match_partial(positionals, selected_pattern) 1881 | 1882 | # slice off the appropriate arg strings for each Positional 1883 | # and add the Positional and its args to the list 1884 | for action, arg_count in zip(positionals, arg_counts): 1885 | args = arg_strings[start_index: start_index + arg_count] 1886 | start_index += arg_count 1887 | take_action(action, args) 1888 | 1889 | # slice off the Positionals that we just parsed and return the 1890 | # index at which the Positionals' string args stopped 1891 | positionals[:] = positionals[len(arg_counts):] 1892 | return start_index 1893 | 1894 | # consume Positionals and Optionals alternately, until we have 1895 | # passed the last option string 1896 | extras = [] 1897 | start_index = 0 1898 | if option_string_indices: 1899 | max_option_string_index = max(option_string_indices) 1900 | else: 1901 | max_option_string_index = -1 1902 | while start_index <= max_option_string_index: 1903 | 1904 | # consume any Positionals preceding the next option 1905 | next_option_string_index = min([ 1906 | index 1907 | for index in option_string_indices 1908 | if index >= start_index]) 1909 | if start_index != next_option_string_index: 1910 | positionals_end_index = consume_positionals(start_index) 1911 | 1912 | # only try to parse the next optional if we didn't consume 1913 | # the option string during the positionals parsing 1914 | if positionals_end_index > start_index: 1915 | start_index = positionals_end_index 1916 | continue 1917 | else: 1918 | start_index = positionals_end_index 1919 | 1920 | # if we consumed all the positionals we could and we're not 1921 | # at the index of an option string, there were extra arguments 1922 | if start_index not in option_string_indices: 1923 | strings = arg_strings[start_index:next_option_string_index] 1924 | extras.extend(strings) 1925 | start_index = next_option_string_index 1926 | 1927 | # consume the next optional and any arguments for it 1928 | start_index = consume_optional(start_index) 1929 | 1930 | # consume any positionals following the last Optional 1931 | stop_index = consume_positionals(start_index) 1932 | 1933 | # if we didn't consume all the argument strings, there were extras 1934 | extras.extend(arg_strings[stop_index:]) 1935 | 1936 | # if we didn't use all the Positional objects, there were too few 1937 | # arg strings supplied. 1938 | if positionals: 1939 | self.error(_('too few arguments')) 1940 | 1941 | # make sure all required actions were present, and convert defaults. 1942 | for action in self._actions: 1943 | if action not in seen_actions: 1944 | if action.required: 1945 | name = _get_action_name(action) 1946 | self.error(_('argument %s is required') % name) 1947 | else: 1948 | # Convert action default now instead of doing it before 1949 | # parsing arguments to avoid calling convert functions 1950 | # twice (which may fail) if the argument was given, but 1951 | # only if it was defined already in the namespace 1952 | if (action.default is not None and 1953 | isinstance(action.default, basestring) and 1954 | hasattr(namespace, action.dest) and 1955 | action.default is getattr(namespace, action.dest)): 1956 | setattr(namespace, action.dest, 1957 | self._get_value(action, action.default)) 1958 | 1959 | # make sure all required groups had one option present 1960 | for group in self._mutually_exclusive_groups: 1961 | if group.required: 1962 | for action in group._group_actions: 1963 | if action in seen_non_default_actions: 1964 | break 1965 | 1966 | # if no actions were used, report the error 1967 | else: 1968 | names = [_get_action_name(action) 1969 | for action in group._group_actions 1970 | if action.help is not SUPPRESS] 1971 | msg = _('one of the arguments %s is required') 1972 | self.error(msg % ' '.join(names)) 1973 | 1974 | # return the updated namespace and the extra arguments 1975 | return namespace, extras 1976 | 1977 | def _read_args_from_files(self, arg_strings): 1978 | # expand arguments referencing files 1979 | new_arg_strings = [] 1980 | for arg_string in arg_strings: 1981 | 1982 | # for regular arguments, just add them back into the list 1983 | if not arg_string or arg_string[0] not in self.fromfile_prefix_chars: 1984 | new_arg_strings.append(arg_string) 1985 | 1986 | # replace arguments referencing files with the file content 1987 | else: 1988 | try: 1989 | args_file = open(arg_string[1:]) 1990 | try: 1991 | arg_strings = [] 1992 | for arg_line in args_file.read().splitlines(): 1993 | for arg in self.convert_arg_line_to_args(arg_line): 1994 | arg_strings.append(arg) 1995 | arg_strings = self._read_args_from_files(arg_strings) 1996 | new_arg_strings.extend(arg_strings) 1997 | finally: 1998 | args_file.close() 1999 | except IOError: 2000 | err = _sys.exc_info()[1] 2001 | self.error(str(err)) 2002 | 2003 | # return the modified argument list 2004 | return new_arg_strings 2005 | 2006 | def convert_arg_line_to_args(self, arg_line): 2007 | return [arg_line] 2008 | 2009 | def _match_argument(self, action, arg_strings_pattern): 2010 | # match the pattern for this action to the arg strings 2011 | nargs_pattern = self._get_nargs_pattern(action) 2012 | match = _re.match(nargs_pattern, arg_strings_pattern) 2013 | 2014 | # raise an exception if we weren't able to find a match 2015 | if match is None: 2016 | nargs_errors = { 2017 | None: _('expected one argument'), 2018 | OPTIONAL: _('expected at most one argument'), 2019 | ONE_OR_MORE: _('expected at least one argument'), 2020 | } 2021 | default = _('expected %s argument(s)') % action.nargs 2022 | msg = nargs_errors.get(action.nargs, default) 2023 | raise ArgumentError(action, msg) 2024 | 2025 | # return the number of arguments matched 2026 | return len(match.group(1)) 2027 | 2028 | def _match_arguments_partial(self, actions, arg_strings_pattern): 2029 | # progressively shorten the actions list by slicing off the 2030 | # final actions until we find a match 2031 | result = [] 2032 | for i in range(len(actions), 0, -1): 2033 | actions_slice = actions[:i] 2034 | pattern = ''.join([self._get_nargs_pattern(action) 2035 | for action in actions_slice]) 2036 | match = _re.match(pattern, arg_strings_pattern) 2037 | if match is not None: 2038 | result.extend([len(string) for string in match.groups()]) 2039 | break 2040 | 2041 | # return the list of arg string counts 2042 | return result 2043 | 2044 | def _parse_optional(self, arg_string): 2045 | # if it's an empty string, it was meant to be a positional 2046 | if not arg_string: 2047 | return None 2048 | 2049 | # if it doesn't start with a prefix, it was meant to be positional 2050 | if not arg_string[0] in self.prefix_chars: 2051 | return None 2052 | 2053 | # if the option string is present in the parser, return the action 2054 | if arg_string in self._option_string_actions: 2055 | action = self._option_string_actions[arg_string] 2056 | return action, arg_string, None 2057 | 2058 | # if it's just a single character, it was meant to be positional 2059 | if len(arg_string) == 1: 2060 | return None 2061 | 2062 | # if the option string before the "=" is present, return the action 2063 | if '=' in arg_string: 2064 | option_string, explicit_arg = arg_string.split('=', 1) 2065 | if option_string in self._option_string_actions: 2066 | action = self._option_string_actions[option_string] 2067 | return action, option_string, explicit_arg 2068 | 2069 | # search through all possible prefixes of the option string 2070 | # and all actions in the parser for possible interpretations 2071 | option_tuples = self._get_option_tuples(arg_string) 2072 | 2073 | # if multiple actions match, the option string was ambiguous 2074 | if len(option_tuples) > 1: 2075 | options = ', '.join([option_string 2076 | for action, option_string, explicit_arg in option_tuples]) 2077 | tup = arg_string, options 2078 | self.error(_('ambiguous option: %s could match %s') % tup) 2079 | 2080 | # if exactly one action matched, this segmentation is good, 2081 | # so return the parsed action 2082 | elif len(option_tuples) == 1: 2083 | option_tuple, = option_tuples 2084 | return option_tuple 2085 | 2086 | # if it was not found as an option, but it looks like a negative 2087 | # number, it was meant to be positional 2088 | # unless there are negative-number-like options 2089 | if self._negative_number_matcher.match(arg_string): 2090 | if not self._has_negative_number_optionals: 2091 | return None 2092 | 2093 | # if it contains a space, it was meant to be a positional 2094 | if ' ' in arg_string: 2095 | return None 2096 | 2097 | # it was meant to be an optional but there is no such option 2098 | # in this parser (though it might be a valid option in a subparser) 2099 | return None, arg_string, None 2100 | 2101 | def _get_option_tuples(self, option_string): 2102 | result = [] 2103 | 2104 | # option strings starting with two prefix characters are only 2105 | # split at the '=' 2106 | chars = self.prefix_chars 2107 | if option_string[0] in chars and option_string[1] in chars: 2108 | if '=' in option_string: 2109 | option_prefix, explicit_arg = option_string.split('=', 1) 2110 | else: 2111 | option_prefix = option_string 2112 | explicit_arg = None 2113 | for option_string in self._option_string_actions: 2114 | if option_string.startswith(option_prefix): 2115 | action = self._option_string_actions[option_string] 2116 | tup = action, option_string, explicit_arg 2117 | result.append(tup) 2118 | 2119 | # single character options can be concatenated with their arguments 2120 | # but multiple character options always have to have their argument 2121 | # separate 2122 | elif option_string[0] in chars and option_string[1] not in chars: 2123 | option_prefix = option_string 2124 | explicit_arg = None 2125 | short_option_prefix = option_string[:2] 2126 | short_explicit_arg = option_string[2:] 2127 | 2128 | for option_string in self._option_string_actions: 2129 | if option_string == short_option_prefix: 2130 | action = self._option_string_actions[option_string] 2131 | tup = action, option_string, short_explicit_arg 2132 | result.append(tup) 2133 | elif option_string.startswith(option_prefix): 2134 | action = self._option_string_actions[option_string] 2135 | tup = action, option_string, explicit_arg 2136 | result.append(tup) 2137 | 2138 | # shouldn't ever get here 2139 | else: 2140 | self.error(_('unexpected option string: %s') % option_string) 2141 | 2142 | # return the collected option tuples 2143 | return result 2144 | 2145 | def _get_nargs_pattern(self, action): 2146 | # in all examples below, we have to allow for '--' args 2147 | # which are represented as '-' in the pattern 2148 | nargs = action.nargs 2149 | 2150 | # the default (None) is assumed to be a single argument 2151 | if nargs is None: 2152 | nargs_pattern = '(-*A-*)' 2153 | 2154 | # allow zero or one arguments 2155 | elif nargs == OPTIONAL: 2156 | nargs_pattern = '(-*A?-*)' 2157 | 2158 | # allow zero or more arguments 2159 | elif nargs == ZERO_OR_MORE: 2160 | nargs_pattern = '(-*[A-]*)' 2161 | 2162 | # allow one or more arguments 2163 | elif nargs == ONE_OR_MORE: 2164 | nargs_pattern = '(-*A[A-]*)' 2165 | 2166 | # allow any number of options or arguments 2167 | elif nargs == REMAINDER: 2168 | nargs_pattern = '([-AO]*)' 2169 | 2170 | # allow one argument followed by any number of options or arguments 2171 | elif nargs == PARSER: 2172 | nargs_pattern = '(-*A[-AO]*)' 2173 | 2174 | # all others should be integers 2175 | else: 2176 | nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs) 2177 | 2178 | # if this is an optional action, -- is not allowed 2179 | if action.option_strings: 2180 | nargs_pattern = nargs_pattern.replace('-*', '') 2181 | nargs_pattern = nargs_pattern.replace('-', '') 2182 | 2183 | # return the pattern 2184 | return nargs_pattern 2185 | 2186 | # ======================== 2187 | # Value conversion methods 2188 | # ======================== 2189 | def _get_values(self, action, arg_strings): 2190 | # for everything but PARSER, REMAINDER args, strip out first '--' 2191 | if action.nargs not in [PARSER, REMAINDER]: 2192 | try: 2193 | arg_strings.remove('--') 2194 | except ValueError: 2195 | pass 2196 | 2197 | # optional argument produces a default when not present 2198 | if not arg_strings and action.nargs == OPTIONAL: 2199 | if action.option_strings: 2200 | value = action.const 2201 | else: 2202 | value = action.default 2203 | if isinstance(value, basestring): 2204 | value = self._get_value(action, value) 2205 | self._check_value(action, value) 2206 | 2207 | # when nargs='*' on a positional, if there were no command-line 2208 | # args, use the default if it is anything other than None 2209 | elif (not arg_strings and action.nargs == ZERO_OR_MORE and 2210 | not action.option_strings): 2211 | if action.default is not None: 2212 | value = action.default 2213 | else: 2214 | value = arg_strings 2215 | self._check_value(action, value) 2216 | 2217 | # single argument or optional argument produces a single value 2218 | elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]: 2219 | arg_string, = arg_strings 2220 | value = self._get_value(action, arg_string) 2221 | self._check_value(action, value) 2222 | 2223 | # REMAINDER arguments convert all values, checking none 2224 | elif action.nargs == REMAINDER: 2225 | value = [self._get_value(action, v) for v in arg_strings] 2226 | 2227 | # PARSER arguments convert all values, but check only the first 2228 | elif action.nargs == PARSER: 2229 | value = [self._get_value(action, v) for v in arg_strings] 2230 | self._check_value(action, value[0]) 2231 | 2232 | # all other types of nargs produce a list 2233 | else: 2234 | value = [self._get_value(action, v) for v in arg_strings] 2235 | for v in value: 2236 | self._check_value(action, v) 2237 | 2238 | # return the converted value 2239 | return value 2240 | 2241 | def _get_value(self, action, arg_string): 2242 | type_func = self._registry_get('type', action.type, action.type) 2243 | if not _callable(type_func): 2244 | msg = _('%r is not callable') 2245 | raise ArgumentError(action, msg % type_func) 2246 | 2247 | # convert the value to the appropriate type 2248 | try: 2249 | result = type_func(arg_string) 2250 | 2251 | # ArgumentTypeErrors indicate errors 2252 | except ArgumentTypeError: 2253 | name = getattr(action.type, '__name__', repr(action.type)) 2254 | msg = str(_sys.exc_info()[1]) 2255 | raise ArgumentError(action, msg) 2256 | 2257 | # TypeErrors or ValueErrors also indicate errors 2258 | except (TypeError, ValueError): 2259 | name = getattr(action.type, '__name__', repr(action.type)) 2260 | msg = _('invalid %s value: %r') 2261 | raise ArgumentError(action, msg % (name, arg_string)) 2262 | 2263 | # return the converted value 2264 | return result 2265 | 2266 | def _check_value(self, action, value): 2267 | # converted value must be one of the choices (if specified) 2268 | if action.choices is not None and value not in action.choices: 2269 | tup = value, ', '.join(map(repr, action.choices)) 2270 | msg = _('invalid choice: %r (choose from %s)') % tup 2271 | raise ArgumentError(action, msg) 2272 | 2273 | # ======================= 2274 | # Help-formatting methods 2275 | # ======================= 2276 | def format_usage(self): 2277 | formatter = self._get_formatter() 2278 | formatter.add_usage(self.usage, self._actions, 2279 | self._mutually_exclusive_groups) 2280 | return formatter.format_help() 2281 | 2282 | def format_help(self): 2283 | formatter = self._get_formatter() 2284 | 2285 | # usage 2286 | formatter.add_usage(self.usage, self._actions, 2287 | self._mutually_exclusive_groups) 2288 | 2289 | # description 2290 | formatter.add_text(self.description) 2291 | 2292 | # positionals, optionals and user-defined groups 2293 | for action_group in self._action_groups: 2294 | formatter.start_section(action_group.title) 2295 | formatter.add_text(action_group.description) 2296 | formatter.add_arguments(action_group._group_actions) 2297 | formatter.end_section() 2298 | 2299 | # epilog 2300 | formatter.add_text(self.epilog) 2301 | 2302 | # determine help from format above 2303 | return formatter.format_help() 2304 | 2305 | def format_version(self): 2306 | import warnings 2307 | warnings.warn( 2308 | 'The format_version method is deprecated -- the "version" ' 2309 | 'argument to ArgumentParser is no longer supported.', 2310 | DeprecationWarning) 2311 | formatter = self._get_formatter() 2312 | formatter.add_text(self.version) 2313 | return formatter.format_help() 2314 | 2315 | def _get_formatter(self): 2316 | return self.formatter_class(prog=self.prog) 2317 | 2318 | # ===================== 2319 | # Help-printing methods 2320 | # ===================== 2321 | def print_usage(self, file=None): 2322 | if file is None: 2323 | file = _sys.stdout 2324 | self._print_message(self.format_usage(), file) 2325 | 2326 | def print_help(self, file=None): 2327 | if file is None: 2328 | file = _sys.stdout 2329 | self._print_message(self.format_help(), file) 2330 | 2331 | def print_version(self, file=None): 2332 | import warnings 2333 | warnings.warn( 2334 | 'The print_version method is deprecated -- the "version" ' 2335 | 'argument to ArgumentParser is no longer supported.', 2336 | DeprecationWarning) 2337 | self._print_message(self.format_version(), file) 2338 | 2339 | def _print_message(self, message, file=None): 2340 | if message: 2341 | if file is None: 2342 | file = _sys.stderr 2343 | file.write(message) 2344 | 2345 | # =============== 2346 | # Exiting methods 2347 | # =============== 2348 | def exit(self, status=0, message=None): 2349 | if message: 2350 | self._print_message(message, _sys.stderr) 2351 | _sys.exit(status) 2352 | 2353 | def error(self, message): 2354 | """error(message: string) 2355 | 2356 | Prints a usage message incorporating the message to stderr and 2357 | exits. 2358 | 2359 | If you override this in a subclass, it should not return -- it 2360 | should either exit or raise an exception. 2361 | """ 2362 | self.print_usage(_sys.stderr) 2363 | self.exit(2, _('%s: error: %s\n') % (self.prog, message)) 2364 | --------------------------------------------------------------------------------