Copyright (C) 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contains configuration and read/write utility functions for GoogleCL.
from __future__ import with_statement
__author__ = 'tom.h.miller@gmail.com (Tom Miller)'
import logging
import os
import re
import googlecl.config.parser
SUBDIR_NAME = 'googlecl'
DEFAULT_GOOGLECL_DIR = os.path.expanduser(os.path.join('~', '.googlecl'))
HISTORY_FILENAME = 'history'
TOKENS_FILENAME_FORMAT = 'access_tok_%s'
DEVKEY_FILENAME = 'yt_devkey'
FILE_EXT_PATTERN = re.compile('.*\.([a-zA-Z0-9]{2,}$)')
LOGGER_NAME = __name__
LOG = logging.getLogger(LOGGER_NAME)
def determine_terminal_encoding(config=None):
import sys
in_enc = ''
out_enc = ''
if sys.stdin.encoding:
in_enc = sys.stdin.encoding
if sys.stdout.encoding:
out_enc = sys.stdout.encoding
Sometimes these are both defined, and hopefully they are both equal. I'm not sure if they are guaranteed to be equal.
if in_enc.lower() == out_enc.lower():
If they're not defined, return the encoding specific in the config file, or the python system-wide default encoding (if the above is not defined) Apparently the above values aren't set when run as a cron job or piped?
if not in_enc:
return_enc = None
if config is not None:
return_enc = config.safe_get('GENERAL', 'default_encoding')
if return_enc is None:
return_enc = sys.getdefaultencoding()
else:
return_enc = in_enc
If they are not equal, at least one of them must be defined.
else:
Both defined, but are not the same
if in_enc and out_enc:
LOG.warning('HEY! You have a different encoding for input and output')
LOG.warning('Input: ' + in_enc)
LOG.warning('Output: ' + in_enc)
return_enc = out_enc or in_enc
LOG.debug('determine_terminal_encoding(): ' + return_enc)
return return_enc
Because this gets done at googlecl module load, there's no config parser to pass in. XXX: googlecl.config.load_configuration() currently sets this again, but that's less than optimal...
TERMINAL_ENCODING = determine_terminal_encoding()
class SafeEncodeError(Exception):
pass
class SafeDecodeError(Exception):
pass
Build a list of titles from the 'title' option and arguments.
def build_titles_list(title, args):
Args: title: NoneType or unicode Title given to options.title args: list Leftover arguments on the command line, presumably extra titles.
Returns: List of strings or [None].
If args is non-empty, assume the user is giving us titles to get
if args:
titles_list = args
If options.title is also given, add it to the list of titles
if title:
titles_list.insert(0,title)
else:
titles_list = [title]
return titles_list
Return the extension of a file.
def get_extension_from_path(path):
match = FILE_EXT_PATTERN.match(path)
if match:
return match.group(1)
else:
return None
Get the full path to the history file.
def get_data_path(filename,
default_directories=None,
create_missing_dir=False):
See googlecl.get_xdg_path()
return get_xdg_path(filename, 'DATA', default_directories,
create_missing_dir)
Get the full path to a file using XDG file layout spec.
def get_xdg_path(filename, data_type, default_directories=None,
create_missing_dir=False):
Follows XDG Base Directory Specification. (http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html). Tries default_directories and DEFAULT_GOOGLECL_DIR if no file is found via XDG spec.
Keyword arguments: filename: Filename of the file. data_type: One of 'config' for config files or 'data' for data files. default_directories: List of directories to check if no file is found in directories specified by XDG spec and DEFAULT_GOOGLECL_DIR. Default None. create_missing_dir: Whether or not to create a missing config subdirectory in the default base directory. Default False.
Returns: Path to config file, which may not exist. If create_missing_dir, the directory where the config file should be will be created if it does not exist.
data_type = data_type.upper()
if data_type not in ('CONFIG', 'DATA'):
raise Exception('Invalid value for data_type: ' + data_type)
xdg_home_dir = os.environ.get('XDG_' + data_type + '_HOME')
if not xdg_home_dir:
home_dir = os.path.expanduser('~')
if data_type == 'DATA':
xdg_home_dir = os.path.join(home_dir, '.local', 'share')
elif data_type == 'CONFIG':
No variable defined, using $HOME/.config
xdg_home_dir = os.path.join(home_dir, '.config')
xdg_home_dir = os.path.join(xdg_home_dir, SUBDIR_NAME)
xdg_dir_list = os.environ.get('XDG_' + data_type + '_DIRS')
if not xdg_dir_list:
if data_type == 'DATA':
xdg_dir_list = '/usr/local/share/:/usr/share/'
elif data_type == 'CONFIG':
xdg_dir_list = '/etc/xdg'
xdg_dir_list = [os.path.join(d, SUBDIR_NAME)
for d in xdg_dir_list.split(':')]
dir_list = [os.path.abspath('.'), xdg_home_dir] + xdg_dir_list +\
[DEFAULT_GOOGLECL_DIR]
if default_directories:
dir_list += default_directories
for directory in dir_list:
config_path = os.path.join(directory, filename)
if os.path.isfile(config_path):
return config_path
LOG.debug('Could not find ' + filename + ' in any of ' + str(dir_list))
if os.name == 'posix':
default_dir = xdg_home_dir
mode = 0700
else:
default_dir = DEFAULT_GOOGLECL_DIR
mode = 0777
if not os.path.isdir(default_dir) and create_missing_dir:
try:
os.makedirs(default_dir, mode)
except OSError, err:
LOG.error(err)
return ''
return os.path.join(default_dir, filename)
Return the cached YouTube developer's key.
def read_devkey():
key_path = get_data_path(DEVKEY_FILENAME)
devkey = None
if os.path.exists(key_path):
with open(key_path, 'r') as key_file:
devkey = key_file.read().strip()
return devkey
Encode a unicode string to target_encoding.
def safe_encode(string, target_encoding=TERMINAL_ENCODING,
errors='backslashreplace'):
If given a str type, check to see that target_encoding can decode it without an error. Raises a SafeEncodeError if it can't. Given any other type, returns str() version of it.
Args: string: unicode String to encode. target_encoding: str Encoding to encode to. Default TERMINAL_ENCODING. errors: str Name of the error handler to call if something goes wrong. See docs on the codecs module. Default 'backslashreplace'.
Returns: A string encoded with target_encoding, or raises an error.
if isinstance(string, unicode):
return string.encode(target_encoding, errors)
elif isinstance(string, str):
try:
string.decode(target_encoding)
except UnicodeDecodeError:
raise SafeEncodeError('Passed a non-unicode string to safe_encode!')
else:
return string
else:
Got something else, probably an int or bool or the like.
return str(string)
Decode a byte string.
def safe_decode(string, current_encoding='utf-8', errors='strict'):
Raises a SafeDecodeError if current_encoding cannot decode the string and the value of errors causes an exception to be raised. If given a unicde type, returns it immediately. Given any other type, returns unicode() version of it.
Args: string: str String to decode. target_encoding: str Encoding to decode with. Default 'utf-8'. errors: str Name of the error handler to call if something goes wrong. See docs on the codecs module. Default 'strict'.
Returns: A unicode string, or raises an error.
if isinstance(string, str):
try:
return string.decode(current_encoding, errors)
except UnicodeDecodeError:
raise SafeDecodeError(current_encoding + ' could not decode ' +
repr(string))
elif isinstance(string, unicode):
return string
else:
Got something elese, probably an int or bool or the like.
return unicode(string)
Write the devkey to the youtube devkey file.
def write_devkey(devkey):
import stat
key_path = get_data_path(DEVKEY_FILENAME)
with open(key_path, 'w') as key_file:
os.chmod(key_path, stat.S_IRUSR | stat.S_IWUSR)
key_file.write(devkey)