pax_global_header00006660000000000000000000000064136254570010014515gustar00rootroot0000000000000052 comment=29ccf6cf85781315a696774e7458a2f1f61aac57 pyppd-release-1-1-0/000077500000000000000000000000001362545700100142645ustar00rootroot00000000000000pyppd-release-1-1-0/.gitignore000066400000000000000000000001101362545700100162440ustar00rootroot00000000000000build dist *.egg *.pyc *.egg-info *.so *.pyd *.swp CHANGES.txt MANIFEST pyppd-release-1-1-0/ISSUES000066400000000000000000000000651362545700100152230ustar00rootroot00000000000000Issues ------ * Add unit tests. * Generate manpage. pyppd-release-1-1-0/LICENSE.txt000066400000000000000000000020421362545700100161050ustar00rootroot00000000000000Copyright (c) 2012 Vitor Baptista Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pyppd-release-1-1-0/MANIFEST.in000066400000000000000000000000601362545700100160160ustar00rootroot00000000000000include *.txt include pyppd/*.in include ISSUES pyppd-release-1-1-0/README000066400000000000000000000041511362545700100151450ustar00rootroot00000000000000pyppd ===== ``pyppd`` is a CUPS PPD generator. It holds an compressed archive of PPDs, which can be listed and retrieved only when needed by CUPS, saving disk space. Installation ------------ To install ``pyppd``, you can use: # pip install pyppd Or download the source package, uncompress, and run as root: # python3 setup.py install It depends on Python 2.x or 3.x (http://www.python.org) and XZ Utils (http://tukaani.org/xz/). Usage ----- At first, you have to create a PPD archive. For such, put all PPDs (they might be gzipped) you want to add in the archive inside a single folder (which can have subfolders), then run: $ pyppd /path/to/your/ppd/folder It'll create ``pyppd-ppdfile`` in your current folder. This executable only works with the same Python version that you used to generate it. You can test it by running: $ ./pyppd-ppdfile list And, for reading a PPD from the archive, simply do: $ ./pyppd-ppdfile cat pyppd-ppdfile:MY-PPD-FILE.PPD For CUPS to be able to use your newly-created archive, copy ``pyppd-ppdfile`` to ``/usr/lib/cups/driver/`` and you're done. The generated ``pyppd-ppdfile`` can be arbitrarily renamed, so that more than one packed repository can be installed on one system. This can be useful if you need a better performance, be it in time or memory usage. Note that also the PPD URIs will follow the new name: $ ./pyppd-ppdfile list pyppd-ppdfile:LasterStar/LaserStar-XX100.ppd $ mv pyppd-ppdfile laserstar $ ./laserstar list laserstar:LaserStar/LaserStar-XX100.ppd Contributors ------------ * **Till Kamppeter** - Original idea, mentoring and feedback. User #0. * **Hin-Tak Leung** - Lots of technical suggestions. * **Martin Pitt** - Python 3 port. * **Flávio Ribeiro** and **Diógenes Fernandes** - Refactorings and general Python's best practices tips. * **Didier Raboud** - Make archives reproducible, by sorting the list of found PPDs and using JSON dumps instead of Pickle dumps. * **Sambhav Dusad** - Streaming decompression, to not need to hold the whole decompressed archive in memory. * **Google's OSPO** - Initial funding at GSoC 2010. pyppd-release-1-1-0/README.md000077700000000000000000000000001362545700100164162READMEustar00rootroot00000000000000pyppd-release-1-1-0/bin/000077500000000000000000000000001362545700100150345ustar00rootroot00000000000000pyppd-release-1-1-0/bin/pyppd000077500000000000000000000002721362545700100161170ustar00rootroot00000000000000#!/usr/bin/env python3 from pyppd import runner try: runner.run() except KeyboardInterrupt: # We don't want a KeyboardInterrupt throwing a traceback # into stdout. pass pyppd-release-1-1-0/contrib/000077500000000000000000000000001362545700100157245ustar00rootroot00000000000000pyppd-release-1-1-0/contrib/git2changes.py000077500000000000000000000067471362545700100205150ustar00rootroot00000000000000#!/usr/bin/python # Copyright 2010 Vitor Baptista # Distributed under the terms of the GNU Lesser General Public License v3 or later from subprocess import Popen, PIPE import re class Commit: def __init__(self, log): log.strip() log_array = log.split('\n') self.commit = log_array.pop(0).split('commit ')[0] self.author = log_array.pop(0).split('Author: ')[1] self.date = log_array.pop(0).split('Date: ')[1] # Remove garbage from array while True: garbage = log_array.pop() if re.search(r'files? changed', garbage): break self.changed_files = [] changed_file = log_array.pop() while changed_file != "": self.changed_files += [changed_file.split('|')[0].strip()] changed_file = log_array.pop() self.message = "" try: while True: commit_line = log_array.pop().strip() if (commit_line == "" or commit_line.find('Signed-off-by') != -1 or commit_line.find('git-svn-id') != -1): commit_line = log_array.pop() continue self.message = commit_line.strip() + " " + self.message except IndexError: pass self.message = self.__break_string(self.message, 80) self.version = None def __break_string(self, string, column): finished_string = "" string = string.replace('\n', ' ') while column < len(string): break_point = string[0:column].rfind(' ') if break_point == -1: break_point = string[column + 1:].find(' ') if break_point != -1: break_point += column finished_string += string[0:break_point] + "\n" string = string[break_point + 1:] if string != "": finished_string += string return finished_string def __str__(self): message_with_files = '* ' + ', '.join(self.changed_files) + ': ' + self.message message_with_files = self.__break_string(message_with_files, 78) message_with_files = ' ' + message_with_files.replace('\n', '\n ') if message_with_files[-1] == ' ': message_with_files = message_with_files[0:-1] return message_with_files def get_version(commit): setup_py = Popen(["git", "show", "%s:setup.py" % commit], stdout=PIPE).communicate()[0] version = setup_py.split("version='")[1].split("'")[0] return version def run(output): log = Popen(["git", "log", "--summary", "--stat", "--no-merges", "--date=short"], stdout=PIPE).communicate()[0] log = "\n%s" % log log_array = re.split('\ncommit ', log) log_array.pop(0) commits = [] for commit in log_array: commits.insert(0, Commit(commit)) prevVersion = "" for commit in commits: version = get_version(commit.commit) if prevVersion == "" or version != prevVersion: commit.version = version prevVersion = version commits.reverse() if not commits[0].version: commits[0].version = "HEAD" for commit in commits: if commit.version: output_format = "v%s (%s)\n\n" if commit.version == "HEAD": output_format = "%s (%s)\n\n" output.write(output_format % (commit.version, commit.date)) output.write("%s\n\n" % commit) if __name__ == "__main__": from sys import stdout run(stdout) stdout.close() pyppd-release-1-1-0/pyppd/000077500000000000000000000000001362545700100154205ustar00rootroot00000000000000pyppd-release-1-1-0/pyppd/__init__.py000066400000000000000000000000001362545700100175170ustar00rootroot00000000000000pyppd-release-1-1-0/pyppd/archiver.py000066400000000000000000000075151362545700100176050ustar00rootroot00000000000000import base64 import sys import os import fnmatch import gzip import logging from random import randint import pyppd.compressor import pyppd.ppd import json def archive(ppds_directory): """Returns a string with the decompressor, its dependencies and the archive. It reads the template at pyppd/pyppd-ppdfile.in, inserts the dependencies and the archive encoded in base64, and returns as a string. """ logging.info('Compressing folder "%s".' % ppds_directory) ppds_compressed = compress(ppds_directory) if not ppds_compressed: return None ppds_compressed_b64 = base64.b64encode(ppds_compressed) logging.info('Populating template.') template = read_file_in_syspath("pyppd/pyppd-ppdfile.in") compressor_py = read_file_in_syspath("pyppd/compressor.py") template = template.replace(b"@compressor@", compressor_py) template = template.replace(b"@ppds_compressed_b64@", ppds_compressed_b64) return template def compress(directory): """Compresses and indexes *.ppd and *.ppd.gz in directory returning a string. The directory is walked recursively, concatenating all ppds found in a string. For each, it tests if its filename ends in *.gz. If so, opens with gzip. If not, opens directly. Then, it parses and saves its name, description (in the format CUPS needs (which can be more than one)) and it's position in the ppds string (start position and length) into a dictionary, used as an index. Then, it compresses the string, adds into the dictionary as key ARCHIVE and returns a compressed pickle dump of it. """ ppds = b"" ppds_index = {} abs_directory = os.path.abspath(directory) for ppd_path in sorted(find_files(directory, ("*.ppd", "*.ppd.gz"))): # Remove 'directory/' from the filename ppd_filename = ppd_path[len(abs_directory)+1:] if ppd_path.lower().endswith(".gz"): ppd_file = gzip.open(ppd_path).read() # We don't want the .gz extension in our filename ppd_filename = ppd_filename[:-3] else: ppd_file = open(ppd_path, 'rb').read() start = len(ppds) length = len(ppd_file) logging.debug('Found %s (%d bytes).' % (ppd_path, length)) ppd_parsed = pyppd.ppd.parse(ppd_file, ppd_filename) ppd_descriptions = [p.__str__() for p in ppd_parsed] ppds_index[ppd_parsed[0].uri] = (start, length, ppd_descriptions) logging.debug('Adding %d entry(ies): %s.' % (len(ppd_descriptions), ppd_descriptions)) ppds += ppd_file if not ppds: logging.error('No PPDs found in folder "%s".' % directory) return None logging.info('Compressing archive, encode to base64 string.') ppds_index['ARCHIVE'] = base64.b64encode(pyppd.compressor.compress(ppds)).decode('ASCII') logging.info('Generating and compressing json dump.') ppds_json = pyppd.compressor.compress(json.dumps(ppds_index, ensure_ascii=True, sort_keys=True).encode('ASCII')) return ppds_json def read_file_in_syspath(filename): """Reads the file in filename in each sys.path. If we couldn't find, throws the last IOError caught. """ last_exception = None for path in sys.path: try: return open(path + "/" + filename, 'rb').read() except IOError as ex: last_exception = ex continue raise last_exception def find_files(directory, patterns): """Yields each file that matches any of patterns in directory.""" logging.debug('Searching for "%s" files in folder "%s".' % (", ".join(patterns), directory)) abs_directory = os.path.abspath(directory) for root, dirnames, filenames in os.walk(abs_directory): for pattern in patterns: for filename in fnmatch.filter(filenames, pattern): yield os.path.join(root, filename) pyppd-release-1-1-0/pyppd/compressor.py000066400000000000000000000012331362545700100201650ustar00rootroot00000000000000from subprocess import Popen, PIPE def compress(value): """Compresses a byte array with the xz binary""" process = Popen(["xz", "--compress", "--force"], stdin=PIPE, stdout=PIPE) return process.communicate(value)[0] def decompress(value): """Decompresses a byte array with the xz binary""" process = Popen(["xz", "--decompress", "--stdout", "--force"], stdin=PIPE, stdout=PIPE) return process.communicate(value)[0] def compress_file(path): """Compress the file at 'path' with the xz binary""" process = Popen(["xz", "--compress", "--force", "--stdout", path], stdout=PIPE) return process.communicate()[0] pyppd-release-1-1-0/pyppd/ppd.py000066400000000000000000000172371362545700100165670ustar00rootroot00000000000000import re import logging LANGUAGES = {'afar': 'aa', 'abkhazian': 'ab', 'afrikaans': 'af', 'amharic': 'am', 'arabic': 'ar', 'assamese': 'as', 'aymara': 'ay', 'azerbaijani': 'az', 'bashkir': 'ba', 'byelorussian': 'be', 'bulgarian': 'bg', 'bihari': 'bh', 'bislama': 'bi', 'bengali': 'bn', 'bangla': 'bn', 'tibetan': 'bo', 'breton': 'br', 'catalan': 'ca', 'corsican': 'co', 'czech': 'cs', 'welsh': 'cy', 'danish': 'da', 'german': 'de', 'bhutani': 'dz', 'greek': 'el', 'english': 'en', 'esperanto': 'eo', 'spanish': 'es', 'estonian': 'et', 'basque': 'eu', 'persian': 'fa', 'finnish': 'fi', 'fiji': 'fj', 'faeroese': 'fo', 'french': 'fr', 'frisian': 'fy', 'irish': 'ga', 'scots gaelic': 'gd', 'galician': 'gl', 'guarani': 'gn', 'gujarati': 'gu', 'hausa': 'ha', 'hindi': 'hi', 'croatian': 'hr', 'hungarian': 'hu', 'armenian': 'hy', 'interlingua': 'ia', 'interlingue': 'ie', 'inupiak': 'ik', 'indonesian': 'in', 'icelandic': 'is', 'italian': 'it', 'hebrew': 'iw', 'japanese': 'ja', 'yiddish': 'ji', 'javanese': 'jw', 'georgian': 'ka', 'kazakh': 'kk', 'greenlandic': 'kl', 'cambodian': 'km', 'kannada': 'kn', 'korean': 'ko', 'kashmiri': 'ks', 'kurdish': 'ku', 'kirghiz': 'ky', 'latin': 'la', 'lingala': 'ln', 'laothian': 'lo', 'lithuanian': 'lt', 'latvian': 'lv','lettish': 'lv', 'malagasy': 'mg', 'maori': 'mi', 'macedonian': 'mk', 'malayalam': 'ml', 'mongolian': 'mn', 'moldavian': 'mo', 'marathi': 'mr', 'malay': 'ms', 'maltese': 'mt', 'burmese': 'my', 'nauru': 'na', 'nepali': 'ne', 'dutch': 'nl', 'norwegian': 'no', 'occitan': 'oc', '(afan) oromo': 'om', 'oriya': 'or', 'punjabi': 'pa', 'polish': 'pl', 'pashto': 'ps', 'pushto': 'ps', 'portuguese': 'pt', 'quechua': 'qu', 'rhaeto-romance': 'rm', 'kirundi': 'rn', 'romanian': 'ro', 'russian': 'ru', 'kinyarwanda': 'rw', 'sanskrit': 'sa', 'sindhi': 'sd', 'sangro': 'sg', 'serbo-croatian': 'sh', 'singhalese': 'si', 'slovak': 'sk', 'slovenian': 'sl', 'samoan': 'sm', 'shona': 'sn', 'somali': 'so', 'albanian': 'sq', 'serbian': 'sr', 'siswati': 'ss', 'sesotho': 'st', 'sundanese': 'su', 'swedish': 'sv', 'swahili': 'sw', 'tamil': 'ta', 'tegulu': 'te', 'tajik': 'tg', 'thai': 'th', 'tigrinya': 'ti', 'turkmen': 'tk', 'tagalog': 'tl', 'setswana': 'tn', 'tonga': 'to', 'turkish': 'tr', 'tsonga': 'ts', 'tatar': 'tt', 'twi': 'tw', 'ukrainian': 'uk', 'urdu': 'ur', 'uzbek': 'uz', 'vietnamese': 'vi', 'volapuk': 'vo', 'wolof': 'wo', 'xhosa': 'xh', 'yoruba': 'yo', 'chinese': 'zh', 'simplified chinese': 'zh_TW', 'traditional chinese': 'zh_CN', 'zulu': 'zu', 'portuguese_brazil': 'pt_BR'} class PPD(object): """Represents a PostScript Description file.""" def __init__(self, uri, language, manufacturer, nickname, deviceid): """Initializes a PPD object with the information passed.""" self.uri = uri self.language = language self.manufacturer = manufacturer self.nickname = nickname self.deviceid = deviceid def __str__(self): return '"%s" %s "%s" "%s" "%s"' % (self.uri, self.language, self.manufacturer, self.nickname, self.deviceid) def parse(ppd_file, filename): """Parses ppd_file and returns an array with the PPDs it found. One ppd_file might result in more than one PPD. The rules are: return an PPD for each "1284DeviceID" entry, and one for each "Product" line, if it creates an unique (Manufacturer, Product) DeviceID. """ def standardize(model_name): # Consider it the same model if the product name differs only by # upper/lower case and by the presence/absence of the manufacturer # name return model_name.lower().replace("Hewlett-Packard ".lower(), "").replace("%s " % manufacturer.lower(), "").strip() logging.debug('Parsing %s.' % filename) language_re = re.search(b'\*LanguageVersion:\s*(.+)', ppd_file) manufacturer_re = re.search(b'\*Manufacturer:\s*"(.+)"', ppd_file) nickname_re = re.search(b'\*NickName:\s*"(.+)"', ppd_file) deviceids = re.findall(b'\*1284DeviceID:\s*"(.+)"', ppd_file) try: language = LANGUAGES[language_re.group(1).decode('UTF-8', errors='replace').strip().lower()] manufacturer = manufacturer_re.group(1).strip().decode('UTF-8', errors='replace') nickname = nickname_re.group(1).strip().decode('UTF-8', errors='replace') logging.debug('Language: "%s", Manufacturer: "%s", Nickname: "%s".' % (language, manufacturer, nickname)) ppds = [] models = [] drventry = None line = 0 num_device_ids = 0 num_products = 0 if deviceids: for deviceid in deviceids: deviceid = deviceid.decode('UTF-8', errors='replace') logging.debug('1284DeviceID: "%s".' % deviceid) if (not deviceid.endswith(";")): deviceid += ";" uri = "%d/%s" % (line, filename) # Save a DRV field (from Foomatic) and use it for all entries # of this PPD newdrventry = re.findall(".*DRV:\s*(.*?)\s*;.*", deviceid, re.I) if (len(newdrventry) > 0): drventry = newdrventry[0] elif (drventry != None): deviceid += "DRV:%s;" % drventry newmodels = re.findall(".*(?:MODEL|MDL):\s*(.*?)\s*;.*", deviceid, re.I) if (newmodels): newmodels = list(map(standardize, newmodels)) if (len(newmodels) > 0): # Consider only IDs with a MODEL/MDL field ppds += [PPD(uri, language, manufacturer, nickname, deviceid.strip())] models += newmodels num_device_ids += 1 line += 1 for product in re.findall(b'\*Product:\s*"\(\s*(.+?)\s*\)"', ppd_file): product = product.strip().decode('UTF-8', errors='replace') # Don't add a new entry if there's already one for the same # product/model product_standardized = standardize(product) if product_standardized in models: logging.debug('Ignoring already found *Product: "%s".' % product) continue logging.debug('Product: "%s"' % product) deviceid = "MFG:%s;MDL:%s;" % (manufacturer, product) if (drventry != None): deviceid += "DRV:%s;" % drventry uri = "%d/%s" % (line, filename) ppds += [PPD(uri, language, manufacturer, nickname, deviceid)] num_products += 1 line += 1 models += [product_standardized] if (num_products == 1 and num_device_ids > 0): # If there is at least one device ID and only one Product line, do # not consider the Product line as another model than the one # represented by the device ID and use only the data of the device # ID. ppds.pop() return ppds except: raise Exception("Error parsing PPD file '%s'" % filename) pyppd-release-1-1-0/pyppd/pyppd-ppdfile.in000066400000000000000000000077011362545700100205320ustar00rootroot00000000000000#!/usr/bin/env python3 # compressor.py @compressor@ # compressor.py import os import sys from optparse import OptionParser from sys import argv import base64 import json from io import BytesIO from os.path import basename from errno import EPIPE import lzma def load(): ppds_compressed = base64.b64decode(ppds_compressed_b64) ppds_decompressed = decompress(ppds_compressed) ppds = json.loads(ppds_decompressed.decode(encoding='ASCII')) return ppds def ls(): binary_name = basename(argv[0]) ppds = load() for key, value in ppds.items(): if key == 'ARCHIVE': continue for ppd in value[2]: try: print(ppd.replace('"', '"' + binary_name + ':', 1)) except IOError as e: # Errors like broken pipes (program which takes the standard # output terminates before this program terminates) should not # generate a traceback. if e.errno == EPIPE: exit(0) raise def cat(ppd): # Ignore driver's name, take only PPD's ppd = ppd.split(":")[-1] # Remove also the index ppd = "0/" + ppd[ppd.find("/")+1:] # Object for streaming decompression decompressor = lzma.LZMADecompressor() # size for one decompression i.e. ~20MB size = 20000000 ppds = load() ppds['ARCHIVE'] = base64.b64decode(ppds['ARCHIVE'].encode('ASCII')) ppdtext=bytearray() if ppd in ppds: start = ppds[ppd][0] length = ppds[ppd][1] text = BytesIO(decompressor.decompress(ppds['ARCHIVE'],size)) for i in range(int(start/size)): text = BytesIO(decompressor.decompress(ppds['ARCHIVE'],size)) text.seek(start%size) if((size-(start%size)) < length): ppdtext.extend(text.read()) length = length - (size-(start%size)) text = BytesIO(decompressor.decompress(ppds['ARCHIVE'],size)) while(size < length): ppdtext.extend(text.read()) length = length - size text = BytesIO(decompressor.decompress(ppds['ARCHIVE'],size)) ppdtext.extend(text.read(length)) else: ppdtext.extend(text.read(length)) return ppdtext def main(): usage = "usage: %prog list\n" \ " %prog cat URI" version = "%prog 1.1.0\n" \ "Copyright (c) 2013 Vitor Baptista.\n" \ "This is free software; see the source for copying conditions.\n" \ "There is NO warranty; not even for MERCHANTABILITY or\n" \ "FITNESS FOR A PARTICULAR PURPOSE." parser = OptionParser(usage=usage, version=version) (options, args) = parser.parse_args() if len(args) == 0 or len(args) > 2: parser.error("incorrect number of arguments") if args[0].lower() == 'list': ls() elif args[0].lower() == 'cat': if not len(args) == 2: parser.error("incorrect number of arguments") ppd = cat(args[1]) if not ppd: parser.error("Printer '%s' does not have default driver!" % args[1]) try: # avoid any assumption of encoding or system locale; just print the # bytes of the PPD as they are if sys.version_info.major < 3: sys.stdout.write(ppd) else: sys.stdout.buffer.write(ppd) except IOError as e: # Errors like broken pipes (program which takes the standard output # terminates before this program terminates) should not generate a # traceback. if e.errno == EPIPE: exit(0) raise else: parser.error("argument " + args[0] + " invalid") # PPDs Archive ppds_compressed_b64 = b"@ppds_compressed_b64@" if __name__ == "__main__": try: main() except KeyboardInterrupt: # We don't want a KeyboardInterrupt throwing a # traceback into stdout. pass pyppd-release-1-1-0/pyppd/runner.py000066400000000000000000000050721362545700100173070ustar00rootroot00000000000000import os import stat import errno import logging import logging.handlers from optparse import OptionParser import pyppd.archiver def parse_args(): usage = "usage: %prog [options] ppds_directory" version = "%prog 1.1.0\n" \ "Copyright (c) 2013 Vitor Baptista.\n" \ "This is free software; see the source for copying conditions.\n" \ "There is NO warranty; not even for MERCHANTABILITY or\n" \ "FITNESS FOR A PARTICULAR PURPOSE." parser = OptionParser(usage=usage, version=version) parser.add_option("-v", "--verbose", action="count", dest="verbosity", help="run verbosely (can be supplied multiple times to " \ "increase verbosity)") parser.add_option("-d", "--debug", action="store_const", const=2, dest="verbose", help="print debug messages") parser.add_option("-o", "--output", default="pyppd-ppdfile", metavar="FILE", help="write archive to FILE [default %default]") (options, args) = parser.parse_args() if len(args) != 1: parser.error("incorrect number of arguments") if not os.path.isdir(args[0]): parser.error("'%s' isn't a directory" % args[0]) return (options, args) def configure_logging(verbosity): """Configures logging verbosity To stdout, we only WARNING of worse messages in a simpler format. To the file, we save every log message with its time, level, module and method. We also rotate the log_file, removing old entries when it reaches 2 MB. """ if verbosity == 1: level = logging.INFO elif verbosity == 2: level = logging.DEBUG else: level = logging.WARNING formatter = '[%(levelname)s] %(module)s.%(funcName)s(): %(message)s' logging.basicConfig(level=level, format=formatter) def run(): (options, args) = parse_args() configure_logging(options.verbosity) ppds_directory = args[0] logging.info('Archiving folder "%s".' % ppds_directory) archive = pyppd.archiver.archive(ppds_directory) if not archive: exit(errno.ENOENT) logging.info('Writing archive to "%s".' % options.output) output = open(options.output, "wb+") output.write(archive) output.close() logging.info('Setting "%s" executable flag.' % options.output) execute_mode = stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH mode = os.stat(options.output).st_mode | execute_mode os.chmod(options.output, mode) pyppd-release-1-1-0/setup.py000077500000000000000000000022161362545700100160020ustar00rootroot00000000000000#!/usr/bin/env python3 from distutils.core import setup from distutils.command.sdist import sdist as _sdist class sdist(_sdist): def run(self): try: import sys sys.path.append("contrib") import git2changes print('generating CHANGES.txt') with open('CHANGES.txt', 'w+') as f: git2changes.run(f) except ImportError: pass _sdist.run(self) setup( name='pyppd', version='1.1.0', author='Vitor Baptista', author_email='vitor@vitorbaptista.com', packages=['pyppd'], package_data={'pyppd': ['*.in']}, scripts=['bin/pyppd'], url='https://github.com/OpenPrinting/pyppd/', license='MIT', description='A CUPS PostScript Printer Driver\'s compressor and generator', long_description=open('README', 'rb').read().decode('UTF-8'), cmdclass={'sdist': sdist}, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: System Administrators', 'Operating System :: POSIX', 'License :: OSI Approved :: MIT License', 'Topic :: Printing', ], )