mirror of https://github.com/morpheus65535/bazarr
More work
This commit is contained in:
parent
ae6bd5269c
commit
9e0a530af6
114
bazarr/main.py
114
bazarr/main.py
|
@ -145,12 +145,6 @@ def check_credentials(user, pw):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def authorize():
|
|
||||||
if login_auth == 'form':
|
|
||||||
aaa = Cork(os.path.normpath(os.path.join(args.config_dir, 'config')))
|
|
||||||
aaa.require(fail_redirect=(base_url + 'login'))
|
|
||||||
|
|
||||||
|
|
||||||
def api_authorize():
|
def api_authorize():
|
||||||
if 'apikey' in request.GET.dict:
|
if 'apikey' in request.GET.dict:
|
||||||
if request.GET.dict['apikey'][0] == settings.auth.apikey:
|
if request.GET.dict['apikey'][0] == settings.auth.apikey:
|
||||||
|
@ -198,14 +192,14 @@ def logout():
|
||||||
# @app.route('/')
|
# @app.route('/')
|
||||||
# # @custom_auth_basic(check_credentials)
|
# # @custom_auth_basic(check_credentials)
|
||||||
# def redirect_root():
|
# def redirect_root():
|
||||||
# authorize()
|
#
|
||||||
# redirect(base_url)
|
# redirect(base_url)
|
||||||
|
|
||||||
|
|
||||||
@app.route(base_url + 'shutdown/')
|
@app.route(base_url + 'shutdown/')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def shutdown():
|
def shutdown():
|
||||||
authorize()
|
|
||||||
try:
|
try:
|
||||||
server.stop()
|
server.stop()
|
||||||
except:
|
except:
|
||||||
|
@ -225,7 +219,7 @@ def shutdown():
|
||||||
@app.route(base_url + 'restart/')
|
@app.route(base_url + 'restart/')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def restart():
|
def restart():
|
||||||
authorize()
|
|
||||||
try:
|
try:
|
||||||
server.stop()
|
server.stop()
|
||||||
except:
|
except:
|
||||||
|
@ -246,7 +240,7 @@ def restart():
|
||||||
@app.route(base_url + 'wizard/')
|
@app.route(base_url + 'wizard/')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def wizard():
|
def wizard():
|
||||||
authorize()
|
|
||||||
|
|
||||||
# Get languages list
|
# Get languages list
|
||||||
settings_languages = database.execute("SELECT * FROM table_settings_languages ORDER BY name")
|
settings_languages = database.execute("SELECT * FROM table_settings_languages ORDER BY name")
|
||||||
|
@ -261,7 +255,7 @@ def wizard():
|
||||||
@app.route(base_url + 'save_wizard', methods=['POST'])
|
@app.route(base_url + 'save_wizard', methods=['POST'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def save_wizard():
|
def save_wizard():
|
||||||
authorize()
|
|
||||||
|
|
||||||
settings_general_ip = request.form.get('settings_general_ip')
|
settings_general_ip = request.form.get('settings_general_ip')
|
||||||
settings_general_port = request.form.get('settings_general_port')
|
settings_general_port = request.form.get('settings_general_port')
|
||||||
|
@ -477,7 +471,7 @@ def save_wizard():
|
||||||
@app.route(base_url + 'emptylog')
|
@app.route(base_url + 'emptylog')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def emptylog():
|
def emptylog():
|
||||||
authorize()
|
|
||||||
ref = request.environ['HTTP_REFERER']
|
ref = request.environ['HTTP_REFERER']
|
||||||
|
|
||||||
empty_log()
|
empty_log()
|
||||||
|
@ -489,14 +483,14 @@ def emptylog():
|
||||||
@app.route(base_url + 'bazarr.log')
|
@app.route(base_url + 'bazarr.log')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def download_log():
|
def download_log():
|
||||||
authorize()
|
|
||||||
return static_file('bazarr.log', root=os.path.join(args.config_dir, 'log/'), download='bazarr.log')
|
return static_file('bazarr.log', root=os.path.join(args.config_dir, 'log/'), download='bazarr.log')
|
||||||
|
|
||||||
|
|
||||||
@app.route(base_url + 'image_proxy/<path:url>', methods=['GET'])
|
@app.route(base_url + 'image_proxy/<path:url>', methods=['GET'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def image_proxy(url):
|
def image_proxy(url):
|
||||||
authorize()
|
|
||||||
apikey = settings.sonarr.apikey
|
apikey = settings.sonarr.apikey
|
||||||
url_image = url_sonarr_short() + '/' + url + '?apikey=' + apikey
|
url_image = url_sonarr_short() + '/' + url + '?apikey=' + apikey
|
||||||
try:
|
try:
|
||||||
|
@ -514,7 +508,7 @@ def image_proxy(url):
|
||||||
@app.route(base_url + 'image_proxy_movies/<path:url>', methods=['GET'])
|
@app.route(base_url + 'image_proxy_movies/<path:url>', methods=['GET'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def image_proxy_movies(url):
|
def image_proxy_movies(url):
|
||||||
authorize()
|
|
||||||
apikey = settings.radarr.apikey
|
apikey = settings.radarr.apikey
|
||||||
try:
|
try:
|
||||||
url_image = (url_radarr_short() + '/' + url + '?apikey=' + apikey).replace('/fanart.jpg', '/banner.jpg')
|
url_image = (url_radarr_short() + '/' + url + '?apikey=' + apikey).replace('/fanart.jpg', '/banner.jpg')
|
||||||
|
@ -534,7 +528,7 @@ def image_proxy_movies(url):
|
||||||
@app.route(base_url)
|
@app.route(base_url)
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def redirect_root():
|
def redirect_root():
|
||||||
authorize()
|
|
||||||
if settings.general.getboolean('use_sonarr'):
|
if settings.general.getboolean('use_sonarr'):
|
||||||
return redirect(base_url + 'series')
|
return redirect(base_url + 'series')
|
||||||
elif settings.general.getboolean('use_radarr'):
|
elif settings.general.getboolean('use_radarr'):
|
||||||
|
@ -548,7 +542,7 @@ def redirect_root():
|
||||||
@app.route(base_url + 'series/')
|
@app.route(base_url + 'series/')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def series():
|
def series():
|
||||||
authorize()
|
|
||||||
|
|
||||||
series_count = database.execute("SELECT COUNT(*) as count FROM table_shows", only_one=True)['count']
|
series_count = database.execute("SELECT COUNT(*) as count FROM table_shows", only_one=True)['count']
|
||||||
page = request.data
|
page = request.data
|
||||||
|
@ -605,7 +599,7 @@ def series():
|
||||||
@app.route(base_url + 'serieseditor/')
|
@app.route(base_url + 'serieseditor/')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def serieseditor():
|
def serieseditor():
|
||||||
authorize()
|
|
||||||
|
|
||||||
# Get missing count
|
# Get missing count
|
||||||
missing_count = database.execute("SELECT COUNT(*) as count FROM table_shows", only_one=True)['count']
|
missing_count = database.execute("SELECT COUNT(*) as count FROM table_shows", only_one=True)['count']
|
||||||
|
@ -627,7 +621,7 @@ def serieseditor():
|
||||||
@app.route(base_url + 'search_json/<query>', methods=['GET'])
|
@app.route(base_url + 'search_json/<query>', methods=['GET'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def search_json(query):
|
def search_json(query):
|
||||||
authorize()
|
|
||||||
|
|
||||||
query = '%' + query + '%'
|
query = '%' + query + '%'
|
||||||
search_list = []
|
search_list = []
|
||||||
|
@ -655,7 +649,7 @@ def search_json(query):
|
||||||
@app.route(base_url + 'edit_series/<int:no>', methods=['POST'])
|
@app.route(base_url + 'edit_series/<int:no>', methods=['POST'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def edit_series(no):
|
def edit_series(no):
|
||||||
authorize()
|
|
||||||
ref = request.environ['HTTP_REFERER']
|
ref = request.environ['HTTP_REFERER']
|
||||||
|
|
||||||
lang = request.form.getlist('languages')
|
lang = request.form.getlist('languages')
|
||||||
|
@ -693,7 +687,7 @@ def edit_series(no):
|
||||||
@app.route(base_url + 'edit_serieseditor', methods=['POST'])
|
@app.route(base_url + 'edit_serieseditor', methods=['POST'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def edit_serieseditor():
|
def edit_serieseditor():
|
||||||
authorize()
|
|
||||||
ref = request.environ['HTTP_REFERER']
|
ref = request.environ['HTTP_REFERER']
|
||||||
|
|
||||||
series = request.form.get('series')
|
series = request.form.get('series')
|
||||||
|
@ -723,7 +717,7 @@ def edit_serieseditor():
|
||||||
@app.route(base_url + 'episodes/<int:no>', methods=['GET'])
|
@app.route(base_url + 'episodes/<int:no>', methods=['GET'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def episodes(no):
|
def episodes(no):
|
||||||
authorize()
|
|
||||||
|
|
||||||
series_details = database.execute("SELECT title, overview, poster, fanart, hearing_impaired, tvdbId, "
|
series_details = database.execute("SELECT title, overview, poster, fanart, hearing_impaired, tvdbId, "
|
||||||
"audio_language, languages, path, forced FROM table_shows WHERE "
|
"audio_language, languages, path, forced FROM table_shows WHERE "
|
||||||
|
@ -755,7 +749,7 @@ def episodes(no):
|
||||||
@app.route(base_url + 'movies')
|
@app.route(base_url + 'movies')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def movies():
|
def movies():
|
||||||
authorize()
|
|
||||||
|
|
||||||
missing_count = database.execute("SELECT COUNT(*) as count FROM table_movies", only_one=True)['count']
|
missing_count = database.execute("SELECT COUNT(*) as count FROM table_movies", only_one=True)['count']
|
||||||
page = request.data
|
page = request.data
|
||||||
|
@ -782,7 +776,7 @@ def movies():
|
||||||
@app.route(base_url + 'movieseditor')
|
@app.route(base_url + 'movieseditor')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def movieseditor():
|
def movieseditor():
|
||||||
authorize()
|
|
||||||
|
|
||||||
missing_count = database.execute("SELECT COUNT(*) as count FROM table_movies", only_one=True)['count']
|
missing_count = database.execute("SELECT COUNT(*) as count FROM table_movies", only_one=True)['count']
|
||||||
|
|
||||||
|
@ -801,7 +795,7 @@ def movieseditor():
|
||||||
@app.route(base_url + 'edit_movieseditor', methods=['POST'])
|
@app.route(base_url + 'edit_movieseditor', methods=['POST'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def edit_movieseditor():
|
def edit_movieseditor():
|
||||||
authorize()
|
|
||||||
ref = request.environ['HTTP_REFERER']
|
ref = request.environ['HTTP_REFERER']
|
||||||
|
|
||||||
movies = request.form.get('movies')
|
movies = request.form.get('movies')
|
||||||
|
@ -831,7 +825,7 @@ def edit_movieseditor():
|
||||||
@app.route(base_url + 'edit_movie/<int:no>', methods=['POST'])
|
@app.route(base_url + 'edit_movie/<int:no>', methods=['POST'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def edit_movie(no):
|
def edit_movie(no):
|
||||||
authorize()
|
|
||||||
ref = request.environ['HTTP_REFERER']
|
ref = request.environ['HTTP_REFERER']
|
||||||
|
|
||||||
lang = request.form.getlist('languages')
|
lang = request.form.getlist('languages')
|
||||||
|
@ -869,7 +863,7 @@ def edit_movie(no):
|
||||||
@app.route(base_url + 'movie/<int:no>', methods=['GET'])
|
@app.route(base_url + 'movie/<int:no>', methods=['GET'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def movie(no):
|
def movie(no):
|
||||||
authorize()
|
|
||||||
|
|
||||||
movies_details = database.execute("SELECT title, overview, poster, fanart, hearing_impaired, tmdbId, "
|
movies_details = database.execute("SELECT title, overview, poster, fanart, hearing_impaired, tmdbId, "
|
||||||
"audio_language, languages, path, subtitles, radarrId, missing_subtitles, "
|
"audio_language, languages, path, subtitles, radarrId, missing_subtitles, "
|
||||||
|
@ -890,7 +884,7 @@ def movie(no):
|
||||||
@app.route(base_url + 'scan_disk/<int:no>', methods=['GET'])
|
@app.route(base_url + 'scan_disk/<int:no>', methods=['GET'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def scan_disk(no):
|
def scan_disk(no):
|
||||||
authorize()
|
|
||||||
ref = request.environ['HTTP_REFERER']
|
ref = request.environ['HTTP_REFERER']
|
||||||
|
|
||||||
series_scan_subtitles(no)
|
series_scan_subtitles(no)
|
||||||
|
@ -901,7 +895,7 @@ def scan_disk(no):
|
||||||
@app.route(base_url + 'scan_disk_movie/<int:no>', methods=['GET'])
|
@app.route(base_url + 'scan_disk_movie/<int:no>', methods=['GET'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def scan_disk_movie(no):
|
def scan_disk_movie(no):
|
||||||
authorize()
|
|
||||||
ref = request.environ['HTTP_REFERER']
|
ref = request.environ['HTTP_REFERER']
|
||||||
|
|
||||||
movies_scan_subtitles(no)
|
movies_scan_subtitles(no)
|
||||||
|
@ -912,7 +906,7 @@ def scan_disk_movie(no):
|
||||||
@app.route(base_url + 'search_missing_subtitles/<int:no>', methods=['GET'])
|
@app.route(base_url + 'search_missing_subtitles/<int:no>', methods=['GET'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def search_missing_subtitles(no):
|
def search_missing_subtitles(no):
|
||||||
authorize()
|
|
||||||
ref = request.environ['HTTP_REFERER']
|
ref = request.environ['HTTP_REFERER']
|
||||||
|
|
||||||
add_job(series_download_subtitles, args=[no], name=('search_missing_subtitles_' + str(no)))
|
add_job(series_download_subtitles, args=[no], name=('search_missing_subtitles_' + str(no)))
|
||||||
|
@ -923,7 +917,7 @@ def search_missing_subtitles(no):
|
||||||
@app.route(base_url + 'search_missing_subtitles_movie/<int:no>', methods=['GET'])
|
@app.route(base_url + 'search_missing_subtitles_movie/<int:no>', methods=['GET'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def search_missing_subtitles_movie(no):
|
def search_missing_subtitles_movie(no):
|
||||||
authorize()
|
|
||||||
ref = request.environ['HTTP_REFERER']
|
ref = request.environ['HTTP_REFERER']
|
||||||
|
|
||||||
add_job(movies_download_subtitles, args=[no], name=('movies_download_subtitles_' + str(no)))
|
add_job(movies_download_subtitles, args=[no], name=('movies_download_subtitles_' + str(no)))
|
||||||
|
@ -934,14 +928,14 @@ def search_missing_subtitles_movie(no):
|
||||||
@app.route(base_url + 'history')
|
@app.route(base_url + 'history')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def history():
|
def history():
|
||||||
authorize()
|
|
||||||
return render_template('history', bazarr_version=bazarr_version, base_url=base_url, current_port=settings.general.port)
|
return render_template('history', bazarr_version=bazarr_version, base_url=base_url, current_port=settings.general.port)
|
||||||
|
|
||||||
|
|
||||||
@app.route(base_url + 'historyseries')
|
@app.route(base_url + 'historyseries')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def historyseries():
|
def historyseries():
|
||||||
authorize()
|
|
||||||
|
|
||||||
row_count = database.execute("SELECT COUNT(*) as count FROM table_history LEFT JOIN table_shows on "
|
row_count = database.execute("SELECT COUNT(*) as count FROM table_history LEFT JOIN table_shows on "
|
||||||
"table_history.sonarrSeriesId = table_shows.sonarrSeriesId WHERE "
|
"table_history.sonarrSeriesId = table_shows.sonarrSeriesId WHERE "
|
||||||
|
@ -1020,7 +1014,7 @@ def historyseries():
|
||||||
@app.route(base_url + 'historymovies')
|
@app.route(base_url + 'historymovies')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def historymovies():
|
def historymovies():
|
||||||
authorize()
|
|
||||||
|
|
||||||
row_count = database.execute("SELECT COUNT(*) as count FROM table_history_movie LEFT JOIN table_movies ON "
|
row_count = database.execute("SELECT COUNT(*) as count FROM table_history_movie LEFT JOIN table_movies ON "
|
||||||
"table_history_movie.radarrId=table_movies.radarrId "
|
"table_history_movie.radarrId=table_movies.radarrId "
|
||||||
|
@ -1097,14 +1091,14 @@ def historymovies():
|
||||||
@app.route(base_url + 'wanted')
|
@app.route(base_url + 'wanted')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def wanted():
|
def wanted():
|
||||||
authorize()
|
|
||||||
return render_template('wanted', bazarr_version=bazarr_version, base_url=base_url, current_port=settings.general.port)
|
return render_template('wanted', bazarr_version=bazarr_version, base_url=base_url, current_port=settings.general.port)
|
||||||
|
|
||||||
|
|
||||||
@app.route(base_url + 'wantedseries')
|
@app.route(base_url + 'wantedseries')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def wantedseries():
|
def wantedseries():
|
||||||
authorize()
|
|
||||||
|
|
||||||
if settings.sonarr.getboolean('only_monitored'):
|
if settings.sonarr.getboolean('only_monitored'):
|
||||||
monitored_only_query_string = " AND monitored='True'"
|
monitored_only_query_string = " AND monitored='True'"
|
||||||
|
@ -1138,7 +1132,7 @@ def wantedseries():
|
||||||
@app.route(base_url + 'wantedmovies')
|
@app.route(base_url + 'wantedmovies')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def wantedmovies():
|
def wantedmovies():
|
||||||
authorize()
|
|
||||||
|
|
||||||
if settings.radarr.getboolean('only_monitored'):
|
if settings.radarr.getboolean('only_monitored'):
|
||||||
monitored_only_query_string = " AND monitored='True'"
|
monitored_only_query_string = " AND monitored='True'"
|
||||||
|
@ -1169,7 +1163,7 @@ def wantedmovies():
|
||||||
@app.route(base_url + 'wanted_search_missing_subtitles')
|
@app.route(base_url + 'wanted_search_missing_subtitles')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def wanted_search_missing_subtitles_list():
|
def wanted_search_missing_subtitles_list():
|
||||||
authorize()
|
|
||||||
ref = request.environ['HTTP_REFERER']
|
ref = request.environ['HTTP_REFERER']
|
||||||
|
|
||||||
add_job(wanted_search_missing_subtitles, name='manual_wanted_search_missing_subtitles')
|
add_job(wanted_search_missing_subtitles, name='manual_wanted_search_missing_subtitles')
|
||||||
|
@ -1180,7 +1174,7 @@ def wanted_search_missing_subtitles_list():
|
||||||
@app.route(base_url + 'settings/')
|
@app.route(base_url + 'settings/')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def _settings():
|
def _settings():
|
||||||
authorize()
|
|
||||||
|
|
||||||
settings_languages = database.execute("SELECT * FROM table_settings_languages ORDER BY name")
|
settings_languages = database.execute("SELECT * FROM table_settings_languages ORDER BY name")
|
||||||
settings_providers = sorted(provider_manager.names())
|
settings_providers = sorted(provider_manager.names())
|
||||||
|
@ -1194,7 +1188,7 @@ def _settings():
|
||||||
@app.route(base_url + 'save_settings', methods=['POST'])
|
@app.route(base_url + 'save_settings', methods=['POST'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def save_settings():
|
def save_settings():
|
||||||
authorize()
|
|
||||||
ref = request.environ['HTTP_REFERER']
|
ref = request.environ['HTTP_REFERER']
|
||||||
|
|
||||||
settings_general_ip = request.form.get('settings_general_ip')
|
settings_general_ip = request.form.get('settings_general_ip')
|
||||||
|
@ -1624,7 +1618,7 @@ def save_settings():
|
||||||
@app.route(base_url + 'check_update')
|
@app.route(base_url + 'check_update')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def check_update():
|
def check_update():
|
||||||
authorize()
|
|
||||||
ref = request.environ['HTTP_REFERER']
|
ref = request.environ['HTTP_REFERER']
|
||||||
|
|
||||||
if not args.no_update:
|
if not args.no_update:
|
||||||
|
@ -1636,7 +1630,7 @@ def check_update():
|
||||||
@app.route(base_url + 'system')
|
@app.route(base_url + 'system')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def system():
|
def system():
|
||||||
authorize()
|
|
||||||
|
|
||||||
def get_time_from_interval(td_object):
|
def get_time_from_interval(td_object):
|
||||||
seconds = int(td_object.total_seconds())
|
seconds = int(td_object.total_seconds())
|
||||||
|
@ -1720,7 +1714,7 @@ def system():
|
||||||
@app.route(base_url + 'logs')
|
@app.route(base_url + 'logs')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def get_logs():
|
def get_logs():
|
||||||
authorize()
|
|
||||||
logs = []
|
logs = []
|
||||||
with open(os.path.join(args.config_dir, 'log', 'bazarr.log')) as file:
|
with open(os.path.join(args.config_dir, 'log', 'bazarr.log')) as file:
|
||||||
for line in file.readlines():
|
for line in file.readlines():
|
||||||
|
@ -1735,7 +1729,7 @@ def get_logs():
|
||||||
@app.route(base_url + 'execute/<taskid>')
|
@app.route(base_url + 'execute/<taskid>')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def execute_task(taskid):
|
def execute_task(taskid):
|
||||||
authorize()
|
|
||||||
ref = request.environ['HTTP_REFERER']
|
ref = request.environ['HTTP_REFERER']
|
||||||
|
|
||||||
execute_now(taskid)
|
execute_now(taskid)
|
||||||
|
@ -1746,7 +1740,7 @@ def execute_task(taskid):
|
||||||
@app.route(base_url + 'remove_subtitles', methods=['POST'])
|
@app.route(base_url + 'remove_subtitles', methods=['POST'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def remove_subtitles():
|
def remove_subtitles():
|
||||||
authorize()
|
|
||||||
episodePath = request.form.get('episodePath')
|
episodePath = request.form.get('episodePath')
|
||||||
language = request.form.get('language')
|
language = request.form.get('language')
|
||||||
subtitlesPath = request.form.get('subtitlesPath')
|
subtitlesPath = request.form.get('subtitlesPath')
|
||||||
|
@ -1765,7 +1759,7 @@ def remove_subtitles():
|
||||||
@app.route(base_url + 'remove_subtitles_movie', methods=['POST'])
|
@app.route(base_url + 'remove_subtitles_movie', methods=['POST'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def remove_subtitles_movie():
|
def remove_subtitles_movie():
|
||||||
authorize()
|
|
||||||
moviePath = request.form.get('moviePath')
|
moviePath = request.form.get('moviePath')
|
||||||
language = request.form.get('language')
|
language = request.form.get('language')
|
||||||
subtitlesPath = request.form.get('subtitlesPath')
|
subtitlesPath = request.form.get('subtitlesPath')
|
||||||
|
@ -1783,7 +1777,7 @@ def remove_subtitles_movie():
|
||||||
@app.route(base_url + 'get_subtitle', methods=['POST'])
|
@app.route(base_url + 'get_subtitle', methods=['POST'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def get_subtitle():
|
def get_subtitle():
|
||||||
authorize()
|
|
||||||
ref = request.environ['HTTP_REFERER']
|
ref = request.environ['HTTP_REFERER']
|
||||||
|
|
||||||
episodePath = request.form.get('episodePath')
|
episodePath = request.form.get('episodePath')
|
||||||
|
@ -1819,7 +1813,7 @@ def get_subtitle():
|
||||||
@app.route(base_url + 'manual_search', methods=['POST'])
|
@app.route(base_url + 'manual_search', methods=['POST'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def manual_search_json():
|
def manual_search_json():
|
||||||
authorize()
|
|
||||||
|
|
||||||
episodePath = request.form.get('episodePath')
|
episodePath = request.form.get('episodePath')
|
||||||
sceneName = request.form.get('sceneName')
|
sceneName = request.form.get('sceneName')
|
||||||
|
@ -1838,7 +1832,7 @@ def manual_search_json():
|
||||||
@app.route(base_url + 'manual_get_subtitle', methods=['POST'])
|
@app.route(base_url + 'manual_get_subtitle', methods=['POST'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def manual_get_subtitle():
|
def manual_get_subtitle():
|
||||||
authorize()
|
|
||||||
ref = request.environ['HTTP_REFERER']
|
ref = request.environ['HTTP_REFERER']
|
||||||
|
|
||||||
episodePath = request.form.get('episodePath')
|
episodePath = request.form.get('episodePath')
|
||||||
|
@ -1876,7 +1870,7 @@ def manual_get_subtitle():
|
||||||
@app.route(base_url + 'manual_upload_subtitle', methods=['POST'])
|
@app.route(base_url + 'manual_upload_subtitle', methods=['POST'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def perform_manual_upload_subtitle():
|
def perform_manual_upload_subtitle():
|
||||||
authorize()
|
|
||||||
ref = request.environ['HTTP_REFERER']
|
ref = request.environ['HTTP_REFERER']
|
||||||
|
|
||||||
episodePath = request.form.get('episodePath')
|
episodePath = request.form.get('episodePath')
|
||||||
|
@ -1920,7 +1914,7 @@ def perform_manual_upload_subtitle():
|
||||||
@app.route(base_url + 'get_subtitle_movie', methods=['POST'])
|
@app.route(base_url + 'get_subtitle_movie', methods=['POST'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def get_subtitle_movie():
|
def get_subtitle_movie():
|
||||||
authorize()
|
|
||||||
ref = request.environ['HTTP_REFERER']
|
ref = request.environ['HTTP_REFERER']
|
||||||
|
|
||||||
moviePath = request.form.get('moviePath')
|
moviePath = request.form.get('moviePath')
|
||||||
|
@ -1955,7 +1949,7 @@ def get_subtitle_movie():
|
||||||
@app.route(base_url + 'manual_search_movie', methods=['POST'])
|
@app.route(base_url + 'manual_search_movie', methods=['POST'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def manual_search_movie_json():
|
def manual_search_movie_json():
|
||||||
authorize()
|
|
||||||
|
|
||||||
moviePath = request.form.get('moviePath')
|
moviePath = request.form.get('moviePath')
|
||||||
sceneName = request.form.get('sceneName')
|
sceneName = request.form.get('sceneName')
|
||||||
|
@ -1974,7 +1968,7 @@ def manual_search_movie_json():
|
||||||
@app.route(base_url + 'manual_get_subtitle_movie', methods=['POST'])
|
@app.route(base_url + 'manual_get_subtitle_movie', methods=['POST'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def manual_get_subtitle_movie():
|
def manual_get_subtitle_movie():
|
||||||
authorize()
|
|
||||||
ref = request.environ['HTTP_REFERER']
|
ref = request.environ['HTTP_REFERER']
|
||||||
|
|
||||||
moviePath = request.form.get('moviePath')
|
moviePath = request.form.get('moviePath')
|
||||||
|
@ -2010,7 +2004,7 @@ def manual_get_subtitle_movie():
|
||||||
@app.route(base_url + 'manual_upload_subtitle_movie', methods=['POST'])
|
@app.route(base_url + 'manual_upload_subtitle_movie', methods=['POST'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def perform_manual_upload_subtitle_movie():
|
def perform_manual_upload_subtitle_movie():
|
||||||
authorize()
|
|
||||||
ref = request.environ['HTTP_REFERER']
|
ref = request.environ['HTTP_REFERER']
|
||||||
|
|
||||||
moviePath = request.form.get('moviePath')
|
moviePath = request.form.get('moviePath')
|
||||||
|
@ -2117,7 +2111,7 @@ def api_movies_history():
|
||||||
@app.route(base_url + 'test_url/<protocol>/<path:url>', methods=['GET'])
|
@app.route(base_url + 'test_url/<protocol>/<path:url>', methods=['GET'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def test_url(protocol, url):
|
def test_url(protocol, url):
|
||||||
authorize()
|
|
||||||
url = six.moves.urllib.parse.unquote(url)
|
url = six.moves.urllib.parse.unquote(url)
|
||||||
try:
|
try:
|
||||||
result = requests.get(protocol + "://" + url, allow_redirects=False, verify=False).json()['version']
|
result = requests.get(protocol + "://" + url, allow_redirects=False, verify=False).json()['version']
|
||||||
|
@ -2130,7 +2124,7 @@ def test_url(protocol, url):
|
||||||
@app.route(base_url + 'test_notification/<protocol>/<path:provider>', methods=['GET'])
|
@app.route(base_url + 'test_notification/<protocol>/<path:provider>', methods=['GET'])
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def test_notification(protocol, provider):
|
def test_notification(protocol, provider):
|
||||||
authorize()
|
|
||||||
provider = six.moves.urllib.parse.unquote(provider)
|
provider = six.moves.urllib.parse.unquote(provider)
|
||||||
apobj = apprise.Apprise()
|
apobj = apprise.Apprise()
|
||||||
apobj.add(protocol + "://" + provider)
|
apobj.add(protocol + "://" + provider)
|
||||||
|
@ -2144,7 +2138,7 @@ def test_notification(protocol, provider):
|
||||||
@app.route(base_url + 'notifications')
|
@app.route(base_url + 'notifications')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def notifications():
|
def notifications():
|
||||||
authorize()
|
|
||||||
if queueconfig.notifications:
|
if queueconfig.notifications:
|
||||||
return queueconfig.notifications.read()
|
return queueconfig.notifications.read()
|
||||||
else:
|
else:
|
||||||
|
@ -2154,14 +2148,14 @@ def notifications():
|
||||||
@app.route(base_url + 'running_tasks')
|
@app.route(base_url + 'running_tasks')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def running_tasks_list():
|
def running_tasks_list():
|
||||||
authorize()
|
|
||||||
return dict(tasks=running_tasks)
|
return dict(tasks=running_tasks)
|
||||||
|
|
||||||
|
|
||||||
@app.route(base_url + 'episode_history/<int:no>')
|
@app.route(base_url + 'episode_history/<int:no>')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def episode_history(no):
|
def episode_history(no):
|
||||||
authorize()
|
|
||||||
episode_history = database.execute("SELECT action, timestamp, language, provider, score FROM table_history "
|
episode_history = database.execute("SELECT action, timestamp, language, provider, score FROM table_history "
|
||||||
"WHERE sonarrEpisodeId=? ORDER BY timestamp DESC", (no,))
|
"WHERE sonarrEpisodeId=? ORDER BY timestamp DESC", (no,))
|
||||||
for item in episode_history:
|
for item in episode_history:
|
||||||
|
@ -2200,7 +2194,7 @@ def episode_history(no):
|
||||||
@app.route(base_url + 'movie_history/<int:no>')
|
@app.route(base_url + 'movie_history/<int:no>')
|
||||||
# @custom_auth_basic(check_credentials)
|
# @custom_auth_basic(check_credentials)
|
||||||
def movie_history(no):
|
def movie_history(no):
|
||||||
authorize()
|
|
||||||
movie_history = database.execute("SELECT action, timestamp, language, provider, score FROM table_history_movie "
|
movie_history = database.execute("SELECT action, timestamp, language, provider, score FROM table_history_movie "
|
||||||
"WHERE radarrId=? ORDER BY timestamp DESC", (no,))
|
"WHERE radarrId=? ORDER BY timestamp DESC", (no,))
|
||||||
for item in movie_history:
|
for item in movie_history:
|
||||||
|
|
|
@ -1,27 +1,81 @@
|
||||||
|
import copy
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .utils import echo
|
from .utils import echo
|
||||||
from .parser import split_arg_string
|
from .parser import split_arg_string
|
||||||
from .core import MultiCommand, Option
|
from .core import MultiCommand, Option, Argument
|
||||||
|
from .types import Choice
|
||||||
|
|
||||||
|
try:
|
||||||
|
from collections import abc
|
||||||
|
except ImportError:
|
||||||
|
import collections as abc
|
||||||
|
|
||||||
COMPLETION_SCRIPT = '''
|
WORDBREAK = '='
|
||||||
|
|
||||||
|
# Note, only BASH version 4.4 and later have the nosort option.
|
||||||
|
COMPLETION_SCRIPT_BASH = '''
|
||||||
%(complete_func)s() {
|
%(complete_func)s() {
|
||||||
|
local IFS=$'\n'
|
||||||
COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\
|
COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\
|
||||||
COMP_CWORD=$COMP_CWORD \\
|
COMP_CWORD=$COMP_CWORD \\
|
||||||
%(autocomplete_var)s=complete $1 ) )
|
%(autocomplete_var)s=complete $1 ) )
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
complete -F %(complete_func)s -o default %(script_names)s
|
%(complete_func)setup() {
|
||||||
|
local COMPLETION_OPTIONS=""
|
||||||
|
local BASH_VERSION_ARR=(${BASH_VERSION//./ })
|
||||||
|
# Only BASH version 4.4 and later have the nosort option.
|
||||||
|
if [ ${BASH_VERSION_ARR[0]} -gt 4 ] || ([ ${BASH_VERSION_ARR[0]} -eq 4 ] && [ ${BASH_VERSION_ARR[1]} -ge 4 ]); then
|
||||||
|
COMPLETION_OPTIONS="-o nosort"
|
||||||
|
fi
|
||||||
|
|
||||||
|
complete $COMPLETION_OPTIONS -F %(complete_func)s %(script_names)s
|
||||||
|
}
|
||||||
|
|
||||||
|
%(complete_func)setup
|
||||||
|
'''
|
||||||
|
|
||||||
|
COMPLETION_SCRIPT_ZSH = '''
|
||||||
|
%(complete_func)s() {
|
||||||
|
local -a completions
|
||||||
|
local -a completions_with_descriptions
|
||||||
|
local -a response
|
||||||
|
response=("${(@f)$( env COMP_WORDS=\"${words[*]}\" \\
|
||||||
|
COMP_CWORD=$((CURRENT-1)) \\
|
||||||
|
%(autocomplete_var)s=\"complete_zsh\" \\
|
||||||
|
%(script_names)s )}")
|
||||||
|
|
||||||
|
for key descr in ${(kv)response}; do
|
||||||
|
if [[ "$descr" == "_" ]]; then
|
||||||
|
completions+=("$key")
|
||||||
|
else
|
||||||
|
completions_with_descriptions+=("$key":"$descr")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$completions_with_descriptions" ]; then
|
||||||
|
_describe -V unsorted completions_with_descriptions -U -Q
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$completions" ]; then
|
||||||
|
compadd -U -V unsorted -Q -a completions
|
||||||
|
fi
|
||||||
|
compstate[insert]="automenu"
|
||||||
|
}
|
||||||
|
|
||||||
|
compdef %(complete_func)s %(script_names)s
|
||||||
'''
|
'''
|
||||||
|
|
||||||
_invalid_ident_char_re = re.compile(r'[^a-zA-Z0-9_]')
|
_invalid_ident_char_re = re.compile(r'[^a-zA-Z0-9_]')
|
||||||
|
|
||||||
|
|
||||||
def get_completion_script(prog_name, complete_var):
|
def get_completion_script(prog_name, complete_var, shell):
|
||||||
cf_name = _invalid_ident_char_re.sub('', prog_name.replace('-', '_'))
|
cf_name = _invalid_ident_char_re.sub('', prog_name.replace('-', '_'))
|
||||||
return (COMPLETION_SCRIPT % {
|
script = COMPLETION_SCRIPT_ZSH if shell == 'zsh' else COMPLETION_SCRIPT_BASH
|
||||||
|
return (script % {
|
||||||
'complete_func': '_%s_completion' % cf_name,
|
'complete_func': '_%s_completion' % cf_name,
|
||||||
'script_names': prog_name,
|
'script_names': prog_name,
|
||||||
'autocomplete_var': complete_var,
|
'autocomplete_var': complete_var,
|
||||||
|
@ -29,37 +83,189 @@ def get_completion_script(prog_name, complete_var):
|
||||||
|
|
||||||
|
|
||||||
def resolve_ctx(cli, prog_name, args):
|
def resolve_ctx(cli, prog_name, args):
|
||||||
|
"""
|
||||||
|
Parse into a hierarchy of contexts. Contexts are connected through the parent variable.
|
||||||
|
:param cli: command definition
|
||||||
|
:param prog_name: the program that is running
|
||||||
|
:param args: full list of args
|
||||||
|
:return: the final context/command parsed
|
||||||
|
"""
|
||||||
ctx = cli.make_context(prog_name, args, resilient_parsing=True)
|
ctx = cli.make_context(prog_name, args, resilient_parsing=True)
|
||||||
while ctx.protected_args + ctx.args and isinstance(ctx.command, MultiCommand):
|
args = ctx.protected_args + ctx.args
|
||||||
a = ctx.protected_args + ctx.args
|
while args:
|
||||||
cmd = ctx.command.get_command(ctx, a[0])
|
if isinstance(ctx.command, MultiCommand):
|
||||||
if cmd is None:
|
if not ctx.command.chain:
|
||||||
return None
|
cmd_name, cmd, args = ctx.command.resolve_command(ctx, args)
|
||||||
ctx = cmd.make_context(a[0], a[1:], parent=ctx, resilient_parsing=True)
|
if cmd is None:
|
||||||
|
return ctx
|
||||||
|
ctx = cmd.make_context(cmd_name, args, parent=ctx,
|
||||||
|
resilient_parsing=True)
|
||||||
|
args = ctx.protected_args + ctx.args
|
||||||
|
else:
|
||||||
|
# Walk chained subcommand contexts saving the last one.
|
||||||
|
while args:
|
||||||
|
cmd_name, cmd, args = ctx.command.resolve_command(ctx, args)
|
||||||
|
if cmd is None:
|
||||||
|
return ctx
|
||||||
|
sub_ctx = cmd.make_context(cmd_name, args, parent=ctx,
|
||||||
|
allow_extra_args=True,
|
||||||
|
allow_interspersed_args=False,
|
||||||
|
resilient_parsing=True)
|
||||||
|
args = sub_ctx.args
|
||||||
|
ctx = sub_ctx
|
||||||
|
args = sub_ctx.protected_args + sub_ctx.args
|
||||||
|
else:
|
||||||
|
break
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
def start_of_option(param_str):
|
||||||
|
"""
|
||||||
|
:param param_str: param_str to check
|
||||||
|
:return: whether or not this is the start of an option declaration (i.e. starts "-" or "--")
|
||||||
|
"""
|
||||||
|
return param_str and param_str[:1] == '-'
|
||||||
|
|
||||||
|
|
||||||
|
def is_incomplete_option(all_args, cmd_param):
|
||||||
|
"""
|
||||||
|
:param all_args: the full original list of args supplied
|
||||||
|
:param cmd_param: the current command paramter
|
||||||
|
:return: whether or not the last option declaration (i.e. starts "-" or "--") is incomplete and
|
||||||
|
corresponds to this cmd_param. In other words whether this cmd_param option can still accept
|
||||||
|
values
|
||||||
|
"""
|
||||||
|
if not isinstance(cmd_param, Option):
|
||||||
|
return False
|
||||||
|
if cmd_param.is_flag:
|
||||||
|
return False
|
||||||
|
last_option = None
|
||||||
|
for index, arg_str in enumerate(reversed([arg for arg in all_args if arg != WORDBREAK])):
|
||||||
|
if index + 1 > cmd_param.nargs:
|
||||||
|
break
|
||||||
|
if start_of_option(arg_str):
|
||||||
|
last_option = arg_str
|
||||||
|
|
||||||
|
return True if last_option and last_option in cmd_param.opts else False
|
||||||
|
|
||||||
|
|
||||||
|
def is_incomplete_argument(current_params, cmd_param):
|
||||||
|
"""
|
||||||
|
:param current_params: the current params and values for this argument as already entered
|
||||||
|
:param cmd_param: the current command parameter
|
||||||
|
:return: whether or not the last argument is incomplete and corresponds to this cmd_param. In
|
||||||
|
other words whether or not the this cmd_param argument can still accept values
|
||||||
|
"""
|
||||||
|
if not isinstance(cmd_param, Argument):
|
||||||
|
return False
|
||||||
|
current_param_values = current_params[cmd_param.name]
|
||||||
|
if current_param_values is None:
|
||||||
|
return True
|
||||||
|
if cmd_param.nargs == -1:
|
||||||
|
return True
|
||||||
|
if isinstance(current_param_values, abc.Iterable) \
|
||||||
|
and cmd_param.nargs > 1 and len(current_param_values) < cmd_param.nargs:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_autocompletions(ctx, args, incomplete, cmd_param):
|
||||||
|
"""
|
||||||
|
:param ctx: context associated with the parsed command
|
||||||
|
:param args: full list of args
|
||||||
|
:param incomplete: the incomplete text to autocomplete
|
||||||
|
:param cmd_param: command definition
|
||||||
|
:return: all the possible user-specified completions for the param
|
||||||
|
"""
|
||||||
|
results = []
|
||||||
|
if isinstance(cmd_param.type, Choice):
|
||||||
|
# Choices don't support descriptions.
|
||||||
|
results = [(c, None)
|
||||||
|
for c in cmd_param.type.choices if str(c).startswith(incomplete)]
|
||||||
|
elif cmd_param.autocompletion is not None:
|
||||||
|
dynamic_completions = cmd_param.autocompletion(ctx=ctx,
|
||||||
|
args=args,
|
||||||
|
incomplete=incomplete)
|
||||||
|
results = [c if isinstance(c, tuple) else (c, None)
|
||||||
|
for c in dynamic_completions]
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def get_visible_commands_starting_with(ctx, starts_with):
|
||||||
|
"""
|
||||||
|
:param ctx: context associated with the parsed command
|
||||||
|
:starts_with: string that visible commands must start with.
|
||||||
|
:return: all visible (not hidden) commands that start with starts_with.
|
||||||
|
"""
|
||||||
|
for c in ctx.command.list_commands(ctx):
|
||||||
|
if c.startswith(starts_with):
|
||||||
|
command = ctx.command.get_command(ctx, c)
|
||||||
|
if not command.hidden:
|
||||||
|
yield command
|
||||||
|
|
||||||
|
|
||||||
|
def add_subcommand_completions(ctx, incomplete, completions_out):
|
||||||
|
# Add subcommand completions.
|
||||||
|
if isinstance(ctx.command, MultiCommand):
|
||||||
|
completions_out.extend(
|
||||||
|
[(c.name, c.get_short_help_str()) for c in get_visible_commands_starting_with(ctx, incomplete)])
|
||||||
|
|
||||||
|
# Walk up the context list and add any other completion possibilities from chained commands
|
||||||
|
while ctx.parent is not None:
|
||||||
|
ctx = ctx.parent
|
||||||
|
if isinstance(ctx.command, MultiCommand) and ctx.command.chain:
|
||||||
|
remaining_commands = [c for c in get_visible_commands_starting_with(ctx, incomplete)
|
||||||
|
if c.name not in ctx.protected_args]
|
||||||
|
completions_out.extend([(c.name, c.get_short_help_str()) for c in remaining_commands])
|
||||||
|
|
||||||
|
|
||||||
def get_choices(cli, prog_name, args, incomplete):
|
def get_choices(cli, prog_name, args, incomplete):
|
||||||
|
"""
|
||||||
|
:param cli: command definition
|
||||||
|
:param prog_name: the program that is running
|
||||||
|
:param args: full list of args
|
||||||
|
:param incomplete: the incomplete text to autocomplete
|
||||||
|
:return: all the possible completions for the incomplete
|
||||||
|
"""
|
||||||
|
all_args = copy.deepcopy(args)
|
||||||
|
|
||||||
ctx = resolve_ctx(cli, prog_name, args)
|
ctx = resolve_ctx(cli, prog_name, args)
|
||||||
if ctx is None:
|
if ctx is None:
|
||||||
return
|
return []
|
||||||
|
|
||||||
choices = []
|
# In newer versions of bash long opts with '='s are partitioned, but it's easier to parse
|
||||||
if incomplete and not incomplete[:1].isalnum():
|
# without the '='
|
||||||
|
if start_of_option(incomplete) and WORDBREAK in incomplete:
|
||||||
|
partition_incomplete = incomplete.partition(WORDBREAK)
|
||||||
|
all_args.append(partition_incomplete[0])
|
||||||
|
incomplete = partition_incomplete[2]
|
||||||
|
elif incomplete == WORDBREAK:
|
||||||
|
incomplete = ''
|
||||||
|
|
||||||
|
completions = []
|
||||||
|
if start_of_option(incomplete):
|
||||||
|
# completions for partial options
|
||||||
for param in ctx.command.params:
|
for param in ctx.command.params:
|
||||||
if not isinstance(param, Option):
|
if isinstance(param, Option) and not param.hidden:
|
||||||
continue
|
param_opts = [param_opt for param_opt in param.opts +
|
||||||
choices.extend(param.opts)
|
param.secondary_opts if param_opt not in all_args or param.multiple]
|
||||||
choices.extend(param.secondary_opts)
|
completions.extend([(o, param.help) for o in param_opts if o.startswith(incomplete)])
|
||||||
elif isinstance(ctx.command, MultiCommand):
|
return completions
|
||||||
choices.extend(ctx.command.list_commands(ctx))
|
# completion for option values from user supplied values
|
||||||
|
for param in ctx.command.params:
|
||||||
|
if is_incomplete_option(all_args, param):
|
||||||
|
return get_user_autocompletions(ctx, all_args, incomplete, param)
|
||||||
|
# completion for argument values from user supplied values
|
||||||
|
for param in ctx.command.params:
|
||||||
|
if is_incomplete_argument(ctx.params, param):
|
||||||
|
return get_user_autocompletions(ctx, all_args, incomplete, param)
|
||||||
|
|
||||||
for item in choices:
|
add_subcommand_completions(ctx, incomplete, completions)
|
||||||
if item.startswith(incomplete):
|
# Sort before returning so that proper ordering can be enforced in custom types.
|
||||||
yield item
|
return sorted(completions)
|
||||||
|
|
||||||
|
|
||||||
def do_complete(cli, prog_name):
|
def do_complete(cli, prog_name, include_descriptions):
|
||||||
cwords = split_arg_string(os.environ['COMP_WORDS'])
|
cwords = split_arg_string(os.environ['COMP_WORDS'])
|
||||||
cword = int(os.environ['COMP_CWORD'])
|
cword = int(os.environ['COMP_CWORD'])
|
||||||
args = cwords[1:cword]
|
args = cwords[1:cword]
|
||||||
|
@ -69,15 +275,19 @@ def do_complete(cli, prog_name):
|
||||||
incomplete = ''
|
incomplete = ''
|
||||||
|
|
||||||
for item in get_choices(cli, prog_name, args, incomplete):
|
for item in get_choices(cli, prog_name, args, incomplete):
|
||||||
echo(item)
|
echo(item[0])
|
||||||
|
if include_descriptions:
|
||||||
|
# ZSH has trouble dealing with empty array parameters when returned from commands, so use a well defined character '_' to indicate no description is present.
|
||||||
|
echo(item[1] if item[1] else '_')
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def bashcomplete(cli, prog_name, complete_var, complete_instr):
|
def bashcomplete(cli, prog_name, complete_var, complete_instr):
|
||||||
if complete_instr == 'source':
|
if complete_instr.startswith('source'):
|
||||||
echo(get_completion_script(prog_name, complete_var))
|
shell = 'zsh' if complete_instr == 'source_zsh' else 'bash'
|
||||||
|
echo(get_completion_script(prog_name, complete_var, shell))
|
||||||
return True
|
return True
|
||||||
elif complete_instr == 'complete':
|
elif complete_instr == 'complete' or complete_instr == 'complete_zsh':
|
||||||
return do_complete(cli, prog_name)
|
return do_complete(cli, prog_name, complete_instr == 'complete_zsh')
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -7,24 +7,31 @@ from weakref import WeakKeyDictionary
|
||||||
|
|
||||||
|
|
||||||
PY2 = sys.version_info[0] == 2
|
PY2 = sys.version_info[0] == 2
|
||||||
WIN = sys.platform.startswith('win')
|
CYGWIN = sys.platform.startswith('cygwin')
|
||||||
|
# Determine local App Engine environment, per Google's own suggestion
|
||||||
|
APP_ENGINE = ('APPENGINE_RUNTIME' in os.environ and
|
||||||
|
'Development/' in os.environ['SERVER_SOFTWARE'])
|
||||||
|
WIN = sys.platform.startswith('win') and not APP_ENGINE
|
||||||
DEFAULT_COLUMNS = 80
|
DEFAULT_COLUMNS = 80
|
||||||
|
|
||||||
|
|
||||||
_ansi_re = re.compile('\033\[((?:\d|;)*)([a-zA-Z])')
|
_ansi_re = re.compile(r'\033\[((?:\d|;)*)([a-zA-Z])')
|
||||||
|
|
||||||
|
|
||||||
def get_filesystem_encoding():
|
def get_filesystem_encoding():
|
||||||
return sys.getfilesystemencoding() or sys.getdefaultencoding()
|
return sys.getfilesystemencoding() or sys.getdefaultencoding()
|
||||||
|
|
||||||
|
|
||||||
def _make_text_stream(stream, encoding, errors):
|
def _make_text_stream(stream, encoding, errors,
|
||||||
|
force_readable=False, force_writable=False):
|
||||||
if encoding is None:
|
if encoding is None:
|
||||||
encoding = get_best_encoding(stream)
|
encoding = get_best_encoding(stream)
|
||||||
if errors is None:
|
if errors is None:
|
||||||
errors = 'replace'
|
errors = 'replace'
|
||||||
return _NonClosingTextIOWrapper(stream, encoding, errors,
|
return _NonClosingTextIOWrapper(stream, encoding, errors,
|
||||||
line_buffering=True)
|
line_buffering=True,
|
||||||
|
force_readable=force_readable,
|
||||||
|
force_writable=force_writable)
|
||||||
|
|
||||||
|
|
||||||
def is_ascii_encoding(encoding):
|
def is_ascii_encoding(encoding):
|
||||||
|
@ -45,8 +52,10 @@ def get_best_encoding(stream):
|
||||||
|
|
||||||
class _NonClosingTextIOWrapper(io.TextIOWrapper):
|
class _NonClosingTextIOWrapper(io.TextIOWrapper):
|
||||||
|
|
||||||
def __init__(self, stream, encoding, errors, **extra):
|
def __init__(self, stream, encoding, errors,
|
||||||
self._stream = stream = _FixupStream(stream)
|
force_readable=False, force_writable=False, **extra):
|
||||||
|
self._stream = stream = _FixupStream(stream, force_readable,
|
||||||
|
force_writable)
|
||||||
io.TextIOWrapper.__init__(self, stream, encoding, errors, **extra)
|
io.TextIOWrapper.__init__(self, stream, encoding, errors, **extra)
|
||||||
|
|
||||||
# The io module is a place where the Python 3 text behavior
|
# The io module is a place where the Python 3 text behavior
|
||||||
|
@ -81,10 +90,16 @@ class _FixupStream(object):
|
||||||
"""The new io interface needs more from streams than streams
|
"""The new io interface needs more from streams than streams
|
||||||
traditionally implement. As such, this fix-up code is necessary in
|
traditionally implement. As such, this fix-up code is necessary in
|
||||||
some circumstances.
|
some circumstances.
|
||||||
|
|
||||||
|
The forcing of readable and writable flags are there because some tools
|
||||||
|
put badly patched objects on sys (one such offender are certain version
|
||||||
|
of jupyter notebook).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, stream):
|
def __init__(self, stream, force_readable=False, force_writable=False):
|
||||||
self._stream = stream
|
self._stream = stream
|
||||||
|
self._force_readable = force_readable
|
||||||
|
self._force_writable = force_writable
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
return getattr(self._stream, name)
|
return getattr(self._stream, name)
|
||||||
|
@ -101,6 +116,8 @@ class _FixupStream(object):
|
||||||
return self._stream.read(size)
|
return self._stream.read(size)
|
||||||
|
|
||||||
def readable(self):
|
def readable(self):
|
||||||
|
if self._force_readable:
|
||||||
|
return True
|
||||||
x = getattr(self._stream, 'readable', None)
|
x = getattr(self._stream, 'readable', None)
|
||||||
if x is not None:
|
if x is not None:
|
||||||
return x()
|
return x()
|
||||||
|
@ -111,6 +128,8 @@ class _FixupStream(object):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def writable(self):
|
def writable(self):
|
||||||
|
if self._force_writable:
|
||||||
|
return True
|
||||||
x = getattr(self._stream, 'writable', None)
|
x = getattr(self._stream, 'writable', None)
|
||||||
if x is not None:
|
if x is not None:
|
||||||
return x()
|
return x()
|
||||||
|
@ -139,6 +158,7 @@ if PY2:
|
||||||
bytes = str
|
bytes = str
|
||||||
raw_input = raw_input
|
raw_input = raw_input
|
||||||
string_types = (str, unicode)
|
string_types = (str, unicode)
|
||||||
|
int_types = (int, long)
|
||||||
iteritems = lambda x: x.iteritems()
|
iteritems = lambda x: x.iteritems()
|
||||||
range_type = xrange
|
range_type = xrange
|
||||||
|
|
||||||
|
@ -165,10 +185,13 @@ if PY2:
|
||||||
# available (which is why we use try-catch instead of the WIN variable
|
# available (which is why we use try-catch instead of the WIN variable
|
||||||
# here), such as the Google App Engine development server on Windows. In
|
# here), such as the Google App Engine development server on Windows. In
|
||||||
# those cases there is just nothing we can do.
|
# those cases there is just nothing we can do.
|
||||||
|
def set_binary_mode(f):
|
||||||
|
return f
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import msvcrt
|
import msvcrt
|
||||||
except ImportError:
|
except ImportError:
|
||||||
set_binary_mode = lambda x: x
|
pass
|
||||||
else:
|
else:
|
||||||
def set_binary_mode(f):
|
def set_binary_mode(f):
|
||||||
try:
|
try:
|
||||||
|
@ -179,6 +202,21 @@ if PY2:
|
||||||
msvcrt.setmode(fileno, os.O_BINARY)
|
msvcrt.setmode(fileno, os.O_BINARY)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
try:
|
||||||
|
import fcntl
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
def set_binary_mode(f):
|
||||||
|
try:
|
||||||
|
fileno = f.fileno()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
flags = fcntl.fcntl(fileno, fcntl.F_GETFL)
|
||||||
|
fcntl.fcntl(fileno, fcntl.F_SETFL, flags & ~os.O_NONBLOCK)
|
||||||
|
return f
|
||||||
|
|
||||||
def isidentifier(x):
|
def isidentifier(x):
|
||||||
return _identifier_re.search(x) is not None
|
return _identifier_re.search(x) is not None
|
||||||
|
|
||||||
|
@ -186,28 +224,35 @@ if PY2:
|
||||||
return set_binary_mode(sys.stdin)
|
return set_binary_mode(sys.stdin)
|
||||||
|
|
||||||
def get_binary_stdout():
|
def get_binary_stdout():
|
||||||
|
_wrap_std_stream('stdout')
|
||||||
return set_binary_mode(sys.stdout)
|
return set_binary_mode(sys.stdout)
|
||||||
|
|
||||||
def get_binary_stderr():
|
def get_binary_stderr():
|
||||||
|
_wrap_std_stream('stderr')
|
||||||
return set_binary_mode(sys.stderr)
|
return set_binary_mode(sys.stderr)
|
||||||
|
|
||||||
def get_text_stdin(encoding=None, errors=None):
|
def get_text_stdin(encoding=None, errors=None):
|
||||||
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
||||||
if rv is not None:
|
if rv is not None:
|
||||||
return rv
|
return rv
|
||||||
return _make_text_stream(sys.stdin, encoding, errors)
|
return _make_text_stream(sys.stdin, encoding, errors,
|
||||||
|
force_readable=True)
|
||||||
|
|
||||||
def get_text_stdout(encoding=None, errors=None):
|
def get_text_stdout(encoding=None, errors=None):
|
||||||
|
_wrap_std_stream('stdout')
|
||||||
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
||||||
if rv is not None:
|
if rv is not None:
|
||||||
return rv
|
return rv
|
||||||
return _make_text_stream(sys.stdout, encoding, errors)
|
return _make_text_stream(sys.stdout, encoding, errors,
|
||||||
|
force_writable=True)
|
||||||
|
|
||||||
def get_text_stderr(encoding=None, errors=None):
|
def get_text_stderr(encoding=None, errors=None):
|
||||||
|
_wrap_std_stream('stderr')
|
||||||
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
||||||
if rv is not None:
|
if rv is not None:
|
||||||
return rv
|
return rv
|
||||||
return _make_text_stream(sys.stderr, encoding, errors)
|
return _make_text_stream(sys.stderr, encoding, errors,
|
||||||
|
force_writable=True)
|
||||||
|
|
||||||
def filename_to_ui(value):
|
def filename_to_ui(value):
|
||||||
if isinstance(value, bytes):
|
if isinstance(value, bytes):
|
||||||
|
@ -218,6 +263,7 @@ else:
|
||||||
text_type = str
|
text_type = str
|
||||||
raw_input = input
|
raw_input = input
|
||||||
string_types = (str,)
|
string_types = (str,)
|
||||||
|
int_types = (int,)
|
||||||
range_type = range
|
range_type = range
|
||||||
isidentifier = lambda x: x.isidentifier()
|
isidentifier = lambda x: x.isidentifier()
|
||||||
iteritems = lambda x: iter(x.items())
|
iteritems = lambda x: iter(x.items())
|
||||||
|
@ -298,7 +344,8 @@ else:
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _force_correct_text_reader(text_reader, encoding, errors):
|
def _force_correct_text_reader(text_reader, encoding, errors,
|
||||||
|
force_readable=False):
|
||||||
if _is_binary_reader(text_reader, False):
|
if _is_binary_reader(text_reader, False):
|
||||||
binary_reader = text_reader
|
binary_reader = text_reader
|
||||||
else:
|
else:
|
||||||
|
@ -324,9 +371,11 @@ else:
|
||||||
# we're so fundamentally fucked that nothing can repair it.
|
# we're so fundamentally fucked that nothing can repair it.
|
||||||
if errors is None:
|
if errors is None:
|
||||||
errors = 'replace'
|
errors = 'replace'
|
||||||
return _make_text_stream(binary_reader, encoding, errors)
|
return _make_text_stream(binary_reader, encoding, errors,
|
||||||
|
force_readable=force_readable)
|
||||||
|
|
||||||
def _force_correct_text_writer(text_writer, encoding, errors):
|
def _force_correct_text_writer(text_writer, encoding, errors,
|
||||||
|
force_writable=False):
|
||||||
if _is_binary_writer(text_writer, False):
|
if _is_binary_writer(text_writer, False):
|
||||||
binary_writer = text_writer
|
binary_writer = text_writer
|
||||||
else:
|
else:
|
||||||
|
@ -352,7 +401,8 @@ else:
|
||||||
# we're so fundamentally fucked that nothing can repair it.
|
# we're so fundamentally fucked that nothing can repair it.
|
||||||
if errors is None:
|
if errors is None:
|
||||||
errors = 'replace'
|
errors = 'replace'
|
||||||
return _make_text_stream(binary_writer, encoding, errors)
|
return _make_text_stream(binary_writer, encoding, errors,
|
||||||
|
force_writable=force_writable)
|
||||||
|
|
||||||
def get_binary_stdin():
|
def get_binary_stdin():
|
||||||
reader = _find_binary_reader(sys.stdin)
|
reader = _find_binary_reader(sys.stdin)
|
||||||
|
@ -379,19 +429,22 @@ else:
|
||||||
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
||||||
if rv is not None:
|
if rv is not None:
|
||||||
return rv
|
return rv
|
||||||
return _force_correct_text_reader(sys.stdin, encoding, errors)
|
return _force_correct_text_reader(sys.stdin, encoding, errors,
|
||||||
|
force_readable=True)
|
||||||
|
|
||||||
def get_text_stdout(encoding=None, errors=None):
|
def get_text_stdout(encoding=None, errors=None):
|
||||||
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
||||||
if rv is not None:
|
if rv is not None:
|
||||||
return rv
|
return rv
|
||||||
return _force_correct_text_writer(sys.stdout, encoding, errors)
|
return _force_correct_text_writer(sys.stdout, encoding, errors,
|
||||||
|
force_writable=True)
|
||||||
|
|
||||||
def get_text_stderr(encoding=None, errors=None):
|
def get_text_stderr(encoding=None, errors=None):
|
||||||
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
||||||
if rv is not None:
|
if rv is not None:
|
||||||
return rv
|
return rv
|
||||||
return _force_correct_text_writer(sys.stderr, encoding, errors)
|
return _force_correct_text_writer(sys.stderr, encoding, errors,
|
||||||
|
force_writable=True)
|
||||||
|
|
||||||
def filename_to_ui(value):
|
def filename_to_ui(value):
|
||||||
if isinstance(value, bytes):
|
if isinstance(value, bytes):
|
||||||
|
@ -420,7 +473,7 @@ def open_stream(filename, mode='r', encoding=None, errors='strict',
|
||||||
# Standard streams first. These are simple because they don't need
|
# Standard streams first. These are simple because they don't need
|
||||||
# special handling for the atomic flag. It's entirely ignored.
|
# special handling for the atomic flag. It's entirely ignored.
|
||||||
if filename == '-':
|
if filename == '-':
|
||||||
if 'w' in mode:
|
if any(m in mode for m in ['w', 'a', 'x']):
|
||||||
if 'b' in mode:
|
if 'b' in mode:
|
||||||
return get_binary_stdout(), False
|
return get_binary_stdout(), False
|
||||||
return get_text_stdout(encoding=encoding, errors=errors), False
|
return get_text_stdout(encoding=encoding, errors=errors), False
|
||||||
|
@ -460,7 +513,7 @@ def open_stream(filename, mode='r', encoding=None, errors='strict',
|
||||||
else:
|
else:
|
||||||
f = os.fdopen(fd, mode)
|
f = os.fdopen(fd, mode)
|
||||||
|
|
||||||
return _AtomicFile(f, tmp_filename, filename), True
|
return _AtomicFile(f, tmp_filename, os.path.realpath(filename)), True
|
||||||
|
|
||||||
|
|
||||||
# Used in a destructor call, needs extra protection from interpreter cleanup.
|
# Used in a destructor call, needs extra protection from interpreter cleanup.
|
||||||
|
@ -533,7 +586,7 @@ if WIN:
|
||||||
# Windows has a smaller terminal
|
# Windows has a smaller terminal
|
||||||
DEFAULT_COLUMNS = 79
|
DEFAULT_COLUMNS = 79
|
||||||
|
|
||||||
from ._winconsole import _get_windows_console_stream
|
from ._winconsole import _get_windows_console_stream, _wrap_std_stream
|
||||||
|
|
||||||
def _get_argv_encoding():
|
def _get_argv_encoding():
|
||||||
import locale
|
import locale
|
||||||
|
@ -595,6 +648,7 @@ else:
|
||||||
return getattr(sys.stdin, 'encoding', None) or get_filesystem_encoding()
|
return getattr(sys.stdin, 'encoding', None) or get_filesystem_encoding()
|
||||||
|
|
||||||
_get_windows_console_stream = lambda *x: None
|
_get_windows_console_stream = lambda *x: None
|
||||||
|
_wrap_std_stream = lambda *x: None
|
||||||
|
|
||||||
|
|
||||||
def term_len(x):
|
def term_len(x):
|
||||||
|
@ -620,6 +674,7 @@ def _make_cached_stream_func(src_func, wrapper_func):
|
||||||
return rv
|
return rv
|
||||||
rv = wrapper_func()
|
rv = wrapper_func()
|
||||||
try:
|
try:
|
||||||
|
stream = src_func() # In case wrapper_func() modified the stream
|
||||||
cache[stream] = rv
|
cache[stream] = rv
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,20 +1,24 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
click._termui_impl
|
click._termui_impl
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
This module contains implementations for the termui module. To keep the
|
This module contains implementations for the termui module. To keep the
|
||||||
import time of Click down, some infrequently used functionality is placed
|
import time of Click down, some infrequently used functionality is
|
||||||
in this module and only imported as needed.
|
placed in this module and only imported as needed.
|
||||||
|
|
||||||
:copyright: (c) 2014 by Armin Ronacher.
|
:copyright: © 2014 by the Pallets team.
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE.rst for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import math
|
import math
|
||||||
|
import contextlib
|
||||||
from ._compat import _default_text_stdout, range_type, PY2, isatty, \
|
from ._compat import _default_text_stdout, range_type, PY2, isatty, \
|
||||||
open_stream, strip_ansi, term_len, get_best_encoding, WIN
|
open_stream, strip_ansi, term_len, get_best_encoding, WIN, int_types, \
|
||||||
|
CYGWIN
|
||||||
from .utils import echo
|
from .utils import echo
|
||||||
from .exceptions import ClickException
|
from .exceptions import ClickException
|
||||||
|
|
||||||
|
@ -41,7 +45,7 @@ def _length_hint(obj):
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return None
|
return None
|
||||||
if hint is NotImplemented or \
|
if hint is NotImplemented or \
|
||||||
not isinstance(hint, (int, long)) or \
|
not isinstance(hint, int_types) or \
|
||||||
hint < 0:
|
hint < 0:
|
||||||
return None
|
return None
|
||||||
return hint
|
return hint
|
||||||
|
@ -88,6 +92,7 @@ class ProgressBar(object):
|
||||||
self.current_item = None
|
self.current_item = None
|
||||||
self.is_hidden = not isatty(self.file)
|
self.is_hidden = not isatty(self.file)
|
||||||
self._last_line = None
|
self._last_line = None
|
||||||
|
self.short_limit = 0.5
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.entered = True
|
self.entered = True
|
||||||
|
@ -101,10 +106,13 @@ class ProgressBar(object):
|
||||||
if not self.entered:
|
if not self.entered:
|
||||||
raise RuntimeError('You need to use progress bars in a with block.')
|
raise RuntimeError('You need to use progress bars in a with block.')
|
||||||
self.render_progress()
|
self.render_progress()
|
||||||
return self
|
return self.generator()
|
||||||
|
|
||||||
|
def is_fast(self):
|
||||||
|
return time.time() - self.start <= self.short_limit
|
||||||
|
|
||||||
def render_finish(self):
|
def render_finish(self):
|
||||||
if self.is_hidden:
|
if self.is_hidden or self.is_fast():
|
||||||
return
|
return
|
||||||
self.file.write(AFTER_BAR)
|
self.file.write(AFTER_BAR)
|
||||||
self.file.flush()
|
self.file.flush()
|
||||||
|
@ -129,13 +137,13 @@ class ProgressBar(object):
|
||||||
|
|
||||||
def format_eta(self):
|
def format_eta(self):
|
||||||
if self.eta_known:
|
if self.eta_known:
|
||||||
t = self.eta + 1
|
t = int(self.eta)
|
||||||
seconds = t % 60
|
seconds = t % 60
|
||||||
t /= 60
|
t //= 60
|
||||||
minutes = t % 60
|
minutes = t % 60
|
||||||
t /= 60
|
t //= 60
|
||||||
hours = t % 24
|
hours = t % 24
|
||||||
t /= 24
|
t //= 24
|
||||||
if t > 0:
|
if t > 0:
|
||||||
days = t
|
days = t
|
||||||
return '%dd %02d:%02d:%02d' % (days, hours, minutes, seconds)
|
return '%dd %02d:%02d:%02d' % (days, hours, minutes, seconds)
|
||||||
|
@ -152,25 +160,27 @@ class ProgressBar(object):
|
||||||
def format_pct(self):
|
def format_pct(self):
|
||||||
return ('% 4d%%' % int(self.pct * 100))[1:]
|
return ('% 4d%%' % int(self.pct * 100))[1:]
|
||||||
|
|
||||||
def format_progress_line(self):
|
def format_bar(self):
|
||||||
show_percent = self.show_percent
|
|
||||||
|
|
||||||
info_bits = []
|
|
||||||
if self.length_known:
|
if self.length_known:
|
||||||
bar_length = int(self.pct * self.width)
|
bar_length = int(self.pct * self.width)
|
||||||
bar = self.fill_char * bar_length
|
bar = self.fill_char * bar_length
|
||||||
bar += self.empty_char * (self.width - bar_length)
|
bar += self.empty_char * (self.width - bar_length)
|
||||||
if show_percent is None:
|
elif self.finished:
|
||||||
show_percent = not self.show_pos
|
bar = self.fill_char * self.width
|
||||||
else:
|
else:
|
||||||
if self.finished:
|
bar = list(self.empty_char * (self.width or 1))
|
||||||
bar = self.fill_char * self.width
|
if self.time_per_iteration != 0:
|
||||||
else:
|
bar[int((math.cos(self.pos * self.time_per_iteration)
|
||||||
bar = list(self.empty_char * (self.width or 1))
|
/ 2.0 + 0.5) * self.width)] = self.fill_char
|
||||||
if self.time_per_iteration != 0:
|
bar = ''.join(bar)
|
||||||
bar[int((math.cos(self.pos * self.time_per_iteration)
|
return bar
|
||||||
/ 2.0 + 0.5) * self.width)] = self.fill_char
|
|
||||||
bar = ''.join(bar)
|
def format_progress_line(self):
|
||||||
|
show_percent = self.show_percent
|
||||||
|
|
||||||
|
info_bits = []
|
||||||
|
if self.length_known and show_percent is None:
|
||||||
|
show_percent = not self.show_pos
|
||||||
|
|
||||||
if self.show_pos:
|
if self.show_pos:
|
||||||
info_bits.append(self.format_pos())
|
info_bits.append(self.format_pos())
|
||||||
|
@ -185,49 +195,47 @@ class ProgressBar(object):
|
||||||
|
|
||||||
return (self.bar_template % {
|
return (self.bar_template % {
|
||||||
'label': self.label,
|
'label': self.label,
|
||||||
'bar': bar,
|
'bar': self.format_bar(),
|
||||||
'info': self.info_sep.join(info_bits)
|
'info': self.info_sep.join(info_bits)
|
||||||
}).rstrip()
|
}).rstrip()
|
||||||
|
|
||||||
def render_progress(self):
|
def render_progress(self):
|
||||||
from .termui import get_terminal_size
|
from .termui import get_terminal_size
|
||||||
nl = False
|
|
||||||
|
|
||||||
if self.is_hidden:
|
if self.is_hidden:
|
||||||
buf = [self.label]
|
return
|
||||||
nl = True
|
|
||||||
else:
|
|
||||||
buf = []
|
|
||||||
# Update width in case the terminal has been resized
|
|
||||||
if self.autowidth:
|
|
||||||
old_width = self.width
|
|
||||||
self.width = 0
|
|
||||||
clutter_length = term_len(self.format_progress_line())
|
|
||||||
new_width = max(0, get_terminal_size()[0] - clutter_length)
|
|
||||||
if new_width < old_width:
|
|
||||||
buf.append(BEFORE_BAR)
|
|
||||||
buf.append(' ' * self.max_width)
|
|
||||||
self.max_width = new_width
|
|
||||||
self.width = new_width
|
|
||||||
|
|
||||||
clear_width = self.width
|
buf = []
|
||||||
if self.max_width is not None:
|
# Update width in case the terminal has been resized
|
||||||
clear_width = self.max_width
|
if self.autowidth:
|
||||||
|
old_width = self.width
|
||||||
|
self.width = 0
|
||||||
|
clutter_length = term_len(self.format_progress_line())
|
||||||
|
new_width = max(0, get_terminal_size()[0] - clutter_length)
|
||||||
|
if new_width < old_width:
|
||||||
|
buf.append(BEFORE_BAR)
|
||||||
|
buf.append(' ' * self.max_width)
|
||||||
|
self.max_width = new_width
|
||||||
|
self.width = new_width
|
||||||
|
|
||||||
buf.append(BEFORE_BAR)
|
clear_width = self.width
|
||||||
line = self.format_progress_line()
|
if self.max_width is not None:
|
||||||
line_len = term_len(line)
|
clear_width = self.max_width
|
||||||
if self.max_width is None or self.max_width < line_len:
|
|
||||||
self.max_width = line_len
|
|
||||||
buf.append(line)
|
|
||||||
|
|
||||||
buf.append(' ' * (clear_width - line_len))
|
buf.append(BEFORE_BAR)
|
||||||
|
line = self.format_progress_line()
|
||||||
|
line_len = term_len(line)
|
||||||
|
if self.max_width is None or self.max_width < line_len:
|
||||||
|
self.max_width = line_len
|
||||||
|
|
||||||
|
buf.append(line)
|
||||||
|
buf.append(' ' * (clear_width - line_len))
|
||||||
line = ''.join(buf)
|
line = ''.join(buf)
|
||||||
|
|
||||||
# Render the line only if it changed.
|
# Render the line only if it changed.
|
||||||
if line != self._last_line:
|
|
||||||
|
if line != self._last_line and not self.is_fast():
|
||||||
self._last_line = line
|
self._last_line = line
|
||||||
echo(line, file=self.file, color=self.color, nl=nl)
|
echo(line, file=self.file, color=self.color, nl=False)
|
||||||
self.file.flush()
|
self.file.flush()
|
||||||
|
|
||||||
def make_step(self, n_steps):
|
def make_step(self, n_steps):
|
||||||
|
@ -239,7 +247,16 @@ class ProgressBar(object):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.last_eta = time.time()
|
self.last_eta = time.time()
|
||||||
self.avg = self.avg[-6:] + [-(self.start - time.time()) / (self.pos)]
|
|
||||||
|
# self.avg is a rolling list of length <= 7 of steps where steps are
|
||||||
|
# defined as time elapsed divided by the total progress through
|
||||||
|
# self.length.
|
||||||
|
if self.pos:
|
||||||
|
step = (time.time() - self.start) / self.pos
|
||||||
|
else:
|
||||||
|
step = time.time() - self.start
|
||||||
|
|
||||||
|
self.avg = self.avg[-6:] + [step]
|
||||||
|
|
||||||
self.eta_known = self.length_known
|
self.eta_known = self.length_known
|
||||||
|
|
||||||
|
@ -252,54 +269,56 @@ class ProgressBar(object):
|
||||||
self.current_item = None
|
self.current_item = None
|
||||||
self.finished = True
|
self.finished = True
|
||||||
|
|
||||||
def next(self):
|
def generator(self):
|
||||||
|
"""
|
||||||
|
Returns a generator which yields the items added to the bar during
|
||||||
|
construction, and updates the progress bar *after* the yielded block
|
||||||
|
returns.
|
||||||
|
"""
|
||||||
|
if not self.entered:
|
||||||
|
raise RuntimeError('You need to use progress bars in a with block.')
|
||||||
|
|
||||||
if self.is_hidden:
|
if self.is_hidden:
|
||||||
return next(self.iter)
|
for rv in self.iter:
|
||||||
try:
|
yield rv
|
||||||
rv = next(self.iter)
|
else:
|
||||||
self.current_item = rv
|
for rv in self.iter:
|
||||||
except StopIteration:
|
self.current_item = rv
|
||||||
|
yield rv
|
||||||
|
self.update(1)
|
||||||
self.finish()
|
self.finish()
|
||||||
self.render_progress()
|
self.render_progress()
|
||||||
raise StopIteration()
|
|
||||||
else:
|
|
||||||
self.update(1)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
if not PY2:
|
|
||||||
__next__ = next
|
|
||||||
del next
|
|
||||||
|
|
||||||
|
|
||||||
def pager(text, color=None):
|
def pager(generator, color=None):
|
||||||
"""Decide what method to use for paging through text."""
|
"""Decide what method to use for paging through text."""
|
||||||
stdout = _default_text_stdout()
|
stdout = _default_text_stdout()
|
||||||
if not isatty(sys.stdin) or not isatty(stdout):
|
if not isatty(sys.stdin) or not isatty(stdout):
|
||||||
return _nullpager(stdout, text, color)
|
return _nullpager(stdout, generator, color)
|
||||||
pager_cmd = (os.environ.get('PAGER', None) or '').strip()
|
pager_cmd = (os.environ.get('PAGER', None) or '').strip()
|
||||||
if pager_cmd:
|
if pager_cmd:
|
||||||
if WIN:
|
if WIN:
|
||||||
return _tempfilepager(text, pager_cmd, color)
|
return _tempfilepager(generator, pager_cmd, color)
|
||||||
return _pipepager(text, pager_cmd, color)
|
return _pipepager(generator, pager_cmd, color)
|
||||||
if os.environ.get('TERM') in ('dumb', 'emacs'):
|
if os.environ.get('TERM') in ('dumb', 'emacs'):
|
||||||
return _nullpager(stdout, text, color)
|
return _nullpager(stdout, generator, color)
|
||||||
if WIN or sys.platform.startswith('os2'):
|
if WIN or sys.platform.startswith('os2'):
|
||||||
return _tempfilepager(text, 'more <', color)
|
return _tempfilepager(generator, 'more <', color)
|
||||||
if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0:
|
if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0:
|
||||||
return _pipepager(text, 'less', color)
|
return _pipepager(generator, 'less', color)
|
||||||
|
|
||||||
import tempfile
|
import tempfile
|
||||||
fd, filename = tempfile.mkstemp()
|
fd, filename = tempfile.mkstemp()
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
try:
|
try:
|
||||||
if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0:
|
if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0:
|
||||||
return _pipepager(text, 'more', color)
|
return _pipepager(generator, 'more', color)
|
||||||
return _nullpager(stdout, text, color)
|
return _nullpager(stdout, generator, color)
|
||||||
finally:
|
finally:
|
||||||
os.unlink(filename)
|
os.unlink(filename)
|
||||||
|
|
||||||
|
|
||||||
def _pipepager(text, cmd, color):
|
def _pipepager(generator, cmd, color):
|
||||||
"""Page through text by feeding it to another program. Invoking a
|
"""Page through text by feeding it to another program. Invoking a
|
||||||
pager through this might support colors.
|
pager through this might support colors.
|
||||||
"""
|
"""
|
||||||
|
@ -317,17 +336,19 @@ def _pipepager(text, cmd, color):
|
||||||
elif 'r' in less_flags or 'R' in less_flags:
|
elif 'r' in less_flags or 'R' in less_flags:
|
||||||
color = True
|
color = True
|
||||||
|
|
||||||
if not color:
|
|
||||||
text = strip_ansi(text)
|
|
||||||
|
|
||||||
c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
|
c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
|
||||||
env=env)
|
env=env)
|
||||||
encoding = get_best_encoding(c.stdin)
|
encoding = get_best_encoding(c.stdin)
|
||||||
try:
|
try:
|
||||||
c.stdin.write(text.encode(encoding, 'replace'))
|
for text in generator:
|
||||||
c.stdin.close()
|
if not color:
|
||||||
|
text = strip_ansi(text)
|
||||||
|
|
||||||
|
c.stdin.write(text.encode(encoding, 'replace'))
|
||||||
except (IOError, KeyboardInterrupt):
|
except (IOError, KeyboardInterrupt):
|
||||||
pass
|
pass
|
||||||
|
else:
|
||||||
|
c.stdin.close()
|
||||||
|
|
||||||
# Less doesn't respect ^C, but catches it for its own UI purposes (aborting
|
# Less doesn't respect ^C, but catches it for its own UI purposes (aborting
|
||||||
# search or other commands inside less).
|
# search or other commands inside less).
|
||||||
|
@ -346,10 +367,12 @@ def _pipepager(text, cmd, color):
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def _tempfilepager(text, cmd, color):
|
def _tempfilepager(generator, cmd, color):
|
||||||
"""Page through text by invoking a program on a temporary file."""
|
"""Page through text by invoking a program on a temporary file."""
|
||||||
import tempfile
|
import tempfile
|
||||||
filename = tempfile.mktemp()
|
filename = tempfile.mktemp()
|
||||||
|
# TODO: This never terminates if the passed generator never terminates.
|
||||||
|
text = "".join(generator)
|
||||||
if not color:
|
if not color:
|
||||||
text = strip_ansi(text)
|
text = strip_ansi(text)
|
||||||
encoding = get_best_encoding(sys.stdout)
|
encoding = get_best_encoding(sys.stdout)
|
||||||
|
@ -361,11 +384,12 @@ def _tempfilepager(text, cmd, color):
|
||||||
os.unlink(filename)
|
os.unlink(filename)
|
||||||
|
|
||||||
|
|
||||||
def _nullpager(stream, text, color):
|
def _nullpager(stream, generator, color):
|
||||||
"""Simply print unformatted text. This is the ultimate fallback."""
|
"""Simply print unformatted text. This is the ultimate fallback."""
|
||||||
if not color:
|
for text in generator:
|
||||||
text = strip_ansi(text)
|
if not color:
|
||||||
stream.write(text)
|
text = strip_ansi(text)
|
||||||
|
stream.write(text)
|
||||||
|
|
||||||
|
|
||||||
class Editor(object):
|
class Editor(object):
|
||||||
|
@ -478,6 +502,14 @@ def open_url(url, wait=False, locate=False):
|
||||||
args = 'start %s "" "%s"' % (
|
args = 'start %s "" "%s"' % (
|
||||||
wait and '/WAIT' or '', url.replace('"', ''))
|
wait and '/WAIT' or '', url.replace('"', ''))
|
||||||
return os.system(args)
|
return os.system(args)
|
||||||
|
elif CYGWIN:
|
||||||
|
if locate:
|
||||||
|
url = _unquote_file(url)
|
||||||
|
args = 'cygstart "%s"' % (os.path.dirname(url).replace('"', ''))
|
||||||
|
else:
|
||||||
|
args = 'cygstart %s "%s"' % (
|
||||||
|
wait and '-w' or '', url.replace('"', ''))
|
||||||
|
return os.system(args)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if locate:
|
if locate:
|
||||||
|
@ -497,32 +529,69 @@ def open_url(url, wait=False, locate=False):
|
||||||
|
|
||||||
|
|
||||||
def _translate_ch_to_exc(ch):
|
def _translate_ch_to_exc(ch):
|
||||||
if ch == '\x03':
|
if ch == u'\x03':
|
||||||
raise KeyboardInterrupt()
|
raise KeyboardInterrupt()
|
||||||
if ch == '\x04':
|
if ch == u'\x04' and not WIN: # Unix-like, Ctrl+D
|
||||||
|
raise EOFError()
|
||||||
|
if ch == u'\x1a' and WIN: # Windows, Ctrl+Z
|
||||||
raise EOFError()
|
raise EOFError()
|
||||||
|
|
||||||
|
|
||||||
if WIN:
|
if WIN:
|
||||||
import msvcrt
|
import msvcrt
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def raw_terminal():
|
||||||
|
yield
|
||||||
|
|
||||||
def getchar(echo):
|
def getchar(echo):
|
||||||
rv = msvcrt.getch()
|
# The function `getch` will return a bytes object corresponding to
|
||||||
|
# the pressed character. Since Windows 10 build 1803, it will also
|
||||||
|
# return \x00 when called a second time after pressing a regular key.
|
||||||
|
#
|
||||||
|
# `getwch` does not share this probably-bugged behavior. Moreover, it
|
||||||
|
# returns a Unicode object by default, which is what we want.
|
||||||
|
#
|
||||||
|
# Either of these functions will return \x00 or \xe0 to indicate
|
||||||
|
# a special key, and you need to call the same function again to get
|
||||||
|
# the "rest" of the code. The fun part is that \u00e0 is
|
||||||
|
# "latin small letter a with grave", so if you type that on a French
|
||||||
|
# keyboard, you _also_ get a \xe0.
|
||||||
|
# E.g., consider the Up arrow. This returns \xe0 and then \x48. The
|
||||||
|
# resulting Unicode string reads as "a with grave" + "capital H".
|
||||||
|
# This is indistinguishable from when the user actually types
|
||||||
|
# "a with grave" and then "capital H".
|
||||||
|
#
|
||||||
|
# When \xe0 is returned, we assume it's part of a special-key sequence
|
||||||
|
# and call `getwch` again, but that means that when the user types
|
||||||
|
# the \u00e0 character, `getchar` doesn't return until a second
|
||||||
|
# character is typed.
|
||||||
|
# The alternative is returning immediately, but that would mess up
|
||||||
|
# cross-platform handling of arrow keys and others that start with
|
||||||
|
# \xe0. Another option is using `getch`, but then we can't reliably
|
||||||
|
# read non-ASCII characters, because return values of `getch` are
|
||||||
|
# limited to the current 8-bit codepage.
|
||||||
|
#
|
||||||
|
# Anyway, Click doesn't claim to do this Right(tm), and using `getwch`
|
||||||
|
# is doing the right thing in more situations than with `getch`.
|
||||||
if echo:
|
if echo:
|
||||||
msvcrt.putchar(rv)
|
func = msvcrt.getwche
|
||||||
|
else:
|
||||||
|
func = msvcrt.getwch
|
||||||
|
|
||||||
|
rv = func()
|
||||||
|
if rv in (u'\x00', u'\xe0'):
|
||||||
|
# \x00 and \xe0 are control characters that indicate special key,
|
||||||
|
# see above.
|
||||||
|
rv += func()
|
||||||
_translate_ch_to_exc(rv)
|
_translate_ch_to_exc(rv)
|
||||||
if PY2:
|
|
||||||
enc = getattr(sys.stdin, 'encoding', None)
|
|
||||||
if enc is not None:
|
|
||||||
rv = rv.decode(enc, 'replace')
|
|
||||||
else:
|
|
||||||
rv = rv.decode('cp1252', 'replace')
|
|
||||||
return rv
|
return rv
|
||||||
else:
|
else:
|
||||||
import tty
|
import tty
|
||||||
import termios
|
import termios
|
||||||
|
|
||||||
def getchar(echo):
|
@contextlib.contextmanager
|
||||||
|
def raw_terminal():
|
||||||
if not isatty(sys.stdin):
|
if not isatty(sys.stdin):
|
||||||
f = open('/dev/tty')
|
f = open('/dev/tty')
|
||||||
fd = f.fileno()
|
fd = f.fileno()
|
||||||
|
@ -533,9 +602,7 @@ else:
|
||||||
old_settings = termios.tcgetattr(fd)
|
old_settings = termios.tcgetattr(fd)
|
||||||
try:
|
try:
|
||||||
tty.setraw(fd)
|
tty.setraw(fd)
|
||||||
ch = os.read(fd, 32)
|
yield fd
|
||||||
if echo and isatty(sys.stdout):
|
|
||||||
sys.stdout.write(ch)
|
|
||||||
finally:
|
finally:
|
||||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
@ -543,5 +610,12 @@ else:
|
||||||
f.close()
|
f.close()
|
||||||
except termios.error:
|
except termios.error:
|
||||||
pass
|
pass
|
||||||
_translate_ch_to_exc(ch)
|
|
||||||
return ch.decode(get_best_encoding(sys.stdin), 'replace')
|
def getchar(echo):
|
||||||
|
with raw_terminal() as fd:
|
||||||
|
ch = os.read(fd, 32)
|
||||||
|
ch = ch.decode(get_best_encoding(sys.stdin), 'replace')
|
||||||
|
if echo and isatty(sys.stdout):
|
||||||
|
sys.stdout.write(ch)
|
||||||
|
_translate_ch_to_exc(ch)
|
||||||
|
return ch
|
||||||
|
|
|
@ -14,6 +14,8 @@ click = sys.modules[__name__.rsplit('.', 1)[0]]
|
||||||
|
|
||||||
def _find_unicode_literals_frame():
|
def _find_unicode_literals_frame():
|
||||||
import __future__
|
import __future__
|
||||||
|
if not hasattr(sys, '_getframe'): # not all Python implementations have it
|
||||||
|
return 0
|
||||||
frm = sys._getframe(1)
|
frm = sys._getframe(1)
|
||||||
idx = 1
|
idx = 1
|
||||||
while frm is not None:
|
while frm is not None:
|
||||||
|
@ -41,7 +43,7 @@ def _check_for_unicode_literals():
|
||||||
'because it can introduce subtle bugs in your '
|
'because it can introduce subtle bugs in your '
|
||||||
'code. You should instead use explicit u"" literals '
|
'code. You should instead use explicit u"" literals '
|
||||||
'for your unicode strings. For more information see '
|
'for your unicode strings. For more information see '
|
||||||
'http://click.pocoo.org/python3/'),
|
'https://click.palletsprojects.com/python3/'),
|
||||||
stacklevel=bad_frame)
|
stacklevel=bad_frame)
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,8 +62,11 @@ def _verify_python3_env():
|
||||||
extra = ''
|
extra = ''
|
||||||
if os.name == 'posix':
|
if os.name == 'posix':
|
||||||
import subprocess
|
import subprocess
|
||||||
rv = subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE,
|
try:
|
||||||
stderr=subprocess.PIPE).communicate()[0]
|
rv = subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE).communicate()[0]
|
||||||
|
except OSError:
|
||||||
|
rv = b''
|
||||||
good_locales = set()
|
good_locales = set()
|
||||||
has_c_utf8 = False
|
has_c_utf8 = False
|
||||||
|
|
||||||
|
@ -94,7 +99,7 @@ def _verify_python3_env():
|
||||||
else:
|
else:
|
||||||
extra += (
|
extra += (
|
||||||
'This system lists a couple of UTF-8 supporting locales that\n'
|
'This system lists a couple of UTF-8 supporting locales that\n'
|
||||||
'you can pick from. The following suitable locales where\n'
|
'you can pick from. The following suitable locales were\n'
|
||||||
'discovered: %s'
|
'discovered: %s'
|
||||||
) % ', '.join(sorted(good_locales))
|
) % ', '.join(sorted(good_locales))
|
||||||
|
|
||||||
|
@ -112,7 +117,9 @@ def _verify_python3_env():
|
||||||
'is not supported'
|
'is not supported'
|
||||||
) % bad_locale
|
) % bad_locale
|
||||||
|
|
||||||
raise RuntimeError('Click will abort further execution because Python 3 '
|
raise RuntimeError(
|
||||||
'was configured to use ASCII as encoding for the '
|
'Click will abort further execution because Python 3 was'
|
||||||
'environment. Consult http://click.pocoo.org/python3/'
|
' configured to use ASCII as encoding for the environment.'
|
||||||
'for mitigation steps.' + extra)
|
' Consult https://click.palletsprojects.com/en/7.x/python3/ for'
|
||||||
|
' mitigation steps.' + extra
|
||||||
|
)
|
||||||
|
|
|
@ -15,7 +15,7 @@ import zlib
|
||||||
import time
|
import time
|
||||||
import ctypes
|
import ctypes
|
||||||
import msvcrt
|
import msvcrt
|
||||||
from click._compat import _NonClosingTextIOWrapper, text_type, PY2
|
from ._compat import _NonClosingTextIOWrapper, text_type, PY2
|
||||||
from ctypes import byref, POINTER, c_int, c_char, c_char_p, \
|
from ctypes import byref, POINTER, c_int, c_char, c_char_p, \
|
||||||
c_void_p, py_object, c_ssize_t, c_ulong, windll, WINFUNCTYPE
|
c_void_p, py_object, c_ssize_t, c_ulong, windll, WINFUNCTYPE
|
||||||
try:
|
try:
|
||||||
|
@ -201,6 +201,40 @@ class ConsoleStream(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WindowsChunkedWriter(object):
|
||||||
|
"""
|
||||||
|
Wraps a stream (such as stdout), acting as a transparent proxy for all
|
||||||
|
attribute access apart from method 'write()' which we wrap to write in
|
||||||
|
limited chunks due to a Windows limitation on binary console streams.
|
||||||
|
"""
|
||||||
|
def __init__(self, wrapped):
|
||||||
|
# double-underscore everything to prevent clashes with names of
|
||||||
|
# attributes on the wrapped stream object.
|
||||||
|
self.__wrapped = wrapped
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return getattr(self.__wrapped, name)
|
||||||
|
|
||||||
|
def write(self, text):
|
||||||
|
total_to_write = len(text)
|
||||||
|
written = 0
|
||||||
|
|
||||||
|
while written < total_to_write:
|
||||||
|
to_write = min(total_to_write - written, MAX_BYTES_WRITTEN)
|
||||||
|
self.__wrapped.write(text[written:written+to_write])
|
||||||
|
written += to_write
|
||||||
|
|
||||||
|
|
||||||
|
_wrapped_std_streams = set()
|
||||||
|
|
||||||
|
|
||||||
|
def _wrap_std_stream(name):
|
||||||
|
# Python 2 & Windows 7 and below
|
||||||
|
if PY2 and sys.getwindowsversion()[:2] <= (6, 1) and name not in _wrapped_std_streams:
|
||||||
|
setattr(sys, name, WindowsChunkedWriter(getattr(sys, name)))
|
||||||
|
_wrapped_std_streams.add(name)
|
||||||
|
|
||||||
|
|
||||||
def _get_text_stdin(buffer_stream):
|
def _get_text_stdin(buffer_stream):
|
||||||
text_stream = _NonClosingTextIOWrapper(
|
text_stream = _NonClosingTextIOWrapper(
|
||||||
io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)),
|
io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)),
|
||||||
|
@ -210,14 +244,14 @@ def _get_text_stdin(buffer_stream):
|
||||||
|
|
||||||
def _get_text_stdout(buffer_stream):
|
def _get_text_stdout(buffer_stream):
|
||||||
text_stream = _NonClosingTextIOWrapper(
|
text_stream = _NonClosingTextIOWrapper(
|
||||||
_WindowsConsoleWriter(STDOUT_HANDLE),
|
io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)),
|
||||||
'utf-16-le', 'strict', line_buffering=True)
|
'utf-16-le', 'strict', line_buffering=True)
|
||||||
return ConsoleStream(text_stream, buffer_stream)
|
return ConsoleStream(text_stream, buffer_stream)
|
||||||
|
|
||||||
|
|
||||||
def _get_text_stderr(buffer_stream):
|
def _get_text_stderr(buffer_stream):
|
||||||
text_stream = _NonClosingTextIOWrapper(
|
text_stream = _NonClosingTextIOWrapper(
|
||||||
_WindowsConsoleWriter(STDERR_HANDLE),
|
io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)),
|
||||||
'utf-16-le', 'strict', line_buffering=True)
|
'utf-16-le', 'strict', line_buffering=True)
|
||||||
return ConsoleStream(text_stream, buffer_stream)
|
return ConsoleStream(text_stream, buffer_stream)
|
||||||
|
|
||||||
|
@ -261,7 +295,7 @@ def _get_windows_console_stream(f, encoding, errors):
|
||||||
func = _stream_factories.get(f.fileno())
|
func = _stream_factories.get(f.fileno())
|
||||||
if func is not None:
|
if func is not None:
|
||||||
if not PY2:
|
if not PY2:
|
||||||
f = getattr(f, 'buffer')
|
f = getattr(f, 'buffer', None)
|
||||||
if f is None:
|
if f is None:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import errno
|
import errno
|
||||||
|
import inspect
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
@ -6,15 +7,16 @@ from itertools import repeat
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
|
|
||||||
from .types import convert_type, IntRange, BOOL
|
from .types import convert_type, IntRange, BOOL
|
||||||
from .utils import make_str, make_default_short_help, echo, get_os_args
|
from .utils import PacifyFlushWrapper, make_str, make_default_short_help, \
|
||||||
|
echo, get_os_args
|
||||||
from .exceptions import ClickException, UsageError, BadParameter, Abort, \
|
from .exceptions import ClickException, UsageError, BadParameter, Abort, \
|
||||||
MissingParameter
|
MissingParameter, Exit
|
||||||
from .termui import prompt, confirm
|
from .termui import prompt, confirm, style
|
||||||
from .formatting import HelpFormatter, join_options
|
from .formatting import HelpFormatter, join_options
|
||||||
from .parser import OptionParser, split_opt
|
from .parser import OptionParser, split_opt
|
||||||
from .globals import push_context, pop_context
|
from .globals import push_context, pop_context
|
||||||
|
|
||||||
from ._compat import PY2, isidentifier, iteritems
|
from ._compat import PY2, isidentifier, iteritems, string_types
|
||||||
from ._unicodefun import _check_for_unicode_literals, _verify_python3_env
|
from ._unicodefun import _check_for_unicode_literals, _verify_python3_env
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,6 +26,24 @@ _missing = object()
|
||||||
SUBCOMMAND_METAVAR = 'COMMAND [ARGS]...'
|
SUBCOMMAND_METAVAR = 'COMMAND [ARGS]...'
|
||||||
SUBCOMMANDS_METAVAR = 'COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...'
|
SUBCOMMANDS_METAVAR = 'COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...'
|
||||||
|
|
||||||
|
DEPRECATED_HELP_NOTICE = ' (DEPRECATED)'
|
||||||
|
DEPRECATED_INVOKE_NOTICE = 'DeprecationWarning: ' + \
|
||||||
|
'The command %(name)s is deprecated.'
|
||||||
|
|
||||||
|
|
||||||
|
def _maybe_show_deprecated_notice(cmd):
|
||||||
|
if cmd.deprecated:
|
||||||
|
echo(style(DEPRECATED_INVOKE_NOTICE % {'name': cmd.name}, fg='red'), err=True)
|
||||||
|
|
||||||
|
|
||||||
|
def fast_exit(code):
|
||||||
|
"""Exit without garbage collection, this speeds up exit by about 10ms for
|
||||||
|
things like bash completion.
|
||||||
|
"""
|
||||||
|
sys.stdout.flush()
|
||||||
|
sys.stderr.flush()
|
||||||
|
os._exit(code)
|
||||||
|
|
||||||
|
|
||||||
def _bashcomplete(cmd, prog_name, complete_var=None):
|
def _bashcomplete(cmd, prog_name, complete_var=None):
|
||||||
"""Internal handler for the bash completion support."""
|
"""Internal handler for the bash completion support."""
|
||||||
|
@ -35,7 +55,7 @@ def _bashcomplete(cmd, prog_name, complete_var=None):
|
||||||
|
|
||||||
from ._bashcomplete import bashcomplete
|
from ._bashcomplete import bashcomplete
|
||||||
if bashcomplete(cmd, prog_name, complete_var, complete_instr):
|
if bashcomplete(cmd, prog_name, complete_var, complete_instr):
|
||||||
sys.exit(1)
|
fast_exit(1)
|
||||||
|
|
||||||
|
|
||||||
def _check_multicommand(base_command, cmd_name, cmd, register=False):
|
def _check_multicommand(base_command, cmd_name, cmd, register=False):
|
||||||
|
@ -50,9 +70,7 @@ def _check_multicommand(base_command, cmd_name, cmd, register=False):
|
||||||
raise RuntimeError('%s. Command "%s" is set to chain and "%s" was '
|
raise RuntimeError('%s. Command "%s" is set to chain and "%s" was '
|
||||||
'added as subcommand but it in itself is a '
|
'added as subcommand but it in itself is a '
|
||||||
'multi command. ("%s" is a %s within a chained '
|
'multi command. ("%s" is a %s within a chained '
|
||||||
'%s named "%s"). This restriction was supposed to '
|
'%s named "%s").' % (
|
||||||
'be lifted in 6.0 but the fix was flawed. This '
|
|
||||||
'will be fixed in Click 7.0' % (
|
|
||||||
hint, base_command.name, cmd_name,
|
hint, base_command.name, cmd_name,
|
||||||
cmd_name, cmd.__class__.__name__,
|
cmd_name, cmd.__class__.__name__,
|
||||||
base_command.__class__.__name__,
|
base_command.__class__.__name__,
|
||||||
|
@ -165,7 +183,8 @@ class Context(object):
|
||||||
add some safety mapping on the right.
|
add some safety mapping on the right.
|
||||||
:param resilient_parsing: if this flag is enabled then Click will
|
:param resilient_parsing: if this flag is enabled then Click will
|
||||||
parse without any interactivity or callback
|
parse without any interactivity or callback
|
||||||
invocation. This is useful for implementing
|
invocation. Default values will also be
|
||||||
|
ignored. This is useful for implementing
|
||||||
things such as completion support.
|
things such as completion support.
|
||||||
:param allow_extra_args: if this is set to `True` then extra arguments
|
:param allow_extra_args: if this is set to `True` then extra arguments
|
||||||
at the end will not raise an error and will be
|
at the end will not raise an error and will be
|
||||||
|
@ -295,7 +314,8 @@ class Context(object):
|
||||||
self.token_normalize_func = token_normalize_func
|
self.token_normalize_func = token_normalize_func
|
||||||
|
|
||||||
#: Indicates if resilient parsing is enabled. In that case Click
|
#: Indicates if resilient parsing is enabled. In that case Click
|
||||||
#: will do its best to not cause any failures.
|
#: will do its best to not cause any failures and default values
|
||||||
|
#: will be ignored. Useful for completion.
|
||||||
self.resilient_parsing = resilient_parsing
|
self.resilient_parsing = resilient_parsing
|
||||||
|
|
||||||
# If there is no envvar prefix yet, but the parent has one and
|
# If there is no envvar prefix yet, but the parent has one and
|
||||||
|
@ -308,7 +328,7 @@ class Context(object):
|
||||||
auto_envvar_prefix = '%s_%s' % (parent.auto_envvar_prefix,
|
auto_envvar_prefix = '%s_%s' % (parent.auto_envvar_prefix,
|
||||||
self.info_name.upper())
|
self.info_name.upper())
|
||||||
else:
|
else:
|
||||||
self.auto_envvar_prefix = auto_envvar_prefix.upper()
|
auto_envvar_prefix = auto_envvar_prefix.upper()
|
||||||
self.auto_envvar_prefix = auto_envvar_prefix
|
self.auto_envvar_prefix = auto_envvar_prefix
|
||||||
|
|
||||||
if color is None and parent is not None:
|
if color is None and parent is not None:
|
||||||
|
@ -372,7 +392,7 @@ class Context(object):
|
||||||
@property
|
@property
|
||||||
def meta(self):
|
def meta(self):
|
||||||
"""This is a dictionary which is shared with all the contexts
|
"""This is a dictionary which is shared with all the contexts
|
||||||
that are nested. It exists so that click utiltiies can store some
|
that are nested. It exists so that click utilities can store some
|
||||||
state here if they need to. It is however the responsibility of
|
state here if they need to. It is however the responsibility of
|
||||||
that code to manage this dictionary well.
|
that code to manage this dictionary well.
|
||||||
|
|
||||||
|
@ -481,7 +501,7 @@ class Context(object):
|
||||||
|
|
||||||
def exit(self, code=0):
|
def exit(self, code=0):
|
||||||
"""Exits the application with a given exit code."""
|
"""Exits the application with a given exit code."""
|
||||||
sys.exit(code)
|
raise Exit(code)
|
||||||
|
|
||||||
def get_usage(self):
|
def get_usage(self):
|
||||||
"""Helper method to get formatted usage string for the current
|
"""Helper method to get formatted usage string for the current
|
||||||
|
@ -655,7 +675,7 @@ class BaseCommand(object):
|
||||||
name from ``sys.argv[0]``.
|
name from ``sys.argv[0]``.
|
||||||
:param complete_var: the environment variable that controls the
|
:param complete_var: the environment variable that controls the
|
||||||
bash completion support. The default is
|
bash completion support. The default is
|
||||||
``"_<prog_name>_COMPLETE"`` with prog name in
|
``"_<prog_name>_COMPLETE"`` with prog_name in
|
||||||
uppercase.
|
uppercase.
|
||||||
:param standalone_mode: the default behavior is to invoke the script
|
:param standalone_mode: the default behavior is to invoke the script
|
||||||
in standalone mode. Click will then
|
in standalone mode. Click will then
|
||||||
|
@ -670,7 +690,7 @@ class BaseCommand(object):
|
||||||
constructor. See :class:`Context` for more information.
|
constructor. See :class:`Context` for more information.
|
||||||
"""
|
"""
|
||||||
# If we are in Python 3, we will verify that the environment is
|
# If we are in Python 3, we will verify that the environment is
|
||||||
# sane at this point of reject further execution to avoid a
|
# sane at this point or reject further execution to avoid a
|
||||||
# broken script.
|
# broken script.
|
||||||
if not PY2:
|
if not PY2:
|
||||||
_verify_python3_env()
|
_verify_python3_env()
|
||||||
|
@ -697,6 +717,13 @@ class BaseCommand(object):
|
||||||
rv = self.invoke(ctx)
|
rv = self.invoke(ctx)
|
||||||
if not standalone_mode:
|
if not standalone_mode:
|
||||||
return rv
|
return rv
|
||||||
|
# it's not safe to `ctx.exit(rv)` here!
|
||||||
|
# note that `rv` may actually contain data like "1" which
|
||||||
|
# has obvious effects
|
||||||
|
# more subtle case: `rv=[None, None]` can come out of
|
||||||
|
# chained commands which all returned `None` -- so it's not
|
||||||
|
# even always obvious that `rv` indicates success/failure
|
||||||
|
# by its truthiness/falsiness
|
||||||
ctx.exit()
|
ctx.exit()
|
||||||
except (EOFError, KeyboardInterrupt):
|
except (EOFError, KeyboardInterrupt):
|
||||||
echo(file=sys.stderr)
|
echo(file=sys.stderr)
|
||||||
|
@ -708,9 +735,24 @@ class BaseCommand(object):
|
||||||
sys.exit(e.exit_code)
|
sys.exit(e.exit_code)
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
if e.errno == errno.EPIPE:
|
if e.errno == errno.EPIPE:
|
||||||
|
sys.stdout = PacifyFlushWrapper(sys.stdout)
|
||||||
|
sys.stderr = PacifyFlushWrapper(sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
except Exit as e:
|
||||||
|
if standalone_mode:
|
||||||
|
sys.exit(e.exit_code)
|
||||||
|
else:
|
||||||
|
# in non-standalone mode, return the exit code
|
||||||
|
# note that this is only reached if `self.invoke` above raises
|
||||||
|
# an Exit explicitly -- thus bypassing the check there which
|
||||||
|
# would return its result
|
||||||
|
# the results of non-standalone execution may therefore be
|
||||||
|
# somewhat ambiguous: if there are codepaths which lead to
|
||||||
|
# `ctx.exit(1)` and to `return 1`, the caller won't be able to
|
||||||
|
# tell the difference between the two
|
||||||
|
return e.exit_code
|
||||||
except Abort:
|
except Abort:
|
||||||
if not standalone_mode:
|
if not standalone_mode:
|
||||||
raise
|
raise
|
||||||
|
@ -743,11 +785,16 @@ class Command(BaseCommand):
|
||||||
shown on the command listing of the parent command.
|
shown on the command listing of the parent command.
|
||||||
:param add_help_option: by default each command registers a ``--help``
|
:param add_help_option: by default each command registers a ``--help``
|
||||||
option. This can be disabled by this parameter.
|
option. This can be disabled by this parameter.
|
||||||
|
:param hidden: hide this command from help outputs.
|
||||||
|
|
||||||
|
:param deprecated: issues a message indicating that
|
||||||
|
the command is deprecated.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, context_settings=None, callback=None,
|
def __init__(self, name, context_settings=None, callback=None,
|
||||||
params=None, help=None, epilog=None, short_help=None,
|
params=None, help=None, epilog=None, short_help=None,
|
||||||
options_metavar='[OPTIONS]', add_help_option=True):
|
options_metavar='[OPTIONS]', add_help_option=True,
|
||||||
|
hidden=False, deprecated=False):
|
||||||
BaseCommand.__init__(self, name, context_settings)
|
BaseCommand.__init__(self, name, context_settings)
|
||||||
#: the callback to execute when the command fires. This might be
|
#: the callback to execute when the command fires. This might be
|
||||||
#: `None` in which case nothing happens.
|
#: `None` in which case nothing happens.
|
||||||
|
@ -756,13 +803,17 @@ class Command(BaseCommand):
|
||||||
#: should show up in the help page and execute. Eager parameters
|
#: should show up in the help page and execute. Eager parameters
|
||||||
#: will automatically be handled before non eager ones.
|
#: will automatically be handled before non eager ones.
|
||||||
self.params = params or []
|
self.params = params or []
|
||||||
|
# if a form feed (page break) is found in the help text, truncate help
|
||||||
|
# text to the content preceding the first form feed
|
||||||
|
if help and '\f' in help:
|
||||||
|
help = help.split('\f', 1)[0]
|
||||||
self.help = help
|
self.help = help
|
||||||
self.epilog = epilog
|
self.epilog = epilog
|
||||||
self.options_metavar = options_metavar
|
self.options_metavar = options_metavar
|
||||||
if short_help is None and help:
|
|
||||||
short_help = make_default_short_help(help)
|
|
||||||
self.short_help = short_help
|
self.short_help = short_help
|
||||||
self.add_help_option = add_help_option
|
self.add_help_option = add_help_option
|
||||||
|
self.hidden = hidden
|
||||||
|
self.deprecated = deprecated
|
||||||
|
|
||||||
def get_usage(self, ctx):
|
def get_usage(self, ctx):
|
||||||
formatter = ctx.make_formatter()
|
formatter = ctx.make_formatter()
|
||||||
|
@ -816,8 +867,6 @@ class Command(BaseCommand):
|
||||||
def make_parser(self, ctx):
|
def make_parser(self, ctx):
|
||||||
"""Creates the underlying option parser for this command."""
|
"""Creates the underlying option parser for this command."""
|
||||||
parser = OptionParser(ctx)
|
parser = OptionParser(ctx)
|
||||||
parser.allow_interspersed_args = ctx.allow_interspersed_args
|
|
||||||
parser.ignore_unknown_options = ctx.ignore_unknown_options
|
|
||||||
for param in self.get_params(ctx):
|
for param in self.get_params(ctx):
|
||||||
param.add_to_parser(parser, ctx)
|
param.add_to_parser(parser, ctx)
|
||||||
return parser
|
return parser
|
||||||
|
@ -830,6 +879,10 @@ class Command(BaseCommand):
|
||||||
self.format_help(ctx, formatter)
|
self.format_help(ctx, formatter)
|
||||||
return formatter.getvalue().rstrip('\n')
|
return formatter.getvalue().rstrip('\n')
|
||||||
|
|
||||||
|
def get_short_help_str(self, limit=45):
|
||||||
|
"""Gets short help for the command or makes it by shortening the long help string."""
|
||||||
|
return self.short_help or self.help and make_default_short_help(self.help, limit) or ''
|
||||||
|
|
||||||
def format_help(self, ctx, formatter):
|
def format_help(self, ctx, formatter):
|
||||||
"""Writes the help into the formatter if it exists.
|
"""Writes the help into the formatter if it exists.
|
||||||
|
|
||||||
|
@ -850,7 +903,14 @@ class Command(BaseCommand):
|
||||||
if self.help:
|
if self.help:
|
||||||
formatter.write_paragraph()
|
formatter.write_paragraph()
|
||||||
with formatter.indentation():
|
with formatter.indentation():
|
||||||
formatter.write_text(self.help)
|
help_text = self.help
|
||||||
|
if self.deprecated:
|
||||||
|
help_text += DEPRECATED_HELP_NOTICE
|
||||||
|
formatter.write_text(help_text)
|
||||||
|
elif self.deprecated:
|
||||||
|
formatter.write_paragraph()
|
||||||
|
with formatter.indentation():
|
||||||
|
formatter.write_text(DEPRECATED_HELP_NOTICE)
|
||||||
|
|
||||||
def format_options(self, ctx, formatter):
|
def format_options(self, ctx, formatter):
|
||||||
"""Writes all the options into the formatter if they exist."""
|
"""Writes all the options into the formatter if they exist."""
|
||||||
|
@ -891,6 +951,7 @@ class Command(BaseCommand):
|
||||||
"""Given a context, this invokes the attached callback (if it exists)
|
"""Given a context, this invokes the attached callback (if it exists)
|
||||||
in the right way.
|
in the right way.
|
||||||
"""
|
"""
|
||||||
|
_maybe_show_deprecated_notice(self)
|
||||||
if self.callback is not None:
|
if self.callback is not None:
|
||||||
return ctx.invoke(self.callback, **ctx.params)
|
return ctx.invoke(self.callback, **ctx.params)
|
||||||
|
|
||||||
|
@ -996,19 +1057,29 @@ class MultiCommand(Command):
|
||||||
"""Extra format methods for multi methods that adds all the commands
|
"""Extra format methods for multi methods that adds all the commands
|
||||||
after the options.
|
after the options.
|
||||||
"""
|
"""
|
||||||
rows = []
|
commands = []
|
||||||
for subcommand in self.list_commands(ctx):
|
for subcommand in self.list_commands(ctx):
|
||||||
cmd = self.get_command(ctx, subcommand)
|
cmd = self.get_command(ctx, subcommand)
|
||||||
# What is this, the tool lied about a command. Ignore it
|
# What is this, the tool lied about a command. Ignore it
|
||||||
if cmd is None:
|
if cmd is None:
|
||||||
continue
|
continue
|
||||||
|
if cmd.hidden:
|
||||||
|
continue
|
||||||
|
|
||||||
help = cmd.short_help or ''
|
commands.append((subcommand, cmd))
|
||||||
rows.append((subcommand, help))
|
|
||||||
|
|
||||||
if rows:
|
# allow for 3 times the default spacing
|
||||||
with formatter.section('Commands'):
|
if len(commands):
|
||||||
formatter.write_dl(rows)
|
limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands)
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
for subcommand, cmd in commands:
|
||||||
|
help = cmd.get_short_help_str(limit)
|
||||||
|
rows.append((subcommand, help))
|
||||||
|
|
||||||
|
if rows:
|
||||||
|
with formatter.section('Commands'):
|
||||||
|
formatter.write_dl(rows)
|
||||||
|
|
||||||
def parse_args(self, ctx, args):
|
def parse_args(self, ctx, args):
|
||||||
if not args and self.no_args_is_help and not ctx.resilient_parsing:
|
if not args and self.no_args_is_help and not ctx.resilient_parsing:
|
||||||
|
@ -1111,7 +1182,7 @@ class MultiCommand(Command):
|
||||||
# an option we want to kick off parsing again for arguments to
|
# an option we want to kick off parsing again for arguments to
|
||||||
# resolve things like --help which now should go to the main
|
# resolve things like --help which now should go to the main
|
||||||
# place.
|
# place.
|
||||||
if cmd is None:
|
if cmd is None and not ctx.resilient_parsing:
|
||||||
if split_opt(cmd_name)[0]:
|
if split_opt(cmd_name)[0]:
|
||||||
self.parse_args(ctx, ctx.args)
|
self.parse_args(ctx, ctx.args)
|
||||||
ctx.fail('No such command "%s".' % original_cmd_name)
|
ctx.fail('No such command "%s".' % original_cmd_name)
|
||||||
|
@ -1216,7 +1287,7 @@ class CommandCollection(MultiCommand):
|
||||||
|
|
||||||
|
|
||||||
class Parameter(object):
|
class Parameter(object):
|
||||||
"""A parameter to a command comes in two versions: they are either
|
r"""A parameter to a command comes in two versions: they are either
|
||||||
:class:`Option`\s or :class:`Argument`\s. Other subclasses are currently
|
:class:`Option`\s or :class:`Argument`\s. Other subclasses are currently
|
||||||
not supported by design as some of the internals for parsing are
|
not supported by design as some of the internals for parsing are
|
||||||
intentionally not finalized.
|
intentionally not finalized.
|
||||||
|
@ -1261,7 +1332,8 @@ class Parameter(object):
|
||||||
|
|
||||||
def __init__(self, param_decls=None, type=None, required=False,
|
def __init__(self, param_decls=None, type=None, required=False,
|
||||||
default=None, callback=None, nargs=None, metavar=None,
|
default=None, callback=None, nargs=None, metavar=None,
|
||||||
expose_value=True, is_eager=False, envvar=None):
|
expose_value=True, is_eager=False, envvar=None,
|
||||||
|
autocompletion=None):
|
||||||
self.name, self.opts, self.secondary_opts = \
|
self.name, self.opts, self.secondary_opts = \
|
||||||
self._parse_decls(param_decls or (), expose_value)
|
self._parse_decls(param_decls or (), expose_value)
|
||||||
|
|
||||||
|
@ -1284,6 +1356,7 @@ class Parameter(object):
|
||||||
self.is_eager = is_eager
|
self.is_eager = is_eager
|
||||||
self.metavar = metavar
|
self.metavar = metavar
|
||||||
self.envvar = envvar
|
self.envvar = envvar
|
||||||
|
self.autocompletion = autocompletion
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def human_readable_name(self):
|
def human_readable_name(self):
|
||||||
|
@ -1316,10 +1389,10 @@ class Parameter(object):
|
||||||
|
|
||||||
def consume_value(self, ctx, opts):
|
def consume_value(self, ctx, opts):
|
||||||
value = opts.get(self.name)
|
value = opts.get(self.name)
|
||||||
if value is None:
|
|
||||||
value = ctx.lookup_default(self.name)
|
|
||||||
if value is None:
|
if value is None:
|
||||||
value = self.value_from_envvar(ctx)
|
value = self.value_from_envvar(ctx)
|
||||||
|
if value is None:
|
||||||
|
value = ctx.lookup_default(self.name)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def type_cast_value(self, ctx, value):
|
def type_cast_value(self, ctx, value):
|
||||||
|
@ -1364,7 +1437,7 @@ class Parameter(object):
|
||||||
def full_process_value(self, ctx, value):
|
def full_process_value(self, ctx, value):
|
||||||
value = self.process_value(ctx, value)
|
value = self.process_value(ctx, value)
|
||||||
|
|
||||||
if value is None:
|
if value is None and not ctx.resilient_parsing:
|
||||||
value = self.get_default(ctx)
|
value = self.get_default(ctx)
|
||||||
|
|
||||||
if self.required and self.value_is_missing(value):
|
if self.required and self.value_is_missing(value):
|
||||||
|
@ -1416,6 +1489,13 @@ class Parameter(object):
|
||||||
def get_usage_pieces(self, ctx):
|
def get_usage_pieces(self, ctx):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def get_error_hint(self, ctx):
|
||||||
|
"""Get a stringified version of the param for use in error messages to
|
||||||
|
indicate which param caused the error.
|
||||||
|
"""
|
||||||
|
hint_list = self.opts or [self.human_readable_name]
|
||||||
|
return ' / '.join('"%s"' % x for x in hint_list)
|
||||||
|
|
||||||
|
|
||||||
class Option(Parameter):
|
class Option(Parameter):
|
||||||
"""Options are usually optional values on the command line and
|
"""Options are usually optional values on the command line and
|
||||||
|
@ -1424,10 +1504,15 @@ class Option(Parameter):
|
||||||
All other parameters are passed onwards to the parameter constructor.
|
All other parameters are passed onwards to the parameter constructor.
|
||||||
|
|
||||||
:param show_default: controls if the default value should be shown on the
|
:param show_default: controls if the default value should be shown on the
|
||||||
help page. Normally, defaults are not shown.
|
help page. Normally, defaults are not shown. If this
|
||||||
:param prompt: if set to `True` or a non empty string then the user will
|
value is a string, it shows the string instead of the
|
||||||
be prompted for input if not set. If set to `True` the
|
value. This is particularly useful for dynamic options.
|
||||||
prompt will be the option name capitalized.
|
:param show_envvar: controls if an environment variable should be shown on
|
||||||
|
the help page. Normally, environment variables
|
||||||
|
are not shown.
|
||||||
|
:param prompt: if set to `True` or a non empty string then the user will be
|
||||||
|
prompted for input. If set to `True` the prompt will be the
|
||||||
|
option name capitalized.
|
||||||
:param confirmation_prompt: if set then the value will need to be confirmed
|
:param confirmation_prompt: if set then the value will need to be confirmed
|
||||||
if it was prompted for.
|
if it was prompted for.
|
||||||
:param hide_input: if this is `True` then the input on the prompt will be
|
:param hide_input: if this is `True` then the input on the prompt will be
|
||||||
|
@ -1448,6 +1533,7 @@ class Option(Parameter):
|
||||||
variable in case a prefix is defined on the
|
variable in case a prefix is defined on the
|
||||||
context.
|
context.
|
||||||
:param help: the help string.
|
:param help: the help string.
|
||||||
|
:param hidden: hide this option from help outputs.
|
||||||
"""
|
"""
|
||||||
param_type_name = 'option'
|
param_type_name = 'option'
|
||||||
|
|
||||||
|
@ -1455,7 +1541,8 @@ class Option(Parameter):
|
||||||
prompt=False, confirmation_prompt=False,
|
prompt=False, confirmation_prompt=False,
|
||||||
hide_input=False, is_flag=None, flag_value=None,
|
hide_input=False, is_flag=None, flag_value=None,
|
||||||
multiple=False, count=False, allow_from_autoenv=True,
|
multiple=False, count=False, allow_from_autoenv=True,
|
||||||
type=None, help=None, **attrs):
|
type=None, help=None, hidden=False, show_choices=True,
|
||||||
|
show_envvar=False, **attrs):
|
||||||
default_is_missing = attrs.get('default', _missing) is _missing
|
default_is_missing = attrs.get('default', _missing) is _missing
|
||||||
Parameter.__init__(self, param_decls, type=type, **attrs)
|
Parameter.__init__(self, param_decls, type=type, **attrs)
|
||||||
|
|
||||||
|
@ -1468,6 +1555,7 @@ class Option(Parameter):
|
||||||
self.prompt = prompt_text
|
self.prompt = prompt_text
|
||||||
self.confirmation_prompt = confirmation_prompt
|
self.confirmation_prompt = confirmation_prompt
|
||||||
self.hide_input = hide_input
|
self.hide_input = hide_input
|
||||||
|
self.hidden = hidden
|
||||||
|
|
||||||
# Flags
|
# Flags
|
||||||
if is_flag is None:
|
if is_flag is None:
|
||||||
|
@ -1500,6 +1588,8 @@ class Option(Parameter):
|
||||||
self.allow_from_autoenv = allow_from_autoenv
|
self.allow_from_autoenv = allow_from_autoenv
|
||||||
self.help = help
|
self.help = help
|
||||||
self.show_default = show_default
|
self.show_default = show_default
|
||||||
|
self.show_choices = show_choices
|
||||||
|
self.show_envvar = show_envvar
|
||||||
|
|
||||||
# Sanity check for stuff we don't support
|
# Sanity check for stuff we don't support
|
||||||
if __debug__:
|
if __debug__:
|
||||||
|
@ -1548,8 +1638,8 @@ class Option(Parameter):
|
||||||
opts.append(decl)
|
opts.append(decl)
|
||||||
|
|
||||||
if name is None and possible_names:
|
if name is None and possible_names:
|
||||||
possible_names.sort(key=lambda x: len(x[0]))
|
possible_names.sort(key=lambda x: -len(x[0])) # group long options first
|
||||||
name = possible_names[-1][1].replace('-', '_').lower()
|
name = possible_names[0][1].replace('-', '_').lower()
|
||||||
if not isidentifier(name):
|
if not isidentifier(name):
|
||||||
name = None
|
name = None
|
||||||
|
|
||||||
|
@ -1595,6 +1685,8 @@ class Option(Parameter):
|
||||||
parser.add_option(self.opts, **kwargs)
|
parser.add_option(self.opts, **kwargs)
|
||||||
|
|
||||||
def get_help_record(self, ctx):
|
def get_help_record(self, ctx):
|
||||||
|
if self.hidden:
|
||||||
|
return
|
||||||
any_prefix_is_slash = []
|
any_prefix_is_slash = []
|
||||||
|
|
||||||
def _write_opts(opts):
|
def _write_opts(opts):
|
||||||
|
@ -1611,11 +1703,28 @@ class Option(Parameter):
|
||||||
|
|
||||||
help = self.help or ''
|
help = self.help or ''
|
||||||
extra = []
|
extra = []
|
||||||
|
if self.show_envvar:
|
||||||
|
envvar = self.envvar
|
||||||
|
if envvar is None:
|
||||||
|
if self.allow_from_autoenv and \
|
||||||
|
ctx.auto_envvar_prefix is not None:
|
||||||
|
envvar = '%s_%s' % (ctx.auto_envvar_prefix, self.name.upper())
|
||||||
|
if envvar is not None:
|
||||||
|
extra.append('env var: %s' % (
|
||||||
|
', '.join('%s' % d for d in envvar)
|
||||||
|
if isinstance(envvar, (list, tuple))
|
||||||
|
else envvar, ))
|
||||||
if self.default is not None and self.show_default:
|
if self.default is not None and self.show_default:
|
||||||
extra.append('default: %s' % (
|
if isinstance(self.show_default, string_types):
|
||||||
', '.join('%s' % d for d in self.default)
|
default_string = '({})'.format(self.show_default)
|
||||||
if isinstance(self.default, (list, tuple))
|
elif isinstance(self.default, (list, tuple)):
|
||||||
else self.default, ))
|
default_string = ', '.join('%s' % d for d in self.default)
|
||||||
|
elif inspect.isfunction(self.default):
|
||||||
|
default_string = "(dynamic)"
|
||||||
|
else:
|
||||||
|
default_string = self.default
|
||||||
|
extra.append('default: {}'.format(default_string))
|
||||||
|
|
||||||
if self.required:
|
if self.required:
|
||||||
extra.append('required')
|
extra.append('required')
|
||||||
if extra:
|
if extra:
|
||||||
|
@ -1649,8 +1758,8 @@ class Option(Parameter):
|
||||||
if self.is_bool_flag:
|
if self.is_bool_flag:
|
||||||
return confirm(self.prompt, default)
|
return confirm(self.prompt, default)
|
||||||
|
|
||||||
return prompt(self.prompt, default=default,
|
return prompt(self.prompt, default=default, type=self.type,
|
||||||
hide_input=self.hide_input,
|
hide_input=self.hide_input, show_choices=self.show_choices,
|
||||||
confirmation_prompt=self.confirmation_prompt,
|
confirmation_prompt=self.confirmation_prompt,
|
||||||
value_proc=lambda x: self.process_value(ctx, x))
|
value_proc=lambda x: self.process_value(ctx, x))
|
||||||
|
|
||||||
|
@ -1710,7 +1819,9 @@ class Argument(Parameter):
|
||||||
def make_metavar(self):
|
def make_metavar(self):
|
||||||
if self.metavar is not None:
|
if self.metavar is not None:
|
||||||
return self.metavar
|
return self.metavar
|
||||||
var = self.name.upper()
|
var = self.type.get_metavar(self)
|
||||||
|
if not var:
|
||||||
|
var = self.name.upper()
|
||||||
if not self.required:
|
if not self.required:
|
||||||
var = '[%s]' % var
|
var = '[%s]' % var
|
||||||
if self.nargs != 1:
|
if self.nargs != 1:
|
||||||
|
@ -1725,16 +1836,17 @@ class Argument(Parameter):
|
||||||
if len(decls) == 1:
|
if len(decls) == 1:
|
||||||
name = arg = decls[0]
|
name = arg = decls[0]
|
||||||
name = name.replace('-', '_').lower()
|
name = name.replace('-', '_').lower()
|
||||||
elif len(decls) == 2:
|
|
||||||
name, arg = decls
|
|
||||||
else:
|
else:
|
||||||
raise TypeError('Arguments take exactly one or two '
|
raise TypeError('Arguments take exactly one '
|
||||||
'parameter declarations, got %d' % len(decls))
|
'parameter declaration, got %d' % len(decls))
|
||||||
return name, [arg], []
|
return name, [arg], []
|
||||||
|
|
||||||
def get_usage_pieces(self, ctx):
|
def get_usage_pieces(self, ctx):
|
||||||
return [self.make_metavar()]
|
return [self.make_metavar()]
|
||||||
|
|
||||||
|
def get_error_hint(self, ctx):
|
||||||
|
return '"%s"' % self.make_metavar()
|
||||||
|
|
||||||
def add_to_parser(self, parser, ctx):
|
def add_to_parser(self, parser, ctx):
|
||||||
parser.add_argument(dest=self.name, nargs=self.nargs,
|
parser.add_argument(dest=self.name, nargs=self.nargs,
|
||||||
obj=self)
|
obj=self)
|
||||||
|
|
|
@ -61,7 +61,7 @@ def make_pass_decorator(object_type, ensure=False):
|
||||||
raise RuntimeError('Managed to invoke callback without a '
|
raise RuntimeError('Managed to invoke callback without a '
|
||||||
'context object of type %r existing'
|
'context object of type %r existing'
|
||||||
% object_type.__name__)
|
% object_type.__name__)
|
||||||
return ctx.invoke(f, obj, *args[1:], **kwargs)
|
return ctx.invoke(f, obj, *args, **kwargs)
|
||||||
return update_wrapper(new_func, f)
|
return update_wrapper(new_func, f)
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
@ -85,12 +85,12 @@ def _make_command(f, name, attrs, cls):
|
||||||
help = inspect.cleandoc(help)
|
help = inspect.cleandoc(help)
|
||||||
attrs['help'] = help
|
attrs['help'] = help
|
||||||
_check_for_unicode_literals()
|
_check_for_unicode_literals()
|
||||||
return cls(name=name or f.__name__.lower(),
|
return cls(name=name or f.__name__.lower().replace('_', '-'),
|
||||||
callback=f, params=params, **attrs)
|
callback=f, params=params, **attrs)
|
||||||
|
|
||||||
|
|
||||||
def command(name=None, cls=None, **attrs):
|
def command(name=None, cls=None, **attrs):
|
||||||
"""Creates a new :class:`Command` and uses the decorated function as
|
r"""Creates a new :class:`Command` and uses the decorated function as
|
||||||
callback. This will also automatically attach all decorated
|
callback. This will also automatically attach all decorated
|
||||||
:func:`option`\s and :func:`argument`\s as parameters to the command.
|
:func:`option`\s and :func:`argument`\s as parameters to the command.
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ def command(name=None, cls=None, **attrs):
|
||||||
command :class:`Group`.
|
command :class:`Group`.
|
||||||
|
|
||||||
:param name: the name of the command. This defaults to the function
|
:param name: the name of the command. This defaults to the function
|
||||||
name.
|
name with underscores replaced by dashes.
|
||||||
:param cls: the command class to instantiate. This defaults to
|
:param cls: the command class to instantiate. This defaults to
|
||||||
:class:`Command`.
|
:class:`Command`.
|
||||||
"""
|
"""
|
||||||
|
@ -164,10 +164,13 @@ def option(*param_decls, **attrs):
|
||||||
:class:`Option`.
|
:class:`Option`.
|
||||||
"""
|
"""
|
||||||
def decorator(f):
|
def decorator(f):
|
||||||
if 'help' in attrs:
|
# Issue 926, copy attrs, so pre-defined options can re-use the same cls=
|
||||||
attrs['help'] = inspect.cleandoc(attrs['help'])
|
option_attrs = attrs.copy()
|
||||||
OptionClass = attrs.pop('cls', Option)
|
|
||||||
_param_memo(f, OptionClass(param_decls, **attrs))
|
if 'help' in option_attrs:
|
||||||
|
option_attrs['help'] = inspect.cleandoc(option_attrs['help'])
|
||||||
|
OptionClass = option_attrs.pop('cls', Option)
|
||||||
|
_param_memo(f, OptionClass(param_decls, **option_attrs))
|
||||||
return f
|
return f
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
@ -235,7 +238,11 @@ def version_option(version=None, *param_decls, **attrs):
|
||||||
:param others: everything else is forwarded to :func:`option`.
|
:param others: everything else is forwarded to :func:`option`.
|
||||||
"""
|
"""
|
||||||
if version is None:
|
if version is None:
|
||||||
module = sys._getframe(1).f_globals.get('__name__')
|
if hasattr(sys, '_getframe'):
|
||||||
|
module = sys._getframe(1).f_globals.get('__name__')
|
||||||
|
else:
|
||||||
|
module = ''
|
||||||
|
|
||||||
def decorator(f):
|
def decorator(f):
|
||||||
prog_name = attrs.pop('prog_name', None)
|
prog_name = attrs.pop('prog_name', None)
|
||||||
message = attrs.pop('message', '%(prog)s, version %(version)s')
|
message = attrs.pop('message', '%(prog)s, version %(version)s')
|
||||||
|
|
|
@ -2,6 +2,12 @@ from ._compat import PY2, filename_to_ui, get_text_stderr
|
||||||
from .utils import echo
|
from .utils import echo
|
||||||
|
|
||||||
|
|
||||||
|
def _join_param_hints(param_hint):
|
||||||
|
if isinstance(param_hint, (tuple, list)):
|
||||||
|
return ' / '.join('"%s"' % x for x in param_hint)
|
||||||
|
return param_hint
|
||||||
|
|
||||||
|
|
||||||
class ClickException(Exception):
|
class ClickException(Exception):
|
||||||
"""An exception that Click can handle and show to the user."""
|
"""An exception that Click can handle and show to the user."""
|
||||||
|
|
||||||
|
@ -9,15 +15,25 @@ class ClickException(Exception):
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
|
ctor_msg = message
|
||||||
if PY2:
|
if PY2:
|
||||||
if message is not None:
|
if ctor_msg is not None:
|
||||||
message = message.encode('utf-8')
|
ctor_msg = ctor_msg.encode('utf-8')
|
||||||
Exception.__init__(self, message)
|
Exception.__init__(self, ctor_msg)
|
||||||
self.message = message
|
self.message = message
|
||||||
|
|
||||||
def format_message(self):
|
def format_message(self):
|
||||||
return self.message
|
return self.message
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.message
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
__unicode__ = __str__
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.message.encode('utf-8')
|
||||||
|
|
||||||
def show(self, file=None):
|
def show(self, file=None):
|
||||||
if file is None:
|
if file is None:
|
||||||
file = get_text_stderr()
|
file = get_text_stderr()
|
||||||
|
@ -37,14 +53,20 @@ class UsageError(ClickException):
|
||||||
def __init__(self, message, ctx=None):
|
def __init__(self, message, ctx=None):
|
||||||
ClickException.__init__(self, message)
|
ClickException.__init__(self, message)
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
|
self.cmd = self.ctx and self.ctx.command or None
|
||||||
|
|
||||||
def show(self, file=None):
|
def show(self, file=None):
|
||||||
if file is None:
|
if file is None:
|
||||||
file = get_text_stderr()
|
file = get_text_stderr()
|
||||||
color = None
|
color = None
|
||||||
|
hint = ''
|
||||||
|
if (self.cmd is not None and
|
||||||
|
self.cmd.get_help_option(self.ctx) is not None):
|
||||||
|
hint = ('Try "%s %s" for help.\n'
|
||||||
|
% (self.ctx.command_path, self.ctx.help_option_names[0]))
|
||||||
if self.ctx is not None:
|
if self.ctx is not None:
|
||||||
color = self.ctx.color
|
color = self.ctx.color
|
||||||
echo(self.ctx.get_usage() + '\n', file=file, color=color)
|
echo(self.ctx.get_usage() + '\n%s' % hint, file=file, color=color)
|
||||||
echo('Error: %s' % self.format_message(), file=file, color=color)
|
echo('Error: %s' % self.format_message(), file=file, color=color)
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,11 +98,11 @@ class BadParameter(UsageError):
|
||||||
if self.param_hint is not None:
|
if self.param_hint is not None:
|
||||||
param_hint = self.param_hint
|
param_hint = self.param_hint
|
||||||
elif self.param is not None:
|
elif self.param is not None:
|
||||||
param_hint = self.param.opts or [self.param.human_readable_name]
|
param_hint = self.param.get_error_hint(self.ctx)
|
||||||
else:
|
else:
|
||||||
return 'Invalid value: %s' % self.message
|
return 'Invalid value: %s' % self.message
|
||||||
if isinstance(param_hint, (tuple, list)):
|
param_hint = _join_param_hints(param_hint)
|
||||||
param_hint = ' / '.join('"%s"' % x for x in param_hint)
|
|
||||||
return 'Invalid value for %s: %s' % (param_hint, self.message)
|
return 'Invalid value for %s: %s' % (param_hint, self.message)
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,11 +127,10 @@ class MissingParameter(BadParameter):
|
||||||
if self.param_hint is not None:
|
if self.param_hint is not None:
|
||||||
param_hint = self.param_hint
|
param_hint = self.param_hint
|
||||||
elif self.param is not None:
|
elif self.param is not None:
|
||||||
param_hint = self.param.opts or [self.param.human_readable_name]
|
param_hint = self.param.get_error_hint(self.ctx)
|
||||||
else:
|
else:
|
||||||
param_hint = None
|
param_hint = None
|
||||||
if isinstance(param_hint, (tuple, list)):
|
param_hint = _join_param_hints(param_hint)
|
||||||
param_hint = ' / '.join('"%s"' % x for x in param_hint)
|
|
||||||
|
|
||||||
param_type = self.param_type
|
param_type = self.param_type
|
||||||
if param_type is None and self.param is not None:
|
if param_type is None and self.param is not None:
|
||||||
|
@ -164,10 +185,13 @@ class BadOptionUsage(UsageError):
|
||||||
for an option is not correct.
|
for an option is not correct.
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
.. versionadded:: 4.0
|
||||||
|
|
||||||
|
:param option_name: the name of the option being used incorrectly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, message, ctx=None):
|
def __init__(self, option_name, message, ctx=None):
|
||||||
UsageError.__init__(self, message, ctx)
|
UsageError.__init__(self, message, ctx)
|
||||||
|
self.option_name = option_name
|
||||||
|
|
||||||
|
|
||||||
class BadArgumentUsage(UsageError):
|
class BadArgumentUsage(UsageError):
|
||||||
|
@ -199,3 +223,13 @@ class FileError(ClickException):
|
||||||
|
|
||||||
class Abort(RuntimeError):
|
class Abort(RuntimeError):
|
||||||
"""An internal signalling exception that signals Click to abort."""
|
"""An internal signalling exception that signals Click to abort."""
|
||||||
|
|
||||||
|
|
||||||
|
class Exit(RuntimeError):
|
||||||
|
"""An exception that indicates that the application should exit with some
|
||||||
|
status code.
|
||||||
|
|
||||||
|
:param code: the status code to exit with.
|
||||||
|
"""
|
||||||
|
def __init__(self, code=0):
|
||||||
|
self.exit_code = code
|
||||||
|
|
|
@ -9,7 +9,7 @@ def get_current_context(silent=False):
|
||||||
access the current context object from anywhere. This is a more implicit
|
access the current context object from anywhere. This is a more implicit
|
||||||
alternative to the :func:`pass_context` decorator. This function is
|
alternative to the :func:`pass_context` decorator. This function is
|
||||||
primarily useful for helpers such as :func:`echo` which might be
|
primarily useful for helpers such as :func:`echo` which might be
|
||||||
interested in changing it's behavior based on the current context.
|
interested in changing its behavior based on the current context.
|
||||||
|
|
||||||
To push the current context, :meth:`Context.scope` can be used.
|
To push the current context, :meth:`Context.scope` can be used.
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
click.parser
|
click.parser
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
This module started out as largely a copy paste from the stdlib's
|
This module started out as largely a copy paste from the stdlib's
|
||||||
optparse module with the features removed that we do not need from
|
optparse module with the features removed that we do not need from
|
||||||
optparse because we implement them in Click on a higher level (for
|
optparse because we implement them in Click on a higher level (for
|
||||||
instance type handling, help formatting and a lot more).
|
instance type handling, help formatting and a lot more).
|
||||||
|
|
||||||
The plan is to remove more and more from here over time.
|
The plan is to remove more and more from here over time.
|
||||||
|
|
||||||
The reason this is a different module and not optparse from the stdlib
|
The reason this is a different module and not optparse from the stdlib
|
||||||
is that there are differences in 2.x and 3.x about the error messages
|
is that there are differences in 2.x and 3.x about the error messages
|
||||||
generated and optparse in the stdlib uses gettext for no good reason
|
generated and optparse in the stdlib uses gettext for no good reason
|
||||||
and might cause us issues.
|
and might cause us issues.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from .exceptions import UsageError, NoSuchOption, BadOptionUsage, \
|
from .exceptions import UsageError, NoSuchOption, BadOptionUsage, \
|
||||||
|
@ -74,8 +75,8 @@ def _unpack_args(args, nargs_spec):
|
||||||
|
|
||||||
def _error_opt_args(nargs, opt):
|
def _error_opt_args(nargs, opt):
|
||||||
if nargs == 1:
|
if nargs == 1:
|
||||||
raise BadOptionUsage('%s option requires an argument' % opt)
|
raise BadOptionUsage(opt, '%s option requires an argument' % opt)
|
||||||
raise BadOptionUsage('%s option requires %d arguments' % (opt, nargs))
|
raise BadOptionUsage(opt, '%s option requires %d arguments' % (opt, nargs))
|
||||||
|
|
||||||
|
|
||||||
def split_opt(opt):
|
def split_opt(opt):
|
||||||
|
@ -321,7 +322,7 @@ class OptionParser(object):
|
||||||
if opt not in self._long_opt:
|
if opt not in self._long_opt:
|
||||||
possibilities = [word for word in self._long_opt
|
possibilities = [word for word in self._long_opt
|
||||||
if word.startswith(opt)]
|
if word.startswith(opt)]
|
||||||
raise NoSuchOption(opt, possibilities=possibilities)
|
raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx)
|
||||||
|
|
||||||
option = self._long_opt[opt]
|
option = self._long_opt[opt]
|
||||||
if option.takes_value:
|
if option.takes_value:
|
||||||
|
@ -342,7 +343,7 @@ class OptionParser(object):
|
||||||
del state.rargs[:nargs]
|
del state.rargs[:nargs]
|
||||||
|
|
||||||
elif explicit_value is not None:
|
elif explicit_value is not None:
|
||||||
raise BadOptionUsage('%s option does not take a value' % opt)
|
raise BadOptionUsage(opt, '%s option does not take a value' % opt)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
value = None
|
value = None
|
||||||
|
@ -364,7 +365,7 @@ class OptionParser(object):
|
||||||
if self.ignore_unknown_options:
|
if self.ignore_unknown_options:
|
||||||
unknown_options.append(ch)
|
unknown_options.append(ch)
|
||||||
continue
|
continue
|
||||||
raise NoSuchOption(opt)
|
raise NoSuchOption(opt, ctx=self.ctx)
|
||||||
if option.takes_value:
|
if option.takes_value:
|
||||||
# Any characters left in arg? Pretend they're the
|
# Any characters left in arg? Pretend they're the
|
||||||
# next arg, and stop consuming characters of arg.
|
# next arg, and stop consuming characters of arg.
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import struct
|
import struct
|
||||||
|
import inspect
|
||||||
|
import itertools
|
||||||
|
|
||||||
from ._compat import raw_input, text_type, string_types, \
|
from ._compat import raw_input, text_type, string_types, \
|
||||||
isatty, strip_ansi, get_winterm_size, DEFAULT_COLUMNS, WIN
|
isatty, strip_ansi, get_winterm_size, DEFAULT_COLUMNS, WIN
|
||||||
from .utils import echo
|
from .utils import echo
|
||||||
from .exceptions import Abort, UsageError
|
from .exceptions import Abort, UsageError
|
||||||
from .types import convert_type
|
from .types import convert_type, Choice, Path
|
||||||
from .globals import resolve_color_default
|
from .globals import resolve_color_default
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,8 +16,25 @@ from .globals import resolve_color_default
|
||||||
# functions to customize how they work.
|
# functions to customize how they work.
|
||||||
visible_prompt_func = raw_input
|
visible_prompt_func = raw_input
|
||||||
|
|
||||||
_ansi_colors = ('black', 'red', 'green', 'yellow', 'blue', 'magenta',
|
_ansi_colors = {
|
||||||
'cyan', 'white', 'reset')
|
'black': 30,
|
||||||
|
'red': 31,
|
||||||
|
'green': 32,
|
||||||
|
'yellow': 33,
|
||||||
|
'blue': 34,
|
||||||
|
'magenta': 35,
|
||||||
|
'cyan': 36,
|
||||||
|
'white': 37,
|
||||||
|
'reset': 39,
|
||||||
|
'bright_black': 90,
|
||||||
|
'bright_red': 91,
|
||||||
|
'bright_green': 92,
|
||||||
|
'bright_yellow': 93,
|
||||||
|
'bright_blue': 94,
|
||||||
|
'bright_magenta': 95,
|
||||||
|
'bright_cyan': 96,
|
||||||
|
'bright_white': 97,
|
||||||
|
}
|
||||||
_ansi_reset_all = '\033[0m'
|
_ansi_reset_all = '\033[0m'
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,23 +43,27 @@ def hidden_prompt_func(prompt):
|
||||||
return getpass.getpass(prompt)
|
return getpass.getpass(prompt)
|
||||||
|
|
||||||
|
|
||||||
def _build_prompt(text, suffix, show_default=False, default=None):
|
def _build_prompt(text, suffix, show_default=False, default=None, show_choices=True, type=None):
|
||||||
prompt = text
|
prompt = text
|
||||||
|
if type is not None and show_choices and isinstance(type, Choice):
|
||||||
|
prompt += ' (' + ", ".join(map(str, type.choices)) + ')'
|
||||||
if default is not None and show_default:
|
if default is not None and show_default:
|
||||||
prompt = '%s [%s]' % (prompt, default)
|
prompt = '%s [%s]' % (prompt, default)
|
||||||
return prompt + suffix
|
return prompt + suffix
|
||||||
|
|
||||||
|
|
||||||
def prompt(text, default=None, hide_input=False,
|
def prompt(text, default=None, hide_input=False, confirmation_prompt=False,
|
||||||
confirmation_prompt=False, type=None,
|
type=None, value_proc=None, prompt_suffix=': ', show_default=True,
|
||||||
value_proc=None, prompt_suffix=': ',
|
err=False, show_choices=True):
|
||||||
show_default=True, err=False):
|
|
||||||
"""Prompts a user for input. This is a convenience function that can
|
"""Prompts a user for input. This is a convenience function that can
|
||||||
be used to prompt a user for input later.
|
be used to prompt a user for input later.
|
||||||
|
|
||||||
If the user aborts the input by sending a interrupt signal, this
|
If the user aborts the input by sending a interrupt signal, this
|
||||||
function will catch it and raise a :exc:`Abort` exception.
|
function will catch it and raise a :exc:`Abort` exception.
|
||||||
|
|
||||||
|
.. versionadded:: 7.0
|
||||||
|
Added the show_choices parameter.
|
||||||
|
|
||||||
.. versionadded:: 6.0
|
.. versionadded:: 6.0
|
||||||
Added unicode support for cmd.exe on Windows.
|
Added unicode support for cmd.exe on Windows.
|
||||||
|
|
||||||
|
@ -61,6 +84,10 @@ def prompt(text, default=None, hide_input=False,
|
||||||
:param show_default: shows or hides the default value in the prompt.
|
:param show_default: shows or hides the default value in the prompt.
|
||||||
:param err: if set to true the file defaults to ``stderr`` instead of
|
:param err: if set to true the file defaults to ``stderr`` instead of
|
||||||
``stdout``, the same as with echo.
|
``stdout``, the same as with echo.
|
||||||
|
:param show_choices: Show or hide choices if the passed type is a Choice.
|
||||||
|
For example if type is a Choice of either day or week,
|
||||||
|
show_choices is true and text is "Group by" then the
|
||||||
|
prompt will be "Group by (day, week): ".
|
||||||
"""
|
"""
|
||||||
result = None
|
result = None
|
||||||
|
|
||||||
|
@ -82,17 +109,18 @@ def prompt(text, default=None, hide_input=False,
|
||||||
if value_proc is None:
|
if value_proc is None:
|
||||||
value_proc = convert_type(type, default)
|
value_proc = convert_type(type, default)
|
||||||
|
|
||||||
prompt = _build_prompt(text, prompt_suffix, show_default, default)
|
prompt = _build_prompt(text, prompt_suffix, show_default, default, show_choices, type)
|
||||||
|
|
||||||
while 1:
|
while 1:
|
||||||
while 1:
|
while 1:
|
||||||
value = prompt_func(prompt)
|
value = prompt_func(prompt)
|
||||||
if value:
|
if value:
|
||||||
break
|
break
|
||||||
# If a default is set and used, then the confirmation
|
|
||||||
# prompt is always skipped because that's the only thing
|
|
||||||
# that really makes sense.
|
|
||||||
elif default is not None:
|
elif default is not None:
|
||||||
|
if isinstance(value_proc, Path):
|
||||||
|
# validate Path default value(exists, dir_okay etc.)
|
||||||
|
value = default
|
||||||
|
break
|
||||||
return default
|
return default
|
||||||
try:
|
try:
|
||||||
result = value_proc(value)
|
result = value_proc(value)
|
||||||
|
@ -166,8 +194,14 @@ def get_terminal_size():
|
||||||
sz = shutil_get_terminal_size()
|
sz = shutil_get_terminal_size()
|
||||||
return sz.columns, sz.lines
|
return sz.columns, sz.lines
|
||||||
|
|
||||||
|
# We provide a sensible default for get_winterm_size() when being invoked
|
||||||
|
# inside a subprocess. Without this, it would not provide a useful input.
|
||||||
if get_winterm_size is not None:
|
if get_winterm_size is not None:
|
||||||
return get_winterm_size()
|
size = get_winterm_size()
|
||||||
|
if size == (0, 0):
|
||||||
|
return (79, 24)
|
||||||
|
else:
|
||||||
|
return size
|
||||||
|
|
||||||
def ioctl_gwinsz(fd):
|
def ioctl_gwinsz(fd):
|
||||||
try:
|
try:
|
||||||
|
@ -195,22 +229,33 @@ def get_terminal_size():
|
||||||
return int(cr[1]), int(cr[0])
|
return int(cr[1]), int(cr[0])
|
||||||
|
|
||||||
|
|
||||||
def echo_via_pager(text, color=None):
|
def echo_via_pager(text_or_generator, color=None):
|
||||||
"""This function takes a text and shows it via an environment specific
|
"""This function takes a text and shows it via an environment specific
|
||||||
pager on stdout.
|
pager on stdout.
|
||||||
|
|
||||||
.. versionchanged:: 3.0
|
.. versionchanged:: 3.0
|
||||||
Added the `color` flag.
|
Added the `color` flag.
|
||||||
|
|
||||||
:param text: the text to page.
|
:param text_or_generator: the text to page, or alternatively, a
|
||||||
|
generator emitting the text to page.
|
||||||
:param color: controls if the pager supports ANSI colors or not. The
|
:param color: controls if the pager supports ANSI colors or not. The
|
||||||
default is autodetection.
|
default is autodetection.
|
||||||
"""
|
"""
|
||||||
color = resolve_color_default(color)
|
color = resolve_color_default(color)
|
||||||
if not isinstance(text, string_types):
|
|
||||||
text = text_type(text)
|
if inspect.isgeneratorfunction(text_or_generator):
|
||||||
|
i = text_or_generator()
|
||||||
|
elif isinstance(text_or_generator, string_types):
|
||||||
|
i = [text_or_generator]
|
||||||
|
else:
|
||||||
|
i = iter(text_or_generator)
|
||||||
|
|
||||||
|
# convert every element of i to a text type if necessary
|
||||||
|
text_generator = (el if isinstance(el, string_types) else text_type(el)
|
||||||
|
for el in i)
|
||||||
|
|
||||||
from ._termui_impl import pager
|
from ._termui_impl import pager
|
||||||
return pager(text + '\n', color)
|
return pager(itertools.chain(text_generator, "\n"), color)
|
||||||
|
|
||||||
|
|
||||||
def progressbar(iterable=None, length=None, label=None, show_eta=True,
|
def progressbar(iterable=None, length=None, label=None, show_eta=True,
|
||||||
|
@ -347,10 +392,21 @@ def style(text, fg=None, bg=None, bold=None, dim=None, underline=None,
|
||||||
* ``magenta``
|
* ``magenta``
|
||||||
* ``cyan``
|
* ``cyan``
|
||||||
* ``white`` (might be light gray)
|
* ``white`` (might be light gray)
|
||||||
|
* ``bright_black``
|
||||||
|
* ``bright_red``
|
||||||
|
* ``bright_green``
|
||||||
|
* ``bright_yellow``
|
||||||
|
* ``bright_blue``
|
||||||
|
* ``bright_magenta``
|
||||||
|
* ``bright_cyan``
|
||||||
|
* ``bright_white``
|
||||||
* ``reset`` (reset the color code only)
|
* ``reset`` (reset the color code only)
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. versionadded:: 7.0
|
||||||
|
Added support for bright colors.
|
||||||
|
|
||||||
:param text: the string to style with ansi codes.
|
:param text: the string to style with ansi codes.
|
||||||
:param fg: if provided this will become the foreground color.
|
:param fg: if provided this will become the foreground color.
|
||||||
:param bg: if provided this will become the background color.
|
:param bg: if provided this will become the background color.
|
||||||
|
@ -369,13 +425,13 @@ def style(text, fg=None, bg=None, bold=None, dim=None, underline=None,
|
||||||
bits = []
|
bits = []
|
||||||
if fg:
|
if fg:
|
||||||
try:
|
try:
|
||||||
bits.append('\033[%dm' % (_ansi_colors.index(fg) + 30))
|
bits.append('\033[%dm' % (_ansi_colors[fg]))
|
||||||
except ValueError:
|
except KeyError:
|
||||||
raise TypeError('Unknown color %r' % fg)
|
raise TypeError('Unknown color %r' % fg)
|
||||||
if bg:
|
if bg:
|
||||||
try:
|
try:
|
||||||
bits.append('\033[%dm' % (_ansi_colors.index(bg) + 40))
|
bits.append('\033[%dm' % (_ansi_colors[bg] + 10))
|
||||||
except ValueError:
|
except KeyError:
|
||||||
raise TypeError('Unknown color %r' % bg)
|
raise TypeError('Unknown color %r' % bg)
|
||||||
if bold is not None:
|
if bold is not None:
|
||||||
bits.append('\033[%dm' % (1 if bold else 22))
|
bits.append('\033[%dm' % (1 if bold else 22))
|
||||||
|
@ -405,7 +461,7 @@ def unstyle(text):
|
||||||
return strip_ansi(text)
|
return strip_ansi(text)
|
||||||
|
|
||||||
|
|
||||||
def secho(text, file=None, nl=True, err=False, color=None, **styles):
|
def secho(message=None, file=None, nl=True, err=False, color=None, **styles):
|
||||||
"""This function combines :func:`echo` and :func:`style` into one
|
"""This function combines :func:`echo` and :func:`style` into one
|
||||||
call. As such the following two calls are the same::
|
call. As such the following two calls are the same::
|
||||||
|
|
||||||
|
@ -417,7 +473,9 @@ def secho(text, file=None, nl=True, err=False, color=None, **styles):
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
"""
|
"""
|
||||||
return echo(style(text, **styles), file=file, nl=nl, err=err, color=color)
|
if message is not None:
|
||||||
|
message = style(message, **styles)
|
||||||
|
return echo(message, file=file, nl=nl, err=err, color=color)
|
||||||
|
|
||||||
|
|
||||||
def edit(text=None, editor=None, env=None, require_save=True,
|
def edit(text=None, editor=None, env=None, require_save=True,
|
||||||
|
@ -466,7 +524,7 @@ def launch(url, wait=False, locate=False):
|
||||||
|
|
||||||
Examples::
|
Examples::
|
||||||
|
|
||||||
click.launch('http://click.pocoo.org/')
|
click.launch('https://click.palletsprojects.com/')
|
||||||
click.launch('/my/downloaded/file', locate=True)
|
click.launch('/my/downloaded/file', locate=True)
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
|
@ -499,6 +557,10 @@ def getchar(echo=False):
|
||||||
Note that this will always read from the terminal, even if something
|
Note that this will always read from the terminal, even if something
|
||||||
is piped into the standard input.
|
is piped into the standard input.
|
||||||
|
|
||||||
|
Note for Windows: in rare cases when typing non-ASCII characters, this
|
||||||
|
function might wait for a second character and then return both at once.
|
||||||
|
This is because certain Unicode characters look like special-key markers.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
:param echo: if set to `True`, the character read will also show up on
|
:param echo: if set to `True`, the character read will also show up on
|
||||||
|
@ -510,6 +572,11 @@ def getchar(echo=False):
|
||||||
return f(echo)
|
return f(echo)
|
||||||
|
|
||||||
|
|
||||||
|
def raw_terminal():
|
||||||
|
from ._termui_impl import raw_terminal as f
|
||||||
|
return f()
|
||||||
|
|
||||||
|
|
||||||
def pause(info='Press any key to continue ...', err=False):
|
def pause(info='Press any key to continue ...', err=False):
|
||||||
"""This command stops execution and waits for the user to press any
|
"""This command stops execution and waits for the user to press any
|
||||||
key to continue. This is similar to the Windows batch "pause"
|
key to continue. This is similar to the Windows batch "pause"
|
||||||
|
|
|
@ -3,8 +3,9 @@ import sys
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import shlex
|
||||||
|
|
||||||
from ._compat import iteritems, PY2
|
from ._compat import iteritems, PY2, string_types
|
||||||
|
|
||||||
|
|
||||||
# If someone wants to vendor click, we want to ensure the
|
# If someone wants to vendor click, we want to ensure the
|
||||||
|
@ -72,27 +73,44 @@ def make_input_stream(input, charset):
|
||||||
class Result(object):
|
class Result(object):
|
||||||
"""Holds the captured result of an invoked CLI script."""
|
"""Holds the captured result of an invoked CLI script."""
|
||||||
|
|
||||||
def __init__(self, runner, output_bytes, exit_code, exception,
|
def __init__(self, runner, stdout_bytes, stderr_bytes, exit_code,
|
||||||
exc_info=None):
|
exception, exc_info=None):
|
||||||
#: The runner that created the result
|
#: The runner that created the result
|
||||||
self.runner = runner
|
self.runner = runner
|
||||||
#: The output as bytes.
|
#: The standard output as bytes.
|
||||||
self.output_bytes = output_bytes
|
self.stdout_bytes = stdout_bytes
|
||||||
|
#: The standard error as bytes, or False(y) if not available
|
||||||
|
self.stderr_bytes = stderr_bytes
|
||||||
#: The exit code as integer.
|
#: The exit code as integer.
|
||||||
self.exit_code = exit_code
|
self.exit_code = exit_code
|
||||||
#: The exception that happend if one did.
|
#: The exception that happened if one did.
|
||||||
self.exception = exception
|
self.exception = exception
|
||||||
#: The traceback
|
#: The traceback
|
||||||
self.exc_info = exc_info
|
self.exc_info = exc_info
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def output(self):
|
def output(self):
|
||||||
"""The output as unicode string."""
|
"""The (standard) output as unicode string."""
|
||||||
return self.output_bytes.decode(self.runner.charset, 'replace') \
|
return self.stdout
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stdout(self):
|
||||||
|
"""The standard output as unicode string."""
|
||||||
|
return self.stdout_bytes.decode(self.runner.charset, 'replace') \
|
||||||
.replace('\r\n', '\n')
|
.replace('\r\n', '\n')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stderr(self):
|
||||||
|
"""The standard error as unicode string."""
|
||||||
|
if not self.stderr_bytes:
|
||||||
|
raise ValueError("stderr not separately captured")
|
||||||
|
return self.stderr_bytes.decode(self.runner.charset, 'replace') \
|
||||||
|
.replace('\r\n', '\n')
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Result %s>' % (
|
return '<%s %s>' % (
|
||||||
|
type(self).__name__,
|
||||||
self.exception and repr(self.exception) or 'okay',
|
self.exception and repr(self.exception) or 'okay',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -111,14 +129,21 @@ class CliRunner(object):
|
||||||
to stdout. This is useful for showing examples in
|
to stdout. This is useful for showing examples in
|
||||||
some circumstances. Note that regular prompts
|
some circumstances. Note that regular prompts
|
||||||
will automatically echo the input.
|
will automatically echo the input.
|
||||||
|
:param mix_stderr: if this is set to `False`, then stdout and stderr are
|
||||||
|
preserved as independent streams. This is useful for
|
||||||
|
Unix-philosophy apps that have predictable stdout and
|
||||||
|
noisy stderr, such that each may be measured
|
||||||
|
independently
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, charset=None, env=None, echo_stdin=False):
|
def __init__(self, charset=None, env=None, echo_stdin=False,
|
||||||
|
mix_stderr=True):
|
||||||
if charset is None:
|
if charset is None:
|
||||||
charset = 'utf-8'
|
charset = 'utf-8'
|
||||||
self.charset = charset
|
self.charset = charset
|
||||||
self.env = env or {}
|
self.env = env or {}
|
||||||
self.echo_stdin = echo_stdin
|
self.echo_stdin = echo_stdin
|
||||||
|
self.mix_stderr = mix_stderr
|
||||||
|
|
||||||
def get_default_prog_name(self, cli):
|
def get_default_prog_name(self, cli):
|
||||||
"""Given a command object it will return the default program name
|
"""Given a command object it will return the default program name
|
||||||
|
@ -163,16 +188,27 @@ class CliRunner(object):
|
||||||
env = self.make_env(env)
|
env = self.make_env(env)
|
||||||
|
|
||||||
if PY2:
|
if PY2:
|
||||||
sys.stdout = sys.stderr = bytes_output = StringIO()
|
bytes_output = StringIO()
|
||||||
if self.echo_stdin:
|
if self.echo_stdin:
|
||||||
input = EchoingStdin(input, bytes_output)
|
input = EchoingStdin(input, bytes_output)
|
||||||
|
sys.stdout = bytes_output
|
||||||
|
if not self.mix_stderr:
|
||||||
|
bytes_error = StringIO()
|
||||||
|
sys.stderr = bytes_error
|
||||||
else:
|
else:
|
||||||
bytes_output = io.BytesIO()
|
bytes_output = io.BytesIO()
|
||||||
if self.echo_stdin:
|
if self.echo_stdin:
|
||||||
input = EchoingStdin(input, bytes_output)
|
input = EchoingStdin(input, bytes_output)
|
||||||
input = io.TextIOWrapper(input, encoding=self.charset)
|
input = io.TextIOWrapper(input, encoding=self.charset)
|
||||||
sys.stdout = sys.stderr = io.TextIOWrapper(
|
sys.stdout = io.TextIOWrapper(
|
||||||
bytes_output, encoding=self.charset)
|
bytes_output, encoding=self.charset)
|
||||||
|
if not self.mix_stderr:
|
||||||
|
bytes_error = io.BytesIO()
|
||||||
|
sys.stderr = io.TextIOWrapper(
|
||||||
|
bytes_error, encoding=self.charset)
|
||||||
|
|
||||||
|
if self.mix_stderr:
|
||||||
|
sys.stderr = sys.stdout
|
||||||
|
|
||||||
sys.stdin = input
|
sys.stdin = input
|
||||||
|
|
||||||
|
@ -196,6 +232,7 @@ class CliRunner(object):
|
||||||
return char
|
return char
|
||||||
|
|
||||||
default_color = color
|
default_color = color
|
||||||
|
|
||||||
def should_strip_ansi(stream=None, color=None):
|
def should_strip_ansi(stream=None, color=None):
|
||||||
if color is None:
|
if color is None:
|
||||||
return not default_color
|
return not default_color
|
||||||
|
@ -221,7 +258,7 @@ class CliRunner(object):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
os.environ[key] = value
|
os.environ[key] = value
|
||||||
yield bytes_output
|
yield (bytes_output, not self.mix_stderr and bytes_error)
|
||||||
finally:
|
finally:
|
||||||
for key, value in iteritems(old_env):
|
for key, value in iteritems(old_env):
|
||||||
if value is None:
|
if value is None:
|
||||||
|
@ -241,7 +278,7 @@ class CliRunner(object):
|
||||||
clickpkg.formatting.FORCED_WIDTH = old_forced_width
|
clickpkg.formatting.FORCED_WIDTH = old_forced_width
|
||||||
|
|
||||||
def invoke(self, cli, args=None, input=None, env=None,
|
def invoke(self, cli, args=None, input=None, env=None,
|
||||||
catch_exceptions=True, color=False, **extra):
|
catch_exceptions=True, color=False, mix_stderr=False, **extra):
|
||||||
"""Invokes a command in an isolated environment. The arguments are
|
"""Invokes a command in an isolated environment. The arguments are
|
||||||
forwarded directly to the command line script, the `extra` keyword
|
forwarded directly to the command line script, the `extra` keyword
|
||||||
arguments are passed to the :meth:`~clickpkg.Command.main` function of
|
arguments are passed to the :meth:`~clickpkg.Command.main` function of
|
||||||
|
@ -260,7 +297,10 @@ class CliRunner(object):
|
||||||
The ``color`` parameter was added.
|
The ``color`` parameter was added.
|
||||||
|
|
||||||
:param cli: the command to invoke
|
:param cli: the command to invoke
|
||||||
:param args: the arguments to invoke
|
:param args: the arguments to invoke. It may be given as an iterable
|
||||||
|
or a string. When given as string it will be interpreted
|
||||||
|
as a Unix shell command. More details at
|
||||||
|
:func:`shlex.split`.
|
||||||
:param input: the input data for `sys.stdin`.
|
:param input: the input data for `sys.stdin`.
|
||||||
:param env: the environment overrides.
|
:param env: the environment overrides.
|
||||||
:param catch_exceptions: Whether to catch any other exceptions than
|
:param catch_exceptions: Whether to catch any other exceptions than
|
||||||
|
@ -270,36 +310,48 @@ class CliRunner(object):
|
||||||
application can still override this explicitly.
|
application can still override this explicitly.
|
||||||
"""
|
"""
|
||||||
exc_info = None
|
exc_info = None
|
||||||
with self.isolation(input=input, env=env, color=color) as out:
|
with self.isolation(input=input, env=env, color=color) as outstreams:
|
||||||
exception = None
|
exception = None
|
||||||
exit_code = 0
|
exit_code = 0
|
||||||
|
|
||||||
|
if isinstance(args, string_types):
|
||||||
|
args = shlex.split(args)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cli.main(args=args or (),
|
prog_name = extra.pop("prog_name")
|
||||||
prog_name=self.get_default_prog_name(cli), **extra)
|
except KeyError:
|
||||||
|
prog_name = self.get_default_prog_name(cli)
|
||||||
|
|
||||||
|
try:
|
||||||
|
cli.main(args=args or (), prog_name=prog_name, **extra)
|
||||||
except SystemExit as e:
|
except SystemExit as e:
|
||||||
if e.code != 0:
|
exc_info = sys.exc_info()
|
||||||
|
exit_code = e.code
|
||||||
|
if exit_code is None:
|
||||||
|
exit_code = 0
|
||||||
|
|
||||||
|
if exit_code != 0:
|
||||||
exception = e
|
exception = e
|
||||||
|
|
||||||
exc_info = sys.exc_info()
|
|
||||||
|
|
||||||
exit_code = e.code
|
|
||||||
if not isinstance(exit_code, int):
|
if not isinstance(exit_code, int):
|
||||||
sys.stdout.write(str(exit_code))
|
sys.stdout.write(str(exit_code))
|
||||||
sys.stdout.write('\n')
|
sys.stdout.write('\n')
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if not catch_exceptions:
|
if not catch_exceptions:
|
||||||
raise
|
raise
|
||||||
exception = e
|
exception = e
|
||||||
exit_code = -1
|
exit_code = 1
|
||||||
exc_info = sys.exc_info()
|
exc_info = sys.exc_info()
|
||||||
finally:
|
finally:
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
output = out.getvalue()
|
stdout = outstreams[0].getvalue()
|
||||||
|
stderr = outstreams[1] and outstreams[1].getvalue()
|
||||||
|
|
||||||
return Result(runner=self,
|
return Result(runner=self,
|
||||||
output_bytes=output,
|
stdout_bytes=stdout,
|
||||||
|
stderr_bytes=stderr,
|
||||||
exit_code=exit_code,
|
exit_code=exit_code,
|
||||||
exception=exception,
|
exception=exception,
|
||||||
exc_info=exc_info)
|
exc_info=exc_info)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import stat
|
import stat
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from ._compat import open_stream, text_type, filename_to_ui, \
|
from ._compat import open_stream, text_type, filename_to_ui, \
|
||||||
get_filesystem_encoding, get_streerror, _get_argv_encoding, PY2
|
get_filesystem_encoding, get_streerror, _get_argv_encoding, PY2
|
||||||
|
@ -126,34 +127,54 @@ class StringParamType(ParamType):
|
||||||
|
|
||||||
|
|
||||||
class Choice(ParamType):
|
class Choice(ParamType):
|
||||||
"""The choice type allows a value to be checked against a fixed set of
|
"""The choice type allows a value to be checked against a fixed set
|
||||||
supported values. All of these values have to be strings.
|
of supported values. All of these values have to be strings.
|
||||||
|
|
||||||
|
You should only pass a list or tuple of choices. Other iterables
|
||||||
|
(like generators) may lead to surprising results.
|
||||||
|
|
||||||
See :ref:`choice-opts` for an example.
|
See :ref:`choice-opts` for an example.
|
||||||
|
|
||||||
|
:param case_sensitive: Set to false to make choices case
|
||||||
|
insensitive. Defaults to true.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = 'choice'
|
name = 'choice'
|
||||||
|
|
||||||
def __init__(self, choices):
|
def __init__(self, choices, case_sensitive=True):
|
||||||
self.choices = choices
|
self.choices = choices
|
||||||
|
self.case_sensitive = case_sensitive
|
||||||
|
|
||||||
def get_metavar(self, param):
|
def get_metavar(self, param):
|
||||||
return '[%s]' % '|'.join(self.choices)
|
return '[%s]' % '|'.join(self.choices)
|
||||||
|
|
||||||
def get_missing_message(self, param):
|
def get_missing_message(self, param):
|
||||||
return 'Choose from %s.' % ', '.join(self.choices)
|
return 'Choose from:\n\t%s.' % ',\n\t'.join(self.choices)
|
||||||
|
|
||||||
def convert(self, value, param, ctx):
|
def convert(self, value, param, ctx):
|
||||||
# Exact match
|
# Exact match
|
||||||
if value in self.choices:
|
if value in self.choices:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
# Match through normalization
|
# Match through normalization and case sensitivity
|
||||||
|
# first do token_normalize_func, then lowercase
|
||||||
|
# preserve original `value` to produce an accurate message in
|
||||||
|
# `self.fail`
|
||||||
|
normed_value = value
|
||||||
|
normed_choices = self.choices
|
||||||
|
|
||||||
if ctx is not None and \
|
if ctx is not None and \
|
||||||
ctx.token_normalize_func is not None:
|
ctx.token_normalize_func is not None:
|
||||||
value = ctx.token_normalize_func(value)
|
normed_value = ctx.token_normalize_func(value)
|
||||||
for choice in self.choices:
|
normed_choices = [ctx.token_normalize_func(choice) for choice in
|
||||||
if ctx.token_normalize_func(choice) == value:
|
self.choices]
|
||||||
return choice
|
|
||||||
|
if not self.case_sensitive:
|
||||||
|
normed_value = normed_value.lower()
|
||||||
|
normed_choices = [choice.lower() for choice in normed_choices]
|
||||||
|
|
||||||
|
if normed_value in normed_choices:
|
||||||
|
return normed_value
|
||||||
|
|
||||||
self.fail('invalid choice: %s. (choose from %s)' %
|
self.fail('invalid choice: %s. (choose from %s)' %
|
||||||
(value, ', '.join(self.choices)), param, ctx)
|
(value, ', '.join(self.choices)), param, ctx)
|
||||||
|
@ -162,6 +183,59 @@ class Choice(ParamType):
|
||||||
return 'Choice(%r)' % list(self.choices)
|
return 'Choice(%r)' % list(self.choices)
|
||||||
|
|
||||||
|
|
||||||
|
class DateTime(ParamType):
|
||||||
|
"""The DateTime type converts date strings into `datetime` objects.
|
||||||
|
|
||||||
|
The format strings which are checked are configurable, but default to some
|
||||||
|
common (non-timezone aware) ISO 8601 formats.
|
||||||
|
|
||||||
|
When specifying *DateTime* formats, you should only pass a list or a tuple.
|
||||||
|
Other iterables, like generators, may lead to surprising results.
|
||||||
|
|
||||||
|
The format strings are processed using ``datetime.strptime``, and this
|
||||||
|
consequently defines the format strings which are allowed.
|
||||||
|
|
||||||
|
Parsing is tried using each format, in order, and the first format which
|
||||||
|
parses successfully is used.
|
||||||
|
|
||||||
|
:param formats: A list or tuple of date format strings, in the order in
|
||||||
|
which they should be tried. Defaults to
|
||||||
|
``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``,
|
||||||
|
``'%Y-%m-%d %H:%M:%S'``.
|
||||||
|
"""
|
||||||
|
name = 'datetime'
|
||||||
|
|
||||||
|
def __init__(self, formats=None):
|
||||||
|
self.formats = formats or [
|
||||||
|
'%Y-%m-%d',
|
||||||
|
'%Y-%m-%dT%H:%M:%S',
|
||||||
|
'%Y-%m-%d %H:%M:%S'
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_metavar(self, param):
|
||||||
|
return '[{}]'.format('|'.join(self.formats))
|
||||||
|
|
||||||
|
def _try_to_convert_date(self, value, format):
|
||||||
|
try:
|
||||||
|
return datetime.strptime(value, format)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
# Exact match
|
||||||
|
for format in self.formats:
|
||||||
|
dtime = self._try_to_convert_date(value, format)
|
||||||
|
if dtime:
|
||||||
|
return dtime
|
||||||
|
|
||||||
|
self.fail(
|
||||||
|
'invalid datetime format: {}. (choose from {})'.format(
|
||||||
|
value, ', '.join(self.formats)))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'DateTime'
|
||||||
|
|
||||||
|
|
||||||
class IntParamType(ParamType):
|
class IntParamType(ParamType):
|
||||||
name = 'integer'
|
name = 'integer'
|
||||||
|
|
||||||
|
@ -214,23 +288,6 @@ class IntRange(IntParamType):
|
||||||
return 'IntRange(%r, %r)' % (self.min, self.max)
|
return 'IntRange(%r, %r)' % (self.min, self.max)
|
||||||
|
|
||||||
|
|
||||||
class BoolParamType(ParamType):
|
|
||||||
name = 'boolean'
|
|
||||||
|
|
||||||
def convert(self, value, param, ctx):
|
|
||||||
if isinstance(value, bool):
|
|
||||||
return bool(value)
|
|
||||||
value = value.lower()
|
|
||||||
if value in ('true', '1', 'yes', 'y'):
|
|
||||||
return True
|
|
||||||
elif value in ('false', '0', 'no', 'n'):
|
|
||||||
return False
|
|
||||||
self.fail('%s is not a valid boolean' % value, param, ctx)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'BOOL'
|
|
||||||
|
|
||||||
|
|
||||||
class FloatParamType(ParamType):
|
class FloatParamType(ParamType):
|
||||||
name = 'float'
|
name = 'float'
|
||||||
|
|
||||||
|
@ -245,6 +302,62 @@ class FloatParamType(ParamType):
|
||||||
return 'FLOAT'
|
return 'FLOAT'
|
||||||
|
|
||||||
|
|
||||||
|
class FloatRange(FloatParamType):
|
||||||
|
"""A parameter that works similar to :data:`click.FLOAT` but restricts
|
||||||
|
the value to fit into a range. The default behavior is to fail if the
|
||||||
|
value falls outside the range, but it can also be silently clamped
|
||||||
|
between the two edges.
|
||||||
|
|
||||||
|
See :ref:`ranges` for an example.
|
||||||
|
"""
|
||||||
|
name = 'float range'
|
||||||
|
|
||||||
|
def __init__(self, min=None, max=None, clamp=False):
|
||||||
|
self.min = min
|
||||||
|
self.max = max
|
||||||
|
self.clamp = clamp
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
rv = FloatParamType.convert(self, value, param, ctx)
|
||||||
|
if self.clamp:
|
||||||
|
if self.min is not None and rv < self.min:
|
||||||
|
return self.min
|
||||||
|
if self.max is not None and rv > self.max:
|
||||||
|
return self.max
|
||||||
|
if self.min is not None and rv < self.min or \
|
||||||
|
self.max is not None and rv > self.max:
|
||||||
|
if self.min is None:
|
||||||
|
self.fail('%s is bigger than the maximum valid value '
|
||||||
|
'%s.' % (rv, self.max), param, ctx)
|
||||||
|
elif self.max is None:
|
||||||
|
self.fail('%s is smaller than the minimum valid value '
|
||||||
|
'%s.' % (rv, self.min), param, ctx)
|
||||||
|
else:
|
||||||
|
self.fail('%s is not in the valid range of %s to %s.'
|
||||||
|
% (rv, self.min, self.max), param, ctx)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'FloatRange(%r, %r)' % (self.min, self.max)
|
||||||
|
|
||||||
|
|
||||||
|
class BoolParamType(ParamType):
|
||||||
|
name = 'boolean'
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
if isinstance(value, bool):
|
||||||
|
return bool(value)
|
||||||
|
value = value.lower()
|
||||||
|
if value in ('true', 't', '1', 'yes', 'y'):
|
||||||
|
return True
|
||||||
|
elif value in ('false', 'f', '0', 'no', 'n'):
|
||||||
|
return False
|
||||||
|
self.fail('%s is not a valid boolean' % value, param, ctx)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'BOOL'
|
||||||
|
|
||||||
|
|
||||||
class UUIDParameterType(ParamType):
|
class UUIDParameterType(ParamType):
|
||||||
name = 'uuid'
|
name = 'uuid'
|
||||||
|
|
||||||
|
@ -273,9 +386,12 @@ class File(ParamType):
|
||||||
opened in binary mode or for writing. The encoding parameter can be used
|
opened in binary mode or for writing. The encoding parameter can be used
|
||||||
to force a specific encoding.
|
to force a specific encoding.
|
||||||
|
|
||||||
The `lazy` flag controls if the file should be opened immediately or
|
The `lazy` flag controls if the file should be opened immediately or upon
|
||||||
upon first IO. The default is to be non lazy for standard input and
|
first IO. The default is to be non-lazy for standard input and output
|
||||||
output streams as well as files opened for reading, lazy otherwise.
|
streams as well as files opened for reading, `lazy` otherwise. When opening a
|
||||||
|
file lazily for reading, it is still opened temporarily for validation, but
|
||||||
|
will not be held open until first IO. lazy is mainly useful when opening
|
||||||
|
for writing to avoid creating the file until it is needed.
|
||||||
|
|
||||||
Starting with Click 2.0, files can also be opened atomically in which
|
Starting with Click 2.0, files can also be opened atomically in which
|
||||||
case all writes go into a separate file in the same folder and upon
|
case all writes go into a separate file in the same folder and upon
|
||||||
|
@ -358,14 +474,16 @@ class Path(ParamType):
|
||||||
:param readable: if true, a readable check is performed.
|
:param readable: if true, a readable check is performed.
|
||||||
:param resolve_path: if this is true, then the path is fully resolved
|
:param resolve_path: if this is true, then the path is fully resolved
|
||||||
before the value is passed onwards. This means
|
before the value is passed onwards. This means
|
||||||
that it's absolute and symlinks are resolved.
|
that it's absolute and symlinks are resolved. It
|
||||||
|
will not expand a tilde-prefix, as this is
|
||||||
|
supposed to be done by the shell only.
|
||||||
:param allow_dash: If this is set to `True`, a single dash to indicate
|
:param allow_dash: If this is set to `True`, a single dash to indicate
|
||||||
standard streams is permitted.
|
standard streams is permitted.
|
||||||
:param type: optionally a string type that should be used to
|
:param path_type: optionally a string type that should be used to
|
||||||
represent the path. The default is `None` which
|
represent the path. The default is `None` which
|
||||||
means the return value will be either bytes or
|
means the return value will be either bytes or
|
||||||
unicode depending on what makes most sense given the
|
unicode depending on what makes most sense given the
|
||||||
input data Click deals with.
|
input data Click deals with.
|
||||||
"""
|
"""
|
||||||
envvar_list_splitter = os.path.pathsep
|
envvar_list_splitter = os.path.pathsep
|
||||||
|
|
||||||
|
@ -384,7 +502,7 @@ class Path(ParamType):
|
||||||
if self.file_okay and not self.dir_okay:
|
if self.file_okay and not self.dir_okay:
|
||||||
self.name = 'file'
|
self.name = 'file'
|
||||||
self.path_type = 'File'
|
self.path_type = 'File'
|
||||||
if self.dir_okay and not self.file_okay:
|
elif self.dir_okay and not self.file_okay:
|
||||||
self.name = 'directory'
|
self.name = 'directory'
|
||||||
self.path_type = 'Directory'
|
self.path_type = 'Directory'
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -43,6 +43,7 @@ def make_str(value):
|
||||||
|
|
||||||
|
|
||||||
def make_default_short_help(help, max_length=45):
|
def make_default_short_help(help, max_length=45):
|
||||||
|
"""Return a condensed version of help string."""
|
||||||
words = help.split()
|
words = help.split()
|
||||||
total_length = 0
|
total_length = 0
|
||||||
result = []
|
result = []
|
||||||
|
@ -171,7 +172,7 @@ def echo(message=None, file=None, nl=True, err=False, color=None):
|
||||||
|
|
||||||
Primarily it means that you can print binary data as well as Unicode
|
Primarily it means that you can print binary data as well as Unicode
|
||||||
data on both 2.x and 3.x to the given file in the most appropriate way
|
data on both 2.x and 3.x to the given file in the most appropriate way
|
||||||
possible. This is a very carefree function as in that it will try its
|
possible. This is a very carefree function in that it will try its
|
||||||
best to not fail. As of Click 6.0 this includes support for unicode
|
best to not fail. As of Click 6.0 this includes support for unicode
|
||||||
output on the Windows console.
|
output on the Windows console.
|
||||||
|
|
||||||
|
@ -183,7 +184,7 @@ def echo(message=None, file=None, nl=True, err=False, color=None):
|
||||||
- hide ANSI codes automatically if the destination file is not a
|
- hide ANSI codes automatically if the destination file is not a
|
||||||
terminal.
|
terminal.
|
||||||
|
|
||||||
.. _colorama: http://pypi.python.org/pypi/colorama
|
.. _colorama: https://pypi.org/project/colorama/
|
||||||
|
|
||||||
.. versionchanged:: 6.0
|
.. versionchanged:: 6.0
|
||||||
As of Click 6.0 the echo function will properly support unicode
|
As of Click 6.0 the echo function will properly support unicode
|
||||||
|
@ -413,3 +414,27 @@ def get_app_dir(app_name, roaming=True, force_posix=False):
|
||||||
return os.path.join(
|
return os.path.join(
|
||||||
os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')),
|
os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')),
|
||||||
_posixify(app_name))
|
_posixify(app_name))
|
||||||
|
|
||||||
|
|
||||||
|
class PacifyFlushWrapper(object):
|
||||||
|
"""This wrapper is used to catch and suppress BrokenPipeErrors resulting
|
||||||
|
from ``.flush()`` being called on broken pipe during the shutdown/final-GC
|
||||||
|
of the Python interpreter. Notably ``.flush()`` is always called on
|
||||||
|
``sys.stdout`` and ``sys.stderr``. So as to have minimal impact on any
|
||||||
|
other cleanup code, and the case where the underlying file is not a broken
|
||||||
|
pipe, all calls and attributes are proxied.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, wrapped):
|
||||||
|
self.wrapped = wrapped
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
try:
|
||||||
|
self.wrapped.flush()
|
||||||
|
except IOError as e:
|
||||||
|
import errno
|
||||||
|
if e.errno != errno.EPIPE:
|
||||||
|
raise
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self.wrapped, attr)
|
||||||
|
|
|
@ -0,0 +1,327 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
markupsafe
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
Implements an escape function and a Markup string to replace HTML
|
||||||
|
special characters with safe representations.
|
||||||
|
|
||||||
|
:copyright: 2010 Pallets
|
||||||
|
:license: BSD-3-Clause
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
import string
|
||||||
|
|
||||||
|
from ._compat import int_types
|
||||||
|
from ._compat import iteritems
|
||||||
|
from ._compat import Mapping
|
||||||
|
from ._compat import PY2
|
||||||
|
from ._compat import string_types
|
||||||
|
from ._compat import text_type
|
||||||
|
from ._compat import unichr
|
||||||
|
|
||||||
|
__version__ = "1.1.1"
|
||||||
|
|
||||||
|
__all__ = ["Markup", "soft_unicode", "escape", "escape_silent"]
|
||||||
|
|
||||||
|
_striptags_re = re.compile(r"(<!--.*?-->|<[^>]*>)")
|
||||||
|
_entity_re = re.compile(r"&([^& ;]+);")
|
||||||
|
|
||||||
|
|
||||||
|
class Markup(text_type):
|
||||||
|
"""A string that is ready to be safely inserted into an HTML or XML
|
||||||
|
document, either because it was escaped or because it was marked
|
||||||
|
safe.
|
||||||
|
|
||||||
|
Passing an object to the constructor converts it to text and wraps
|
||||||
|
it to mark it safe without escaping. To escape the text, use the
|
||||||
|
:meth:`escape` class method instead.
|
||||||
|
|
||||||
|
>>> Markup('Hello, <em>World</em>!')
|
||||||
|
Markup('Hello, <em>World</em>!')
|
||||||
|
>>> Markup(42)
|
||||||
|
Markup('42')
|
||||||
|
>>> Markup.escape('Hello, <em>World</em>!')
|
||||||
|
Markup('Hello <em>World</em>!')
|
||||||
|
|
||||||
|
This implements the ``__html__()`` interface that some frameworks
|
||||||
|
use. Passing an object that implements ``__html__()`` will wrap the
|
||||||
|
output of that method, marking it safe.
|
||||||
|
|
||||||
|
>>> class Foo:
|
||||||
|
... def __html__(self):
|
||||||
|
... return '<a href="/foo">foo</a>'
|
||||||
|
...
|
||||||
|
>>> Markup(Foo())
|
||||||
|
Markup('<a href="/foo">foo</a>')
|
||||||
|
|
||||||
|
This is a subclass of the text type (``str`` in Python 3,
|
||||||
|
``unicode`` in Python 2). It has the same methods as that type, but
|
||||||
|
all methods escape their arguments and return a ``Markup`` instance.
|
||||||
|
|
||||||
|
>>> Markup('<em>%s</em>') % 'foo & bar'
|
||||||
|
Markup('<em>foo & bar</em>')
|
||||||
|
>>> Markup('<em>Hello</em> ') + '<foo>'
|
||||||
|
Markup('<em>Hello</em> <foo>')
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def __new__(cls, base=u"", encoding=None, errors="strict"):
|
||||||
|
if hasattr(base, "__html__"):
|
||||||
|
base = base.__html__()
|
||||||
|
if encoding is None:
|
||||||
|
return text_type.__new__(cls, base)
|
||||||
|
return text_type.__new__(cls, base, encoding, errors)
|
||||||
|
|
||||||
|
def __html__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
if isinstance(other, string_types) or hasattr(other, "__html__"):
|
||||||
|
return self.__class__(super(Markup, self).__add__(self.escape(other)))
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __radd__(self, other):
|
||||||
|
if hasattr(other, "__html__") or isinstance(other, string_types):
|
||||||
|
return self.escape(other).__add__(self)
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __mul__(self, num):
|
||||||
|
if isinstance(num, int_types):
|
||||||
|
return self.__class__(text_type.__mul__(self, num))
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
__rmul__ = __mul__
|
||||||
|
|
||||||
|
def __mod__(self, arg):
|
||||||
|
if isinstance(arg, tuple):
|
||||||
|
arg = tuple(_MarkupEscapeHelper(x, self.escape) for x in arg)
|
||||||
|
else:
|
||||||
|
arg = _MarkupEscapeHelper(arg, self.escape)
|
||||||
|
return self.__class__(text_type.__mod__(self, arg))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s(%s)" % (self.__class__.__name__, text_type.__repr__(self))
|
||||||
|
|
||||||
|
def join(self, seq):
|
||||||
|
return self.__class__(text_type.join(self, map(self.escape, seq)))
|
||||||
|
|
||||||
|
join.__doc__ = text_type.join.__doc__
|
||||||
|
|
||||||
|
def split(self, *args, **kwargs):
|
||||||
|
return list(map(self.__class__, text_type.split(self, *args, **kwargs)))
|
||||||
|
|
||||||
|
split.__doc__ = text_type.split.__doc__
|
||||||
|
|
||||||
|
def rsplit(self, *args, **kwargs):
|
||||||
|
return list(map(self.__class__, text_type.rsplit(self, *args, **kwargs)))
|
||||||
|
|
||||||
|
rsplit.__doc__ = text_type.rsplit.__doc__
|
||||||
|
|
||||||
|
def splitlines(self, *args, **kwargs):
|
||||||
|
return list(map(self.__class__, text_type.splitlines(self, *args, **kwargs)))
|
||||||
|
|
||||||
|
splitlines.__doc__ = text_type.splitlines.__doc__
|
||||||
|
|
||||||
|
def unescape(self):
|
||||||
|
"""Convert escaped markup back into a text string. This replaces
|
||||||
|
HTML entities with the characters they represent.
|
||||||
|
|
||||||
|
>>> Markup('Main » <em>About</em>').unescape()
|
||||||
|
'Main » <em>About</em>'
|
||||||
|
"""
|
||||||
|
from ._constants import HTML_ENTITIES
|
||||||
|
|
||||||
|
def handle_match(m):
|
||||||
|
name = m.group(1)
|
||||||
|
if name in HTML_ENTITIES:
|
||||||
|
return unichr(HTML_ENTITIES[name])
|
||||||
|
try:
|
||||||
|
if name[:2] in ("#x", "#X"):
|
||||||
|
return unichr(int(name[2:], 16))
|
||||||
|
elif name.startswith("#"):
|
||||||
|
return unichr(int(name[1:]))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
# Don't modify unexpected input.
|
||||||
|
return m.group()
|
||||||
|
|
||||||
|
return _entity_re.sub(handle_match, text_type(self))
|
||||||
|
|
||||||
|
def striptags(self):
|
||||||
|
""":meth:`unescape` the markup, remove tags, and normalize
|
||||||
|
whitespace to single spaces.
|
||||||
|
|
||||||
|
>>> Markup('Main »\t<em>About</em>').striptags()
|
||||||
|
'Main » About'
|
||||||
|
"""
|
||||||
|
stripped = u" ".join(_striptags_re.sub("", self).split())
|
||||||
|
return Markup(stripped).unescape()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def escape(cls, s):
|
||||||
|
"""Escape a string. Calls :func:`escape` and ensures that for
|
||||||
|
subclasses the correct type is returned.
|
||||||
|
"""
|
||||||
|
rv = escape(s)
|
||||||
|
if rv.__class__ is not cls:
|
||||||
|
return cls(rv)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def make_simple_escaping_wrapper(name): # noqa: B902
|
||||||
|
orig = getattr(text_type, name)
|
||||||
|
|
||||||
|
def func(self, *args, **kwargs):
|
||||||
|
args = _escape_argspec(list(args), enumerate(args), self.escape)
|
||||||
|
_escape_argspec(kwargs, iteritems(kwargs), self.escape)
|
||||||
|
return self.__class__(orig(self, *args, **kwargs))
|
||||||
|
|
||||||
|
func.__name__ = orig.__name__
|
||||||
|
func.__doc__ = orig.__doc__
|
||||||
|
return func
|
||||||
|
|
||||||
|
for method in (
|
||||||
|
"__getitem__",
|
||||||
|
"capitalize",
|
||||||
|
"title",
|
||||||
|
"lower",
|
||||||
|
"upper",
|
||||||
|
"replace",
|
||||||
|
"ljust",
|
||||||
|
"rjust",
|
||||||
|
"lstrip",
|
||||||
|
"rstrip",
|
||||||
|
"center",
|
||||||
|
"strip",
|
||||||
|
"translate",
|
||||||
|
"expandtabs",
|
||||||
|
"swapcase",
|
||||||
|
"zfill",
|
||||||
|
):
|
||||||
|
locals()[method] = make_simple_escaping_wrapper(method)
|
||||||
|
|
||||||
|
def partition(self, sep):
|
||||||
|
return tuple(map(self.__class__, text_type.partition(self, self.escape(sep))))
|
||||||
|
|
||||||
|
def rpartition(self, sep):
|
||||||
|
return tuple(map(self.__class__, text_type.rpartition(self, self.escape(sep))))
|
||||||
|
|
||||||
|
def format(self, *args, **kwargs):
|
||||||
|
formatter = EscapeFormatter(self.escape)
|
||||||
|
kwargs = _MagicFormatMapping(args, kwargs)
|
||||||
|
return self.__class__(formatter.vformat(self, args, kwargs))
|
||||||
|
|
||||||
|
def __html_format__(self, format_spec):
|
||||||
|
if format_spec:
|
||||||
|
raise ValueError("Unsupported format specification " "for Markup.")
|
||||||
|
return self
|
||||||
|
|
||||||
|
# not in python 3
|
||||||
|
if hasattr(text_type, "__getslice__"):
|
||||||
|
__getslice__ = make_simple_escaping_wrapper("__getslice__")
|
||||||
|
|
||||||
|
del method, make_simple_escaping_wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class _MagicFormatMapping(Mapping):
|
||||||
|
"""This class implements a dummy wrapper to fix a bug in the Python
|
||||||
|
standard library for string formatting.
|
||||||
|
|
||||||
|
See http://bugs.python.org/issue13598 for information about why
|
||||||
|
this is necessary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, args, kwargs):
|
||||||
|
self._args = args
|
||||||
|
self._kwargs = kwargs
|
||||||
|
self._last_index = 0
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if key == "":
|
||||||
|
idx = self._last_index
|
||||||
|
self._last_index += 1
|
||||||
|
try:
|
||||||
|
return self._args[idx]
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
key = str(idx)
|
||||||
|
return self._kwargs[key]
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._kwargs)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
if hasattr(text_type, "format"):
|
||||||
|
|
||||||
|
class EscapeFormatter(string.Formatter):
|
||||||
|
def __init__(self, escape):
|
||||||
|
self.escape = escape
|
||||||
|
|
||||||
|
def format_field(self, value, format_spec):
|
||||||
|
if hasattr(value, "__html_format__"):
|
||||||
|
rv = value.__html_format__(format_spec)
|
||||||
|
elif hasattr(value, "__html__"):
|
||||||
|
if format_spec:
|
||||||
|
raise ValueError(
|
||||||
|
"Format specifier {0} given, but {1} does not"
|
||||||
|
" define __html_format__. A class that defines"
|
||||||
|
" __html__ must define __html_format__ to work"
|
||||||
|
" with format specifiers.".format(format_spec, type(value))
|
||||||
|
)
|
||||||
|
rv = value.__html__()
|
||||||
|
else:
|
||||||
|
# We need to make sure the format spec is unicode here as
|
||||||
|
# otherwise the wrong callback methods are invoked. For
|
||||||
|
# instance a byte string there would invoke __str__ and
|
||||||
|
# not __unicode__.
|
||||||
|
rv = string.Formatter.format_field(self, value, text_type(format_spec))
|
||||||
|
return text_type(self.escape(rv))
|
||||||
|
|
||||||
|
|
||||||
|
def _escape_argspec(obj, iterable, escape):
|
||||||
|
"""Helper for various string-wrapped functions."""
|
||||||
|
for key, value in iterable:
|
||||||
|
if hasattr(value, "__html__") or isinstance(value, string_types):
|
||||||
|
obj[key] = escape(value)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class _MarkupEscapeHelper(object):
|
||||||
|
"""Helper for Markup.__mod__"""
|
||||||
|
|
||||||
|
def __init__(self, obj, escape):
|
||||||
|
self.obj = obj
|
||||||
|
self.escape = escape
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
return _MarkupEscapeHelper(self.obj[item], self.escape)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return text_type(self.escape(self.obj))
|
||||||
|
|
||||||
|
__unicode__ = __str__
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self.escape(repr(self.obj)))
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
return int(self.obj)
|
||||||
|
|
||||||
|
def __float__(self):
|
||||||
|
return float(self.obj)
|
||||||
|
|
||||||
|
|
||||||
|
# we have to import it down here as the speedups and native
|
||||||
|
# modules imports the markup type which is define above.
|
||||||
|
try:
|
||||||
|
from ._speedups import escape, escape_silent, soft_unicode
|
||||||
|
except ImportError:
|
||||||
|
from ._native import escape, escape_silent, soft_unicode
|
||||||
|
|
||||||
|
if not PY2:
|
||||||
|
soft_str = soft_unicode
|
||||||
|
__all__.append("soft_str")
|
|
@ -0,0 +1,33 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
markupsafe._compat
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
:copyright: 2010 Pallets
|
||||||
|
:license: BSD-3-Clause
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
PY2 = sys.version_info[0] == 2
|
||||||
|
|
||||||
|
if not PY2:
|
||||||
|
text_type = str
|
||||||
|
string_types = (str,)
|
||||||
|
unichr = chr
|
||||||
|
int_types = (int,)
|
||||||
|
|
||||||
|
def iteritems(x):
|
||||||
|
return iter(x.items())
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
|
|
||||||
|
else:
|
||||||
|
text_type = unicode
|
||||||
|
string_types = (str, unicode)
|
||||||
|
unichr = unichr
|
||||||
|
int_types = (int, long)
|
||||||
|
|
||||||
|
def iteritems(x):
|
||||||
|
return x.iteritems()
|
||||||
|
|
||||||
|
from collections import Mapping
|
|
@ -0,0 +1,264 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
markupsafe._constants
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
:copyright: 2010 Pallets
|
||||||
|
:license: BSD-3-Clause
|
||||||
|
"""
|
||||||
|
|
||||||
|
HTML_ENTITIES = {
|
||||||
|
"AElig": 198,
|
||||||
|
"Aacute": 193,
|
||||||
|
"Acirc": 194,
|
||||||
|
"Agrave": 192,
|
||||||
|
"Alpha": 913,
|
||||||
|
"Aring": 197,
|
||||||
|
"Atilde": 195,
|
||||||
|
"Auml": 196,
|
||||||
|
"Beta": 914,
|
||||||
|
"Ccedil": 199,
|
||||||
|
"Chi": 935,
|
||||||
|
"Dagger": 8225,
|
||||||
|
"Delta": 916,
|
||||||
|
"ETH": 208,
|
||||||
|
"Eacute": 201,
|
||||||
|
"Ecirc": 202,
|
||||||
|
"Egrave": 200,
|
||||||
|
"Epsilon": 917,
|
||||||
|
"Eta": 919,
|
||||||
|
"Euml": 203,
|
||||||
|
"Gamma": 915,
|
||||||
|
"Iacute": 205,
|
||||||
|
"Icirc": 206,
|
||||||
|
"Igrave": 204,
|
||||||
|
"Iota": 921,
|
||||||
|
"Iuml": 207,
|
||||||
|
"Kappa": 922,
|
||||||
|
"Lambda": 923,
|
||||||
|
"Mu": 924,
|
||||||
|
"Ntilde": 209,
|
||||||
|
"Nu": 925,
|
||||||
|
"OElig": 338,
|
||||||
|
"Oacute": 211,
|
||||||
|
"Ocirc": 212,
|
||||||
|
"Ograve": 210,
|
||||||
|
"Omega": 937,
|
||||||
|
"Omicron": 927,
|
||||||
|
"Oslash": 216,
|
||||||
|
"Otilde": 213,
|
||||||
|
"Ouml": 214,
|
||||||
|
"Phi": 934,
|
||||||
|
"Pi": 928,
|
||||||
|
"Prime": 8243,
|
||||||
|
"Psi": 936,
|
||||||
|
"Rho": 929,
|
||||||
|
"Scaron": 352,
|
||||||
|
"Sigma": 931,
|
||||||
|
"THORN": 222,
|
||||||
|
"Tau": 932,
|
||||||
|
"Theta": 920,
|
||||||
|
"Uacute": 218,
|
||||||
|
"Ucirc": 219,
|
||||||
|
"Ugrave": 217,
|
||||||
|
"Upsilon": 933,
|
||||||
|
"Uuml": 220,
|
||||||
|
"Xi": 926,
|
||||||
|
"Yacute": 221,
|
||||||
|
"Yuml": 376,
|
||||||
|
"Zeta": 918,
|
||||||
|
"aacute": 225,
|
||||||
|
"acirc": 226,
|
||||||
|
"acute": 180,
|
||||||
|
"aelig": 230,
|
||||||
|
"agrave": 224,
|
||||||
|
"alefsym": 8501,
|
||||||
|
"alpha": 945,
|
||||||
|
"amp": 38,
|
||||||
|
"and": 8743,
|
||||||
|
"ang": 8736,
|
||||||
|
"apos": 39,
|
||||||
|
"aring": 229,
|
||||||
|
"asymp": 8776,
|
||||||
|
"atilde": 227,
|
||||||
|
"auml": 228,
|
||||||
|
"bdquo": 8222,
|
||||||
|
"beta": 946,
|
||||||
|
"brvbar": 166,
|
||||||
|
"bull": 8226,
|
||||||
|
"cap": 8745,
|
||||||
|
"ccedil": 231,
|
||||||
|
"cedil": 184,
|
||||||
|
"cent": 162,
|
||||||
|
"chi": 967,
|
||||||
|
"circ": 710,
|
||||||
|
"clubs": 9827,
|
||||||
|
"cong": 8773,
|
||||||
|
"copy": 169,
|
||||||
|
"crarr": 8629,
|
||||||
|
"cup": 8746,
|
||||||
|
"curren": 164,
|
||||||
|
"dArr": 8659,
|
||||||
|
"dagger": 8224,
|
||||||
|
"darr": 8595,
|
||||||
|
"deg": 176,
|
||||||
|
"delta": 948,
|
||||||
|
"diams": 9830,
|
||||||
|
"divide": 247,
|
||||||
|
"eacute": 233,
|
||||||
|
"ecirc": 234,
|
||||||
|
"egrave": 232,
|
||||||
|
"empty": 8709,
|
||||||
|
"emsp": 8195,
|
||||||
|
"ensp": 8194,
|
||||||
|
"epsilon": 949,
|
||||||
|
"equiv": 8801,
|
||||||
|
"eta": 951,
|
||||||
|
"eth": 240,
|
||||||
|
"euml": 235,
|
||||||
|
"euro": 8364,
|
||||||
|
"exist": 8707,
|
||||||
|
"fnof": 402,
|
||||||
|
"forall": 8704,
|
||||||
|
"frac12": 189,
|
||||||
|
"frac14": 188,
|
||||||
|
"frac34": 190,
|
||||||
|
"frasl": 8260,
|
||||||
|
"gamma": 947,
|
||||||
|
"ge": 8805,
|
||||||
|
"gt": 62,
|
||||||
|
"hArr": 8660,
|
||||||
|
"harr": 8596,
|
||||||
|
"hearts": 9829,
|
||||||
|
"hellip": 8230,
|
||||||
|
"iacute": 237,
|
||||||
|
"icirc": 238,
|
||||||
|
"iexcl": 161,
|
||||||
|
"igrave": 236,
|
||||||
|
"image": 8465,
|
||||||
|
"infin": 8734,
|
||||||
|
"int": 8747,
|
||||||
|
"iota": 953,
|
||||||
|
"iquest": 191,
|
||||||
|
"isin": 8712,
|
||||||
|
"iuml": 239,
|
||||||
|
"kappa": 954,
|
||||||
|
"lArr": 8656,
|
||||||
|
"lambda": 955,
|
||||||
|
"lang": 9001,
|
||||||
|
"laquo": 171,
|
||||||
|
"larr": 8592,
|
||||||
|
"lceil": 8968,
|
||||||
|
"ldquo": 8220,
|
||||||
|
"le": 8804,
|
||||||
|
"lfloor": 8970,
|
||||||
|
"lowast": 8727,
|
||||||
|
"loz": 9674,
|
||||||
|
"lrm": 8206,
|
||||||
|
"lsaquo": 8249,
|
||||||
|
"lsquo": 8216,
|
||||||
|
"lt": 60,
|
||||||
|
"macr": 175,
|
||||||
|
"mdash": 8212,
|
||||||
|
"micro": 181,
|
||||||
|
"middot": 183,
|
||||||
|
"minus": 8722,
|
||||||
|
"mu": 956,
|
||||||
|
"nabla": 8711,
|
||||||
|
"nbsp": 160,
|
||||||
|
"ndash": 8211,
|
||||||
|
"ne": 8800,
|
||||||
|
"ni": 8715,
|
||||||
|
"not": 172,
|
||||||
|
"notin": 8713,
|
||||||
|
"nsub": 8836,
|
||||||
|
"ntilde": 241,
|
||||||
|
"nu": 957,
|
||||||
|
"oacute": 243,
|
||||||
|
"ocirc": 244,
|
||||||
|
"oelig": 339,
|
||||||
|
"ograve": 242,
|
||||||
|
"oline": 8254,
|
||||||
|
"omega": 969,
|
||||||
|
"omicron": 959,
|
||||||
|
"oplus": 8853,
|
||||||
|
"or": 8744,
|
||||||
|
"ordf": 170,
|
||||||
|
"ordm": 186,
|
||||||
|
"oslash": 248,
|
||||||
|
"otilde": 245,
|
||||||
|
"otimes": 8855,
|
||||||
|
"ouml": 246,
|
||||||
|
"para": 182,
|
||||||
|
"part": 8706,
|
||||||
|
"permil": 8240,
|
||||||
|
"perp": 8869,
|
||||||
|
"phi": 966,
|
||||||
|
"pi": 960,
|
||||||
|
"piv": 982,
|
||||||
|
"plusmn": 177,
|
||||||
|
"pound": 163,
|
||||||
|
"prime": 8242,
|
||||||
|
"prod": 8719,
|
||||||
|
"prop": 8733,
|
||||||
|
"psi": 968,
|
||||||
|
"quot": 34,
|
||||||
|
"rArr": 8658,
|
||||||
|
"radic": 8730,
|
||||||
|
"rang": 9002,
|
||||||
|
"raquo": 187,
|
||||||
|
"rarr": 8594,
|
||||||
|
"rceil": 8969,
|
||||||
|
"rdquo": 8221,
|
||||||
|
"real": 8476,
|
||||||
|
"reg": 174,
|
||||||
|
"rfloor": 8971,
|
||||||
|
"rho": 961,
|
||||||
|
"rlm": 8207,
|
||||||
|
"rsaquo": 8250,
|
||||||
|
"rsquo": 8217,
|
||||||
|
"sbquo": 8218,
|
||||||
|
"scaron": 353,
|
||||||
|
"sdot": 8901,
|
||||||
|
"sect": 167,
|
||||||
|
"shy": 173,
|
||||||
|
"sigma": 963,
|
||||||
|
"sigmaf": 962,
|
||||||
|
"sim": 8764,
|
||||||
|
"spades": 9824,
|
||||||
|
"sub": 8834,
|
||||||
|
"sube": 8838,
|
||||||
|
"sum": 8721,
|
||||||
|
"sup": 8835,
|
||||||
|
"sup1": 185,
|
||||||
|
"sup2": 178,
|
||||||
|
"sup3": 179,
|
||||||
|
"supe": 8839,
|
||||||
|
"szlig": 223,
|
||||||
|
"tau": 964,
|
||||||
|
"there4": 8756,
|
||||||
|
"theta": 952,
|
||||||
|
"thetasym": 977,
|
||||||
|
"thinsp": 8201,
|
||||||
|
"thorn": 254,
|
||||||
|
"tilde": 732,
|
||||||
|
"times": 215,
|
||||||
|
"trade": 8482,
|
||||||
|
"uArr": 8657,
|
||||||
|
"uacute": 250,
|
||||||
|
"uarr": 8593,
|
||||||
|
"ucirc": 251,
|
||||||
|
"ugrave": 249,
|
||||||
|
"uml": 168,
|
||||||
|
"upsih": 978,
|
||||||
|
"upsilon": 965,
|
||||||
|
"uuml": 252,
|
||||||
|
"weierp": 8472,
|
||||||
|
"xi": 958,
|
||||||
|
"yacute": 253,
|
||||||
|
"yen": 165,
|
||||||
|
"yuml": 255,
|
||||||
|
"zeta": 950,
|
||||||
|
"zwj": 8205,
|
||||||
|
"zwnj": 8204,
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
markupsafe._native
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Native Python implementation used when the C module is not compiled.
|
||||||
|
|
||||||
|
:copyright: 2010 Pallets
|
||||||
|
:license: BSD-3-Clause
|
||||||
|
"""
|
||||||
|
from . import Markup
|
||||||
|
from ._compat import text_type
|
||||||
|
|
||||||
|
|
||||||
|
def escape(s):
|
||||||
|
"""Replace the characters ``&``, ``<``, ``>``, ``'``, and ``"`` in
|
||||||
|
the string with HTML-safe sequences. Use this if you need to display
|
||||||
|
text that might contain such characters in HTML.
|
||||||
|
|
||||||
|
If the object has an ``__html__`` method, it is called and the
|
||||||
|
return value is assumed to already be safe for HTML.
|
||||||
|
|
||||||
|
:param s: An object to be converted to a string and escaped.
|
||||||
|
:return: A :class:`Markup` string with the escaped text.
|
||||||
|
"""
|
||||||
|
if hasattr(s, "__html__"):
|
||||||
|
return Markup(s.__html__())
|
||||||
|
return Markup(
|
||||||
|
text_type(s)
|
||||||
|
.replace("&", "&")
|
||||||
|
.replace(">", ">")
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace("'", "'")
|
||||||
|
.replace('"', """)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def escape_silent(s):
|
||||||
|
"""Like :func:`escape` but treats ``None`` as the empty string.
|
||||||
|
Useful with optional values, as otherwise you get the string
|
||||||
|
``'None'`` when the value is ``None``.
|
||||||
|
|
||||||
|
>>> escape(None)
|
||||||
|
Markup('None')
|
||||||
|
>>> escape_silent(None)
|
||||||
|
Markup('')
|
||||||
|
"""
|
||||||
|
if s is None:
|
||||||
|
return Markup()
|
||||||
|
return escape(s)
|
||||||
|
|
||||||
|
|
||||||
|
def soft_unicode(s):
|
||||||
|
"""Convert an object to a string if it isn't already. This preserves
|
||||||
|
a :class:`Markup` string rather than converting it back to a basic
|
||||||
|
string, so it will still be marked as safe and won't be escaped
|
||||||
|
again.
|
||||||
|
|
||||||
|
>>> value = escape('<User 1>')
|
||||||
|
>>> value
|
||||||
|
Markup('<User 1>')
|
||||||
|
>>> escape(str(value))
|
||||||
|
Markup('&lt;User 1&gt;')
|
||||||
|
>>> escape(soft_unicode(value))
|
||||||
|
Markup('<User 1>')
|
||||||
|
"""
|
||||||
|
if not isinstance(s, text_type):
|
||||||
|
s = text_type(s)
|
||||||
|
return s
|
|
@ -0,0 +1,423 @@
|
||||||
|
/**
|
||||||
|
* markupsafe._speedups
|
||||||
|
* ~~~~~~~~~~~~~~~~~~~~
|
||||||
|
*
|
||||||
|
* C implementation of escaping for better performance. Used instead of
|
||||||
|
* the native Python implementation when compiled.
|
||||||
|
*
|
||||||
|
* :copyright: 2010 Pallets
|
||||||
|
* :license: BSD-3-Clause
|
||||||
|
*/
|
||||||
|
#include <Python.h>
|
||||||
|
|
||||||
|
#if PY_MAJOR_VERSION < 3
|
||||||
|
#define ESCAPED_CHARS_TABLE_SIZE 63
|
||||||
|
#define UNICHR(x) (PyUnicode_AS_UNICODE((PyUnicodeObject*)PyUnicode_DecodeASCII(x, strlen(x), NULL)));
|
||||||
|
|
||||||
|
static Py_ssize_t escaped_chars_delta_len[ESCAPED_CHARS_TABLE_SIZE];
|
||||||
|
static Py_UNICODE *escaped_chars_repl[ESCAPED_CHARS_TABLE_SIZE];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static PyObject* markup;
|
||||||
|
|
||||||
|
static int
|
||||||
|
init_constants(void)
|
||||||
|
{
|
||||||
|
PyObject *module;
|
||||||
|
|
||||||
|
#if PY_MAJOR_VERSION < 3
|
||||||
|
/* mapping of characters to replace */
|
||||||
|
escaped_chars_repl['"'] = UNICHR(""");
|
||||||
|
escaped_chars_repl['\''] = UNICHR("'");
|
||||||
|
escaped_chars_repl['&'] = UNICHR("&");
|
||||||
|
escaped_chars_repl['<'] = UNICHR("<");
|
||||||
|
escaped_chars_repl['>'] = UNICHR(">");
|
||||||
|
|
||||||
|
/* lengths of those characters when replaced - 1 */
|
||||||
|
memset(escaped_chars_delta_len, 0, sizeof (escaped_chars_delta_len));
|
||||||
|
escaped_chars_delta_len['"'] = escaped_chars_delta_len['\''] = \
|
||||||
|
escaped_chars_delta_len['&'] = 4;
|
||||||
|
escaped_chars_delta_len['<'] = escaped_chars_delta_len['>'] = 3;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* import markup type so that we can mark the return value */
|
||||||
|
module = PyImport_ImportModule("markupsafe");
|
||||||
|
if (!module)
|
||||||
|
return 0;
|
||||||
|
markup = PyObject_GetAttrString(module, "Markup");
|
||||||
|
Py_DECREF(module);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if PY_MAJOR_VERSION < 3
|
||||||
|
static PyObject*
|
||||||
|
escape_unicode(PyUnicodeObject *in)
|
||||||
|
{
|
||||||
|
PyUnicodeObject *out;
|
||||||
|
Py_UNICODE *inp = PyUnicode_AS_UNICODE(in);
|
||||||
|
const Py_UNICODE *inp_end = PyUnicode_AS_UNICODE(in) + PyUnicode_GET_SIZE(in);
|
||||||
|
Py_UNICODE *next_escp;
|
||||||
|
Py_UNICODE *outp;
|
||||||
|
Py_ssize_t delta=0, erepl=0, delta_len=0;
|
||||||
|
|
||||||
|
/* First we need to figure out how long the escaped string will be */
|
||||||
|
while (*(inp) || inp < inp_end) {
|
||||||
|
if (*inp < ESCAPED_CHARS_TABLE_SIZE) {
|
||||||
|
delta += escaped_chars_delta_len[*inp];
|
||||||
|
erepl += !!escaped_chars_delta_len[*inp];
|
||||||
|
}
|
||||||
|
++inp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Do we need to escape anything at all? */
|
||||||
|
if (!erepl) {
|
||||||
|
Py_INCREF(in);
|
||||||
|
return (PyObject*)in;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = (PyUnicodeObject*)PyUnicode_FromUnicode(NULL, PyUnicode_GET_SIZE(in) + delta);
|
||||||
|
if (!out)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
outp = PyUnicode_AS_UNICODE(out);
|
||||||
|
inp = PyUnicode_AS_UNICODE(in);
|
||||||
|
while (erepl-- > 0) {
|
||||||
|
/* look for the next substitution */
|
||||||
|
next_escp = inp;
|
||||||
|
while (next_escp < inp_end) {
|
||||||
|
if (*next_escp < ESCAPED_CHARS_TABLE_SIZE &&
|
||||||
|
(delta_len = escaped_chars_delta_len[*next_escp])) {
|
||||||
|
++delta_len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++next_escp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next_escp > inp) {
|
||||||
|
/* copy unescaped chars between inp and next_escp */
|
||||||
|
Py_UNICODE_COPY(outp, inp, next_escp-inp);
|
||||||
|
outp += next_escp - inp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* escape 'next_escp' */
|
||||||
|
Py_UNICODE_COPY(outp, escaped_chars_repl[*next_escp], delta_len);
|
||||||
|
outp += delta_len;
|
||||||
|
|
||||||
|
inp = next_escp + 1;
|
||||||
|
}
|
||||||
|
if (inp < inp_end)
|
||||||
|
Py_UNICODE_COPY(outp, inp, PyUnicode_GET_SIZE(in) - (inp - PyUnicode_AS_UNICODE(in)));
|
||||||
|
|
||||||
|
return (PyObject*)out;
|
||||||
|
}
|
||||||
|
#else /* PY_MAJOR_VERSION < 3 */
|
||||||
|
|
||||||
|
#define GET_DELTA(inp, inp_end, delta) \
|
||||||
|
while (inp < inp_end) { \
|
||||||
|
switch (*inp++) { \
|
||||||
|
case '"': \
|
||||||
|
case '\'': \
|
||||||
|
case '&': \
|
||||||
|
delta += 4; \
|
||||||
|
break; \
|
||||||
|
case '<': \
|
||||||
|
case '>': \
|
||||||
|
delta += 3; \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DO_ESCAPE(inp, inp_end, outp) \
|
||||||
|
{ \
|
||||||
|
Py_ssize_t ncopy = 0; \
|
||||||
|
while (inp < inp_end) { \
|
||||||
|
switch (*inp) { \
|
||||||
|
case '"': \
|
||||||
|
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
|
||||||
|
outp += ncopy; ncopy = 0; \
|
||||||
|
*outp++ = '&'; \
|
||||||
|
*outp++ = '#'; \
|
||||||
|
*outp++ = '3'; \
|
||||||
|
*outp++ = '4'; \
|
||||||
|
*outp++ = ';'; \
|
||||||
|
break; \
|
||||||
|
case '\'': \
|
||||||
|
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
|
||||||
|
outp += ncopy; ncopy = 0; \
|
||||||
|
*outp++ = '&'; \
|
||||||
|
*outp++ = '#'; \
|
||||||
|
*outp++ = '3'; \
|
||||||
|
*outp++ = '9'; \
|
||||||
|
*outp++ = ';'; \
|
||||||
|
break; \
|
||||||
|
case '&': \
|
||||||
|
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
|
||||||
|
outp += ncopy; ncopy = 0; \
|
||||||
|
*outp++ = '&'; \
|
||||||
|
*outp++ = 'a'; \
|
||||||
|
*outp++ = 'm'; \
|
||||||
|
*outp++ = 'p'; \
|
||||||
|
*outp++ = ';'; \
|
||||||
|
break; \
|
||||||
|
case '<': \
|
||||||
|
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
|
||||||
|
outp += ncopy; ncopy = 0; \
|
||||||
|
*outp++ = '&'; \
|
||||||
|
*outp++ = 'l'; \
|
||||||
|
*outp++ = 't'; \
|
||||||
|
*outp++ = ';'; \
|
||||||
|
break; \
|
||||||
|
case '>': \
|
||||||
|
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
|
||||||
|
outp += ncopy; ncopy = 0; \
|
||||||
|
*outp++ = '&'; \
|
||||||
|
*outp++ = 'g'; \
|
||||||
|
*outp++ = 't'; \
|
||||||
|
*outp++ = ';'; \
|
||||||
|
break; \
|
||||||
|
default: \
|
||||||
|
ncopy++; \
|
||||||
|
} \
|
||||||
|
inp++; \
|
||||||
|
} \
|
||||||
|
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
escape_unicode_kind1(PyUnicodeObject *in)
|
||||||
|
{
|
||||||
|
Py_UCS1 *inp = PyUnicode_1BYTE_DATA(in);
|
||||||
|
Py_UCS1 *inp_end = inp + PyUnicode_GET_LENGTH(in);
|
||||||
|
Py_UCS1 *outp;
|
||||||
|
PyObject *out;
|
||||||
|
Py_ssize_t delta = 0;
|
||||||
|
|
||||||
|
GET_DELTA(inp, inp_end, delta);
|
||||||
|
if (!delta) {
|
||||||
|
Py_INCREF(in);
|
||||||
|
return (PyObject*)in;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = PyUnicode_New(PyUnicode_GET_LENGTH(in) + delta,
|
||||||
|
PyUnicode_IS_ASCII(in) ? 127 : 255);
|
||||||
|
if (!out)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
inp = PyUnicode_1BYTE_DATA(in);
|
||||||
|
outp = PyUnicode_1BYTE_DATA(out);
|
||||||
|
DO_ESCAPE(inp, inp_end, outp);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
escape_unicode_kind2(PyUnicodeObject *in)
|
||||||
|
{
|
||||||
|
Py_UCS2 *inp = PyUnicode_2BYTE_DATA(in);
|
||||||
|
Py_UCS2 *inp_end = inp + PyUnicode_GET_LENGTH(in);
|
||||||
|
Py_UCS2 *outp;
|
||||||
|
PyObject *out;
|
||||||
|
Py_ssize_t delta = 0;
|
||||||
|
|
||||||
|
GET_DELTA(inp, inp_end, delta);
|
||||||
|
if (!delta) {
|
||||||
|
Py_INCREF(in);
|
||||||
|
return (PyObject*)in;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = PyUnicode_New(PyUnicode_GET_LENGTH(in) + delta, 65535);
|
||||||
|
if (!out)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
inp = PyUnicode_2BYTE_DATA(in);
|
||||||
|
outp = PyUnicode_2BYTE_DATA(out);
|
||||||
|
DO_ESCAPE(inp, inp_end, outp);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
escape_unicode_kind4(PyUnicodeObject *in)
|
||||||
|
{
|
||||||
|
Py_UCS4 *inp = PyUnicode_4BYTE_DATA(in);
|
||||||
|
Py_UCS4 *inp_end = inp + PyUnicode_GET_LENGTH(in);
|
||||||
|
Py_UCS4 *outp;
|
||||||
|
PyObject *out;
|
||||||
|
Py_ssize_t delta = 0;
|
||||||
|
|
||||||
|
GET_DELTA(inp, inp_end, delta);
|
||||||
|
if (!delta) {
|
||||||
|
Py_INCREF(in);
|
||||||
|
return (PyObject*)in;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = PyUnicode_New(PyUnicode_GET_LENGTH(in) + delta, 1114111);
|
||||||
|
if (!out)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
inp = PyUnicode_4BYTE_DATA(in);
|
||||||
|
outp = PyUnicode_4BYTE_DATA(out);
|
||||||
|
DO_ESCAPE(inp, inp_end, outp);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
escape_unicode(PyUnicodeObject *in)
|
||||||
|
{
|
||||||
|
if (PyUnicode_READY(in))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
switch (PyUnicode_KIND(in)) {
|
||||||
|
case PyUnicode_1BYTE_KIND:
|
||||||
|
return escape_unicode_kind1(in);
|
||||||
|
case PyUnicode_2BYTE_KIND:
|
||||||
|
return escape_unicode_kind2(in);
|
||||||
|
case PyUnicode_4BYTE_KIND:
|
||||||
|
return escape_unicode_kind4(in);
|
||||||
|
}
|
||||||
|
assert(0); /* shouldn't happen */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif /* PY_MAJOR_VERSION < 3 */
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
escape(PyObject *self, PyObject *text)
|
||||||
|
{
|
||||||
|
static PyObject *id_html;
|
||||||
|
PyObject *s = NULL, *rv = NULL, *html;
|
||||||
|
|
||||||
|
if (id_html == NULL) {
|
||||||
|
#if PY_MAJOR_VERSION < 3
|
||||||
|
id_html = PyString_InternFromString("__html__");
|
||||||
|
#else
|
||||||
|
id_html = PyUnicode_InternFromString("__html__");
|
||||||
|
#endif
|
||||||
|
if (id_html == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* we don't have to escape integers, bools or floats */
|
||||||
|
if (PyLong_CheckExact(text) ||
|
||||||
|
#if PY_MAJOR_VERSION < 3
|
||||||
|
PyInt_CheckExact(text) ||
|
||||||
|
#endif
|
||||||
|
PyFloat_CheckExact(text) || PyBool_Check(text) ||
|
||||||
|
text == Py_None)
|
||||||
|
return PyObject_CallFunctionObjArgs(markup, text, NULL);
|
||||||
|
|
||||||
|
/* if the object has an __html__ method that performs the escaping */
|
||||||
|
html = PyObject_GetAttr(text ,id_html);
|
||||||
|
if (html) {
|
||||||
|
s = PyObject_CallObject(html, NULL);
|
||||||
|
Py_DECREF(html);
|
||||||
|
if (s == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
/* Convert to Markup object */
|
||||||
|
rv = PyObject_CallFunctionObjArgs(markup, (PyObject*)s, NULL);
|
||||||
|
Py_DECREF(s);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* otherwise make the object unicode if it isn't, then escape */
|
||||||
|
PyErr_Clear();
|
||||||
|
if (!PyUnicode_Check(text)) {
|
||||||
|
#if PY_MAJOR_VERSION < 3
|
||||||
|
PyObject *unicode = PyObject_Unicode(text);
|
||||||
|
#else
|
||||||
|
PyObject *unicode = PyObject_Str(text);
|
||||||
|
#endif
|
||||||
|
if (!unicode)
|
||||||
|
return NULL;
|
||||||
|
s = escape_unicode((PyUnicodeObject*)unicode);
|
||||||
|
Py_DECREF(unicode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
s = escape_unicode((PyUnicodeObject*)text);
|
||||||
|
|
||||||
|
/* convert the unicode string into a markup object. */
|
||||||
|
rv = PyObject_CallFunctionObjArgs(markup, (PyObject*)s, NULL);
|
||||||
|
Py_DECREF(s);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
escape_silent(PyObject *self, PyObject *text)
|
||||||
|
{
|
||||||
|
if (text != Py_None)
|
||||||
|
return escape(self, text);
|
||||||
|
return PyObject_CallFunctionObjArgs(markup, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
soft_unicode(PyObject *self, PyObject *s)
|
||||||
|
{
|
||||||
|
if (!PyUnicode_Check(s))
|
||||||
|
#if PY_MAJOR_VERSION < 3
|
||||||
|
return PyObject_Unicode(s);
|
||||||
|
#else
|
||||||
|
return PyObject_Str(s);
|
||||||
|
#endif
|
||||||
|
Py_INCREF(s);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyMethodDef module_methods[] = {
|
||||||
|
{"escape", (PyCFunction)escape, METH_O,
|
||||||
|
"escape(s) -> markup\n\n"
|
||||||
|
"Convert the characters &, <, >, ', and \" in string s to HTML-safe\n"
|
||||||
|
"sequences. Use this if you need to display text that might contain\n"
|
||||||
|
"such characters in HTML. Marks return value as markup string."},
|
||||||
|
{"escape_silent", (PyCFunction)escape_silent, METH_O,
|
||||||
|
"escape_silent(s) -> markup\n\n"
|
||||||
|
"Like escape but converts None to an empty string."},
|
||||||
|
{"soft_unicode", (PyCFunction)soft_unicode, METH_O,
|
||||||
|
"soft_unicode(object) -> string\n\n"
|
||||||
|
"Make a string unicode if it isn't already. That way a markup\n"
|
||||||
|
"string is not converted back to unicode."},
|
||||||
|
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#if PY_MAJOR_VERSION < 3
|
||||||
|
|
||||||
|
#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
|
||||||
|
#define PyMODINIT_FUNC void
|
||||||
|
#endif
|
||||||
|
PyMODINIT_FUNC
|
||||||
|
init_speedups(void)
|
||||||
|
{
|
||||||
|
if (!init_constants())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Py_InitModule3("markupsafe._speedups", module_methods, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#else /* Python 3.x module initialization */
|
||||||
|
|
||||||
|
static struct PyModuleDef module_definition = {
|
||||||
|
PyModuleDef_HEAD_INIT,
|
||||||
|
"markupsafe._speedups",
|
||||||
|
NULL,
|
||||||
|
-1,
|
||||||
|
module_methods,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
PyMODINIT_FUNC
|
||||||
|
PyInit__speedups(void)
|
||||||
|
{
|
||||||
|
if (!init_constants())
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return PyModule_Create(&module_definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -433,6 +433,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="ten wide column">
|
||||||
|
<div class="fluid column">
|
||||||
|
<div style="color: red;">We don't recommend enabling this option unless absolutely required (ie:
|
||||||
|
media player not supporting language code in subtitles filename). Results may vary.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="middle aligned row">
|
<div class="middle aligned row">
|
||||||
|
@ -445,12 +452,12 @@
|
||||||
class="ui fluid search selection dropdown">
|
class="ui fluid search selection dropdown">
|
||||||
<option value="">Languages</option>
|
<option value="">Languages</option>
|
||||||
{% set enabled_languages = [] %}
|
{% set enabled_languages = [] %}
|
||||||
{%for language in settings_languages%}
|
{% for language in settings_languages %}
|
||||||
<option value="{{ language['code2'] }}">{{ language['name'] }}</option>
|
<option value="{{ language['code2'] }}">{{ language['name'] }}</option>
|
||||||
{%if language['enabled'] == True%}
|
{% if language['enabled'] == True %}
|
||||||
{{ enabled_languages.append(language['code2']|string)}}
|
{{ enabled_languages.append(language['code2']|string) }}
|
||||||
{%endif%}
|
{% endif %}
|
||||||
{%endfor%}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue