PK FlFdocstamp/__init__.pyPK FlF.docstamp/collections.py# -*- coding: utf-8 -*- import logging log = logging.getLogger(__name__) class Enum(set): def __getattr__(self, name): if name in self: return name raise AttributeError class ItemSet(object): def __iter__(self): return self.items.__iter__() def __next__(self): return self.items.__next__() def next(self): return self.items.next() def __getitem__(self, item): if hasattr(self.items, '__getitem__'): return self.items[item] else: raise log.exception('Item set has no __getitem__ implemented.') def __len__(self): return len(self.items) def save(self, file_path): import pickle try: with open(file_path, 'wb'): pickle.dump(self.__dict__, file_path, pickle.HIGHEST_PROTOCOL) except Exception as exc: log.exception('Error pickling itemset into {}.'.format(file_path)) raise def load_from_pickle(self, file_path): import pickle try: with open(file_path, 'rb'): adict = pickle.load(file_path) pickle.dump(self.__dict__, file_path, pickle.HIGHEST_PROTOCOL) self.__dict__.update(adict) except Exception as exc: log.exception('Error loading pickle itemset ' 'from {}.'.format(file_path)) raise PK{r1H docstamp/commands.py# coding=utf-8 # ------------------------------------------------------------------------------- # Author: Alexandre Manhaes Savio # Grupo de Inteligencia Computational # Universidad del Pais Vasco UPV/EHU # # 2015, Alexandre Manhaes Savio # Use this at your own risk! # ------------------------------------------------------------------------------- import os import sys import shutil import logging import subprocess import os.path as op from subprocess import CalledProcessError log = logging.getLogger(__name__) def simple_call(cmd_args): return subprocess.call(' '.join(cmd_args), shell=True) def is_exe(fpath): """Return True if fpath is an executable file path. Parameters ---------- fpath: str File path Returns ------- is_executable: bool """ return op.isfile(fpath) and os.access(fpath, os.X_OK) def which(cmd_name): """Returns the absolute path of the given CLI program name.""" if (sys.version_info > (3, 0)): return which_py3(cmd_name) else: # Python 2 code in this block return which_py2(cmd_name) def which_py3(cmd_name): return shutil.which(cmd_name) def which_py2(cmd_name): fpath, fname = os.path.split(cmd_name) if fpath: if is_exe(cmd_name): return cmd_name else: for path in os.environ["PATH"].split(os.pathsep): path = path.strip('"') exe_file = os.path.join(path, cmd_name) if is_exe(exe_file): return exe_file return None def check_command(cmd_name): """ Raise a FileNotFoundError if the command is not found. :param cmd_name: """ if which(cmd_name) is None: raise FileNotFoundError('Could not find command named {}.'.format(cmd_name)) def call_command(cmd_name, args_strings): """Call CLI command with arguments and returns its return value. Parameters ---------- cmd_name: str Command name or full path to the binary file. arg_strings: str Argument strings list. Returns ------- return_value Command return value. """ if not op.isabs(cmd_name): cmd_fullpath = which(cmd_name) else: cmd_fullpath = cmd_name try: cmd_line = [cmd_fullpath] + args_strings log.info('Calling: {}.'.format(cmd_line)) #retval = subprocess.check_call(cmd_line) retval = subprocess.call(' '.join(cmd_line), shell=True) except CalledProcessError as ce: log.exception("Error calling command with arguments: " "{} \n With return code: {}".format(cmd_line, ce.returncode)) raise else: return retval PK4FG`Ý>>docstamp/config.py# coding=utf-8 # ------------------------------------------------------------------------------- # Author: Alexandre Manhaes Savio # Grupo de Inteligencia Computational # Universidad del Pais Vasco UPV/EHU # # 2015, Alexandre Manhaes Savio # Use this at your own risk! # ------------------------------------------------------------------------------- import os import re import logging import os.path as op from sys import platform as _platform from .commands import which, is_exe LOGGING_LVL = logging.INFO logging.basicConfig(level=LOGGING_LVL) def find_file_match(folder_path, regex=''): """ Returns absolute paths of files that match the regex within folder_path and all its children folders. Note: The regex matching is done using the match function of the re module. Parameters ---------- folder_path: string regex: string Returns ------- A list of strings. """ outlist = [] for root, dirs, files in os.walk(folder_path): outlist.extend([op.join(root, f) for f in files if re.match(regex, f)]) return outlist def get_system_path(): if _platform == "linux" or _platform == "linux2": return os.environ['PATH'] elif _platform == "darwin": return os.environ['PATH'] elif _platform == "win32": # don't know if this works return os.environ['PATH'] def get_other_program_folders(): if _platform == "linux" or _platform == "linux2": return ['/opt/bin'] elif _platform == "darwin": return ['/Applications', op.join(os.environ['HOME'], 'Applications')] elif _platform == "win32": # don't know if this works return ['C:\Program Files'] def get_temp_dir(): if _platform == "linux" or _platform == "linux2": return '/tmp' elif _platform == "darwin": return '.' elif _platform == "win32": # don't know if this works return None def find_in_other_programs_folders(app_name): app_name_regex = '^' + app_name + '$' other_folders = get_other_program_folders() for folder in other_folders: abin_file = find_program(folder, app_name_regex) if abin_file is not None: return abin_file return None def find_program(root_dir, exec_name): file_matches = find_file_match(root_dir, exec_name) for f in file_matches: if is_exe(f): return f return None def ask_for_path_of(app_name): bin_path = None while bin_path is not None: bin_path = input('Insert path of {} executable file [Press Ctrl+C to exit]: '.format(app_name)) if not op.exists(bin_path): print('Could not find file {}. Try it again.'.format(bin_path)) bin_path = None continue if not is_exe(bin_path): print('No execution permissions on file {}. Try again.'.format(bin_path)) bin_path = None continue return bin_path def proactive_search_of(app_name): if _platform == 'win32': bin_name = app_name + '.exe' else: bin_name = app_name bin_path = which(app_name) if bin_path is not None and is_exe(bin_path): return bin_path bin_path = find_in_other_programs_folders(bin_name) if bin_path is not None: return bin_path return ask_for_path_of(bin_name) def get_inkscape_binpath(): if 'INKSCAPE_BINPATH' not in globals(): global INKSCAPE_BINPATH INKSCAPE_BINPATH = proactive_search_of('inkscape') return INKSCAPE_BINPATH def get_lyx_binpath(): if 'LYX_BINPATH' not in globals(): global LYX_BINPATH LYX_BINPATH = proactive_search_of('lyx') return LYX_BINPATH #TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates') # JINJA_ENV = Environment(loader=PackageLoader('docstamp', 'templates')) #JINJA_ENV = Environment(loader=FileSystemLoader(TEMPLATES_DIR)) # FILE_EXPORTERS = {'.svg': Inkscape,} # '.tex': PdfLatex, # '.lyx': LyX} PK*oFudocstamp/data_source.py import sys from .unicode_csv import UnicodeWriter if sys.version_info[0] >= 3: raw_input = input class Google_data: def getCSV(self): """ Returns ------- filename: str """ import getpass import gspread user = raw_input ("Insert Google username:") password = getpass.getpass (prompt="Insert password:") name = raw_input ("SpreadSheet filename on Drive:") sheet = raw_input ("Sheet name (first sheet is default):") cl = gspread.login(user, password) sh = cl.open(name) if not(sheet.strip()): ws = sh.sheet1 sheet = "1" else: ws = sh.worksheet(sheet) filename = name + '-worksheet_' + sheet + '.csv' with open(filename, 'wb') as f: writer = UnicodeWriter(f) writer.writerows(ws.get_all_values()) return filename PKKH& docstamp/file_utils.py# coding=utf-8 # ------------------------------------------------------------------------------- # Author: Alexandre Manhaes Savio # Grupo de Inteligencia Computational # Universidad del Pais Vasco UPV/EHU # # 2015, Alexandre Manhaes Savio # Use this at your own risk! # ------------------------------------------------------------------------------- import os import os.path as op import tempfile import logging from glob import glob from .config import get_temp_dir log = logging.getLogger(__name__) def get_extension(filepath, check_if_exists=False): """Return the extension of fpath. Parameters ---------- fpath: string File name or path check_if_exists: bool Returns ------- str The extension of the file name or path """ if check_if_exists: if not op.exists(filepath): err = 'File not found: ' + filepath log.error(err) raise IOError(err) try: rest, ext = op.splitext(filepath) except: raise else: return ext def add_extension_if_needed(filepath, ext, check_if_exists=False): """Add the extension ext to fpath if it doesn't have it. Parameters ---------- filepath: str File name or path ext: str File extension check_if_exists: bool Returns ------- File name or path with extension added, if needed. """ if not filepath.endswith(ext): filepath += ext if check_if_exists: if not op.exists(filepath): err = 'File not found: ' + filepath log.error(err) raise IOError(err) return filepath def remove_ext(filepath): """Removes the extension of the file. Parameters ---------- filepath: str File path or name Returns ------- str File path or name without extension """ return filepath[:filepath.rindex(get_extension(filepath))] def get_tempfile(suffix='.txt', dirpath=None): """ Return a temporary file with the given suffix within dirpath. If dirpath is None, will look for a temporary folder in your system. Parameters ---------- suffix: str Temporary file name suffix dirpath: str Folder path where create the temporary file Returns ------- temp_filepath: str The path to the temporary path """ if dirpath is None: dirpath = get_temp_dir() return tempfile.NamedTemporaryFile(suffix=suffix, dir=dirpath) def cleanup(workdir, extension): """ Remove the files in workdir that have the given extension. Parameters ---------- workdir: Folder path from where to clean the files. extension: str File extension without the dot, e.g., 'txt' """ [os.remove(f) for f in glob(op.join(workdir, '*.' + extension))] def mkdir(dirpath): """Create a folder in `dirpath` if it does'nt exist.""" if not op.exists(dirpath): os.mkdir(dirpath) def csv_to_json(csv_filepath, json_filepath, fieldnames, ignore_first_line=True): """ Convert a CSV file in `csv_filepath` into a JSON file in `json_filepath`. Parameters ---------- csv_filepath: str Path to the input CSV file. json_filepath: str Path to the output JSON file. Will be overwritten if exists. fieldnames: List[str] Names of the fields in the CSV file. ignore_first_line: bool """ import csv import json csvfile = open(csv_filepath, 'r') jsonfile = open(json_filepath, 'w') reader = csv.DictReader(csvfile, fieldnames) rows = [] if ignore_first_line: next(reader) for row in reader: rows.append(row) json.dump(rows, jsonfile) jsonfile.close() csvfile.close() def replace_file_content(filepath, old, new, max=0): """ Modify the content of `filepath`, replacing `old` for `new`. Parameters ---------- filepath: str Path to the file to be modified. It will be overwritten. old: str This is old substring to be replaced. new: str This is new substring, which would replace old substring. max: int If larger than 0, Only the first `max` occurrences are replaced. """ with open(filepath, 'r') as f: content = f.read() content = content.replace(old, new, max) with open(filepath, 'w') as f: f.write(content) def cleanup_docstamp_output(output_dir): """ Remove the 'tmp*.aux', 'tmp*.out' and 'tmp*.log' files in `output_dir`. :param output_dir: """ suffixes = ['aux', 'out', 'log'] files = [f for suf in suffixes for f in glob('tmp*.{}'.format(suf))] [os.remove(file) for file in files] PKyr1Hdocstamp/gdrive.py""" Helpers to connect and work with google drive spreadsheets """ import gspread import json from oauth2client.client import SignedJwtAssertionCredentials def connect_to_gspread(google_api_key_file): """ Return the connection to a Google spreadsheet. :param google_api_key_file: path to a json file with credentials. Detailed here: https://developers.google.com/identity/protocols/application-default-credentials :type google_api_key_file: str :return: gspread.client.Client """ json_key = json.load(open(google_api_key_file)) scope = ["https://spreadsheets.google.com/feeds"] # authenticate credentials = SignedJwtAssertionCredentials(json_key["client_email"], json_key["private_key"].encode("utf-8"), scope) return gspread.authorize(credentials) def get_spreadsheet(api_key_file_path, doc_key): """ :param api_key_file_path: path to a json file with credentials. :param doc_key: key of the document :return: gspread.models.Spreadsheet """ gc = connect_to_gspread(api_key_file_path) return gc.open_by_key(doc_key) def worksheet_to_dict(wks, header='', start_row=1): """ Transform a gspread worksheet into a pandas DataFrame. :param wks: :param header: :param start_row: :return: """ all_rows = wks.get_all_values() if not header: header = all_rows[0] #print(list(zip(header, all_rows))) #[dict(zip(header, values)) for values in all_rows[1:]] return [dict(zip(header, values)) for values in all_rows[start_row:]] def get_spread_url(spread_key): return 'https://docs.google.com/spreadsheets/d/{}'.format(spread_key) def get_live_form_url(form_key): return 'https://docs.google.com/forms/d/{}/viewform'.format(form_key) def get_edit_form_url(form_key): return 'https://docs.google.com/forms/d/{}/edit'.format(form_key) PK{r1H docstamp/inkscape.py# coding=utf-8 # ------------------------------------------------------------------------------- # Author: Alexandre Manhaes Savio # Grupo de Inteligencia Computational # Universidad del Pais Vasco UPV/EHU # # 2015, Alexandre Manhaes Savio # Use this at your own risk! # ------------------------------------------------------------------------------- import logging import os.path as op from .config import get_inkscape_binpath from .commands import call_command log = logging.getLogger(__name__) class FileExporter(object): """A base class to template a basic file converter: from the template to anything the output_filepath extension says. """ def export(input_filepath, output_filepath, args_string, **kwargs): raise NotImplementedError def call_inkscape(args_strings, inkscape_binpath=None): """Call inkscape CLI with arguments and returns its return value. Parameters ---------- args_string: list of str inkscape_binpath: str Returns ------- return_value Inkscape command CLI call return value. """ log.debug('Looking for the binary file for inkscape.') if inkscape_binpath is None: inkscape_binpath = get_inkscape_binpath() if inkscape_binpath is None or not op.exists(inkscape_binpath): raise IOError('Inkscape binary has not been found. Please check ' 'configuration.') return call_command(inkscape_binpath, args_strings) def inkscape_export(input_file, output_file, export_flag="-A", dpi=90, inkscape_binpath=None): """ Call Inkscape to export the input_file to outpu_file using the specific export argument flag for the output file type. Parameters ---------- input_file: str Path to the input file output_file: str Path to the output file export_flag: str Inkscape CLI flag to indicate the type of the output file Returns ------- return_value Command call return value """ if not op.exists(input_file): log.error('File {} not found.'.format(input_file)) raise IOError((0, 'File not found.', input_file)) if not '=' in export_flag: export_flag += ' ' arg_strings = [] arg_strings += ['--without-gui'] arg_strings += ['{}"{}"'.format(export_flag, output_file)] arg_strings += ['--export-dpi={}'.format(dpi)] arg_strings += ['"{}"'.format(input_file)] return call_inkscape(arg_strings, inkscape_binpath=inkscape_binpath) def svg2pdf(svg_file_path, pdf_file_path, dpi=150, inkscape_binpath=None): """ Transform SVG file to PDF file """ return inkscape_export(svg_file_path, pdf_file_path, export_flag="-A", dpi=dpi, inkscape_binpath=inkscape_binpath) def svg2png(svg_file_path, png_file_path, dpi=150, inkscape_binpath=None): """ Transform SVG file to PNG file """ return inkscape_export(svg_file_path, png_file_path, export_flag="-e", dpi=dpi, inkscape_binpath=inkscape_binpath) PKϩoFvdocstamp/model.py# coding=utf-8 # ------------------------------------------------------------------------------- # Author: Alexandre Manhaes Savio # Grupo de Inteligencia Computational # Universidad del Pais Vasco UPV/EHU # # 2015, Alexandre Manhaes Savio # Use this at your own risk! # ------------------------------------------------------------------------------- import json def translate_key_values(adict, translations, default=''): """Modify the keys in adict to the ones in translations. Be careful, this will modify your input dictionary. The keys not present in translations will be left intact. Parameters ---------- adict: a dictionary translations: iterable of 2-tuples Each 2-tuple must have the following format: (, ) Returns ------- Translated adict """ for src_key, dst_key in translations: adict[dst_key] = adict.pop(src_key, default) return adict def json_to_dict(json_str): """Convert json string into dict""" return json.JSONDecoder().decode(json_str) class JSONMixin(object): """Simple, stateless json utilities mixin. Requires class to implement two methods: to_json(self): convert data to json-compatible datastructure (dict, list, strings, numbers) @classmethod from_json(cls, json): load data from json-compatible structure. """ @classmethod def from_json_str(cls, json_str): """Convert json string representation into class instance. Args: json_str: json representation as string. Returns: New instance of the class with data loaded from json string. """ dct = json_to_dict(json_str) return cls(**dct) def to_json_str(self): """Convert data to json string representation. Returns: json representation as string. """ adict = dict(vars(self), sort_keys=True) adict['type'] = self.__class__.__name__ return json.dumps(adict) def __repr__(self): return self.to_json_str() def __str__(self): return self.to_json_str() PKKH oodocstamp/pdf_utils.py""" Function helpers to manage PDF files. """ from PyPDF2 import PdfFileMerger, PdfFileReader def merge_pdfs(pdf_filepaths, out_filepath): """ Merge all the PDF files in `pdf_filepaths` in a new PDF file `out_filepath`. Parameters ---------- pdf_filepaths: list of str Paths to PDF files. out_filepath: str Path to the result PDF file. Returns ------- path: str The output file path. """ merger = PdfFileMerger() for pdf in pdf_filepaths: merger.append(PdfFileReader(open(pdf, 'rb'))) merger.write(out_filepath) return out_filepath PKƜ3H'6""docstamp/pdflatex.py# coding=utf-8 # ------------------------------------------------------------------------------- # Author: Alexandre Manhaes Savio # Grupo de Inteligencia Computational # Universidad del Pais Vasco UPV/EHU # # 2015, Alexandre Manhaes Savio # Use this at your own risk! # ------------------------------------------------------------------------------- import shutil import logging import os.path as op from .commands import call_command, simple_call, check_command from .file_utils import remove_ext, cleanup log = logging.getLogger(__name__) def tex2pdf(tex_file, output_file=None, output_format='pdf'): """ Call PDFLatex to convert TeX files to PDF. Parameters ---------- tex_file: str Path to the input LateX file. output_file: str Path to the output PDF file. If None, will use the same output directory as the tex_file. output_format: str Output file format. Choices: 'pdf' or 'dvi'. Default: 'pdf' Returns ------- return_value PDFLatex command call return value. """ if not op.exists(tex_file): raise IOError('Could not find file {}.'.format(tex_file)) if output_format != 'pdf' and output_format != 'dvi': raise ValueError("Invalid output format given {}. Can only accept 'pdf' or 'dvi'.".format(output_format)) cmd_name = 'pdflatex' check_command(cmd_name) args_strings = [cmd_name] if output_file is not None: args_strings += ['-output-directory="{}" '.format(op.abspath(op.dirname(output_file)))] result_dir = op.dirname(output_file) if output_file else op.dirname(tex_file) args_strings += ['-output-format="{}"'.format(output_format)] args_strings += ['"' + tex_file + '"'] log.debug('Calling command {} with args: {}.'.format(cmd_name, args_strings)) #ret = call_command(cmd_name, args_strings) ret = simple_call(args_strings) result_file = op.join(result_dir, remove_ext(op.basename(tex_file)) + '.' + output_format) if op.exists(result_file): shutil.move(result_file, output_file) else: raise IOError('Could not find PDFLatex result file.') log.debug('Cleaning *.aux and *.log files from folder {}.'.format(result_dir)) cleanup(result_dir, 'aux') cleanup(result_dir, 'log') return ret def xetex2pdf(tex_file, output_file=None, output_format='pdf'): """ Call XeLatex to convert TeX files to PDF. Parameters ---------- tex_file: str Path to the input LateX file. output_file: str Path to the output PDF file. If None, will use the same output directory as the tex_file. output_format: str Output file format. Choices: 'pdf' or 'dvi'. Default: 'pdf' Returns ------- return_value XeLatex command call return value. """ if not op.exists(tex_file): raise IOError('Could not find file {}.'.format(tex_file)) if output_format != 'pdf' and output_format != 'dvi': raise ValueError("Invalid output format given {}. Can only accept 'pdf' or 'dvi'.".format(output_format)) cmd_name = 'xelatex' check_command(cmd_name) args_strings = [cmd_name] if output_file is not None: args_strings += ['-output-directory="{}"'.format(op.abspath(op.dirname(output_file)))] if output_format == 'dvi': args_strings += ['-no-pdf'] result_dir = op.dirname(output_file) if output_file else op.dirname(tex_file) args_strings += ['"' + tex_file + '"'] log.debug('Calling command {} with args: {}.'.format(cmd_name, args_strings)) #ret = call_command(cmd_name, args_strings) ret = simple_call(args_strings) result_file = op.join(result_dir, remove_ext(op.basename(tex_file)) + '.pdf') if op.exists(result_file): shutil.move(result_file, output_file) else: raise IOError('Could not find PDFLatex result file.') log.debug('Cleaning *.aux and *.log files from folder {}.'.format(result_dir)) cleanup(result_dir, 'aux') cleanup(result_dir, 'log') return ret PKYEH嫬docstamp/qrcode.py""" Utility functions to create QRCodes using `qrcode`. """ import qrcode from .file_utils import replace_file_content def save_into_qrcode(text, out_filepath, color='000000', box_size=10, pixel_size=1850): """ Save `text` in a qrcode svg image file. Parameters ---------- text: str The string to be codified in the QR image. out_filepath: str Path to the output file color: str A RGB color expressed in 6 hexadecimal values. box_size: scalar Size of the QR code boxes. """ try: #img = qrcode.make(vcard_string, image_factory=qrcode.image.svg.SvgPathImage, fit=True) qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=box_size, border=0,) qr.add_data(text) qr.make(fit=True) except Exception as exc: raise Exception('Error trying to generate QR code ' ' from `vcard_string`: {}'.format(text)) from exc else: img = qr.make_image(image_factory=qrcode.image.svg.SvgPathImage) _ = _qrcode_to_file(img, out_filepath) if color != '000000': replace_file_content(out_filepath, 'fill:#000000', 'fill:#{}'.format(color)) return out_filepath def _qrcode_to_file(qrcode, out_filepath): """ Save a `qrcode` object into `out_filepath`. Parameters ---------- qrcode: qrcode object out_filepath: str Path to the output file. """ try: qrcode.save(out_filepath) except Exception as exc: raise IOError('Error trying to save QR code file {}.'.format(out_filepath)) from exc else: return qrcode PK1GHfBI I docstamp/svg_utils.py""" Function helpers to do stuff on svg files. """ import svgutils.transform as sg def replace_chars_for_svg_code(svg_content): """ Replace known special characters to SVG code. Parameters ---------- svg_content: str Returns ------- corrected_svg: str Corrected SVG content """ result = svg_content svg_char = {'&': '&', '>': '>', '<': '<'} for c in svg_char: result = result.replace(c, svg_char[c]) return result def _check_svg_file(svg_file): """ Try to read a SVG file if `svg_file` is a string. Raise an exception in case of error or return the svg object. If `svg_file` is a svgutils svg object, will just return it. Parameters ---------- svg_file: str or svgutils svg object If a `str`: path to a '.svg' file, otherwise a svgutils svg object is expected. Returns ------- svgutils svg object Raises ------ Exception if any error happens. """ if isinstance(svg_file, str): try: svg = sg.fromfile(svg_file) except Exception as exc: raise Exception('Error reading svg file {}.'.format(svg_file)) from exc else: return svg # elif isinstance(svg_file, svgutils.) #TODO check if svg_file is a svgutils svg object else: raise ValueError('Expected `svg_file` to be `str` or `svgutils.SVG`, got {}.'.format(type(svg_file))) def merge_svg_files(svg_file1, svg_file2, x_coord, y_coord, scale=1): """ Merge `svg_file2` in `svg_file1` in the given positions `x_coord`, `y_coord` and `scale`. Parameters ---------- svg_file1: str or svgutils svg document object Path to a '.svg' file. svg_file2: str or svgutils svg document object Path to a '.svg' file. x_coord: float Horizontal axis position of the `svg_file2` content. y_coord: float Vertical axis position of the `svg_file2` content. scale: float Scale to apply to `svg_file2` content. Returns ------- `svg1` svgutils object with the content of 'svg_file2' """ svg1 = _check_svg_file(svg_file1) svg2 = _check_svg_file(svg_file2) svg2_root = svg2.getroot() svg2_root.moveto(x_coord, y_coord, scale=scale) svg1.append([svg2_root]) return svg1 PK{r1HMdocstamp/template.py# coding=utf-8 # ------------------------------------------------------------------------------- # Author: Alexandre Manhaes Savio # Grupo de Inteligencia Computational # Universidad del Pais Vasco UPV/EHU # # 2015, Alexandre Manhaes Savio # Use this at your own risk! # ------------------------------------------------------------------------------- import os.path as op import shutil import logging from jinja2 import Environment, FileSystemLoader from .inkscape import svg2pdf, svg2png from .pdflatex import tex2pdf, xetex2pdf from .file_utils import get_tempfile log = logging.getLogger(__name__) def get_environment_for(file_path): """Return a Jinja2 environment for where file_path is. Parameters ---------- file_path: str Returns ------- jinja_env: Jinja2.Environment """ work_dir = op.dirname(op.abspath(file_path)) if not op.exists(work_dir): raise IOError('Could not find folder for dirname of file {}.'.format(file_path)) try: jinja_env = Environment(loader=FileSystemLoader(work_dir)) except: raise else: return jinja_env def get_doctype_by_extension(extension): if 'txt' in extension: doc_type = TextDocument elif 'svg' in extension: doc_type = SVGDocument elif 'tex' in extension: doc_type = LateXDocument else: raise ValueError('Could not identify the `doc_type` for `extension` {}.'.format(extension)) return doc_type def get_doctype_by_command(command): if not command: doc_type = TextDocument elif command == 'inkscape': doc_type = SVGDocument elif command == 'pdflatex': doc_type = PDFLateXDocument elif command == 'xelatex': doc_type = XeLateXDocument else: raise ValueError('Could not identify the `doc_type` for `command` {}.'.format(command)) return doc_type class TextDocument(object): """ A plain text document model. Parameters ---------- template_file_path: str Document template file path. doc_contents: dict Dictionary with content values for the template to be filled. """ def __init__(self, template_file_path, doc_contents=None): if not op.exists(template_file_path): raise IOError('Could not find template file {}.'.format(template_file_path)) self._setup_template_file(template_file_path) if doc_contents is not None: self.file_content_ = self.fill(doc_contents) def _setup_template_file(self, template_file_path): """ Setup self.template Parameters ---------- template_file_path: str Document template file path. """ try: template_file = template_file_path template_env = get_environment_for(template_file_path) template = template_env.get_template(op.basename(template_file)) except: raise else: self._template_file = template_file self._template_env = template_env self.template = template def fill(self, doc_contents): """ Fill the content of the document with the information in doc_contents. Parameters ---------- doc_contents: dict Set of values to set the template document. Returns ------- filled_doc: str The content of the document with the template information filled. """ try: filled_doc = self.template.render(**doc_contents) except: log.exception('Error rendering Document ' 'for {}.'.format(doc_contents)) raise else: self.file_content_ = filled_doc return filled_doc def save_content(self, file_path): """ Save the content of the .txt file in a text file. Parameters ---------- file_path: str Path to the output file. """ if self.file_content_ is None: msg = 'Template content has not been updated. Please fill the template before rendering it.' log.exception(msg) raise ValueError(msg) try: with open(file_path, "w") as f: f.write(self.file_content_)#.encode('utf8')) except: log.exception('Error saving {} file in {}'.format(str(self.__class__), file_path)) raise def render(self, file_path, **kwargs): """ See self.save_content """ return self.save_content(file_path) @classmethod def from_template_file(self, template_file_path, command=None): """ Create Parameters ---------- template_file_path: str Returns ------- doc """ # get template file extension ext = op.basename(template_file_path).split('.')[-1] try: doc_type = get_doctype_by_command(command) except ValueError: doc_type = get_doctype_by_extension(ext) except: raise else: return doc_type(template_file_path) class SVGDocument(TextDocument): """ A .svg template document model. See GenericDocument. """ _template_file = 'badge_template.svg' def render(self, file_path, **kwargs): """ Save the content of the .svg file in the chosen rendered format. Parameters ---------- file_path: str Path to the output file. Kwargs ------ file_type: str Choices: 'png', 'pdf', 'svg' Default: 'pdf' dpi: int Dots-per-inch for the png and pdf. Default: 150 """ temp = get_tempfile(suffix='.svg') self.save_content(temp.name) file_type = kwargs.get('file_type', 'pdf') dpi = kwargs.get('dpi', 150) #file_type = 'png' #dpi = 150 try: if file_type == 'svg': shutil.copyfile(temp.name, file_path) elif file_type == 'png': svg2png(temp.name, file_path, dpi=dpi) elif file_type == 'pdf': svg2pdf(temp.name, file_path, dpi=dpi) except: log.exception('Error exporting file {} to {}'.format(file_path, file_type)) raise class LateXDocument(TextDocument): """ A .tex template document model. See GenericDocument. """ _render_function = staticmethod(tex2pdf) def render(self, file_path, **kwargs): """ Save the content of the .text file in the PDF. Parameters ---------- file_path: str Path to the output file. """ temp = get_tempfile(suffix='.tex') self.save_content(temp.name) try: self._render_function(temp.name, file_path, output_format='pdf') except: log.exception('Error exporting file {} to PDF.'.format(file_path)) raise class PDFLateXDocument(LateXDocument): pass class XeLateXDocument(LateXDocument): _render_function = staticmethod(xetex2pdf) PKyr1H}5,docstamp/unicode_csv.py# -*- coding: utf-8 -*- import csv import codecs try: from cStringIO import StringIO except: from io import StringIO class UTF8Recoder: """ Iterator that reads an encoded stream and reencodes the input to UTF-8 """ def __init__(self, f, encoding): self.reader = codecs.getreader(encoding)(f) def __iter__(self): return self def next(self): return self.reader.next().encode("utf-8") class UnicodeReader: """ A CSV reader which will iterate over lines in the CSV file "f", which is encoded in the given encoding. """ def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds): f = UTF8Recoder(f, encoding) self.reader = csv.DictReader(f, dialect=dialect, **kwds) def next(self): row = self.reader.next() return {unicode(s, "utf-8"): unicode(row[s], "utf-8") for s in row} def __iter__(self): return self @property def fieldnames(self): return self.reader.fieldnames class UnicodeWriter: """ A CSV writer which will write rows to CSV file "f", which is encoded in the given encoding. """ def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds): # Redirect output to a queue self.queue = StringIO() self.writer = csv.writer(self.queue, dialect=dialect, **kwds) self.stream = f self.encoder = codecs.getincrementalencoder(encoding)() def writerow(self, row): self.writer.writerow([s.encode("utf-8") for s in row]) # Fetch UTF-8 output from the queue ... data = self.queue.getvalue() data = data.decode("utf-8") # ... and reencode it into the target encoding data = self.encoder.encode(data) # write to the target stream self.stream.write(data) # empty queue self.queue.truncate(0) def writerows(self, rows): for row in rows: self.writerow(row) PKKHJdocstamp/vcard.py""" Function helpers to manage contact lists and vcard formats. """ from collections import namedtuple from copy import copy CONTACT_FIELDS = ("Name", "Surname", "Tagline", "Affiliation", "Python_experience", "Email", "Phone", "Company_homepage", "Personal_homepage") Contact = namedtuple('Contact', CONTACT_FIELDS) # vCard helpers def create_contact(person_info): """ Return a Contact from the dict `person_info`. Parameters ---------- person_info: dict[str] -> str Returns ------- contact: Contact """ person = copy(person_info) person['Personal_homepage'] = person['Personal_homepage'].replace('http://', '') person['Company_homepage'] = person['Company_homepage'].replace('http://', '') if not person['Affiliation']: person['Affiliation'] = person['Company_homepage'] return Contact(**person) def create_vcard3_str(name, surname, displayname, email='', org='', url='', note=''): """ Create a vCard3.0 string with the given parameters. Reference: http://www.evenx.com/vcard-3-0-format-specification """ vcard = [] vcard += ['BEGIN:VCARD'] vcard += ['VERSION:3.0'] if name and surname: name = name.strip() vcard += ['N:{};{}'.format(name, surname)] if not displayname: displayname = '{} {}'.format(name, surname) vcard += ['FN:{}'.format(displayname)] if email: vcard += ['EMAIL:{}'.format(email)] if org: vcard += ['ORG:{}'.format(org)] if url: vcard += ['URL:{}'.format(url.replace('http://', ''))] if note: vcard += ['NOTE:{}'.format(note)] vcard += ['END:VCARD'] return '\n'.join([field.strip() for field in vcard]) PK*KHG wwdocstamp/version.py''' Unique version information place ''' __version__ = '0.3.0' VERSION = tuple(int(x) for x in __version__.split('.')) PKGov00docstamp/xml_utils.py""" Function helpers to treat XML content. """ from xml.sax.saxutils import escape, unescape from .file_utils import replace_file_content xml_escape_table = { "&": "&", '"': """, "'": "'", ">": ">", "<": "<", } xml_unescape_table = {v: k for k, v in xml_escape_table.items()} def xml_escape(text): """ Replace not valid characters for XML such as &, < and > to their valid replacement strings Parameters ---------- text: str The text to be escaped. Returns ------- escaped_text: str """ return escape(text, xml_escape_table) def xml_unescape(text): """ Do the inverse of `xml_escape`. Parameters ---------- text: str The text to be escaped. Returns ------- escaped_text: str """ return unescape(text, xml_unescape_table) def change_xml_encoding(filepath, src_enc, dst_enc='utf-8'): """ Modify the encoding entry in the XML file. Parameters ---------- filepath: str Path to the file to be modified. src_enc: str Encoding that is written in the file dst_enc: str Encoding to be set in the file. """ enc_attr = "encoding='{}'" replace_file_content(filepath, enc_attr.format(src_enc), enc_attr.format(dst_enc), 1) PK=KH]I` $docstamp-0.3.0.data/scripts/docstamp#!python # coding=utf-8 # ------------------------------------------------------------------------------- # Author: Alexandre Manhaes Savio # Grupo de Inteligencia Computational # Universidad del Pais Vasco UPV/EHU # # 2015, Alexandre Manhaes Savio # Use this at your own risk! # ------------------------------------------------------------------------------- import os import json import logging import argparse import math import os.path as op import sys if sys.version_info >= (3, 0): from csv import DictReader else: from docstamp.unicode_csv import UnicodeReader as DictReader from docstamp.file_utils import get_extension from docstamp.template import TextDocument from docstamp.model import json_to_dict from docstamp.config import LOGGING_LVL from docstamp.data_source import Google_data logging.basicConfig(level=LOGGING_LVL) log = logging.getLogger(__name__) ACCEPTED_DOCS = "Inkscape (.svg), PDFLatex (.tex), XeLatex (.tex)" def get_items_from_csv(csv_filepath): # CSV to JSON # one JSON object for each item if not op.exists(csv_filepath): raise IOError('Could not find file {}.'.format(csv_filepath)) log.debug('Reading CSV elements from {}.'.format(csv_filepath)) items = {} with open(str(csv_filepath), 'r') as csvfile: reader = DictReader(csvfile) for idx, row in enumerate(reader): item = json_to_dict(json.dumps(row)) if any([item[i] != '' for i in item]): items[idx] = item return items, reader.fieldnames def verbose_switch(verbose=False): if verbose: log_level = logging.DEBUG else: log_level = logging.INFO logging.getLogger().setLevel(log_level) def create_argparser(): parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) # parser.add_argument('-s', '--search', action='store', # dest='search', # help=''' # Search condition string in the format: =. # Look at --input argument help for the valid field_names. # Put as many of these as you need. # ''') parser.add_argument('-i', '--input', action='store', dest='input', required=True, help='''Path to the CSV file with the data elements to be used to fill the template. This file must have the same fields as the template file. ''') parser.add_argument('-t', '--template', action='store', dest='template', required=True, help='''Template file path. The extension of this file will be used to determine what software to use to render the documents: ''' + ACCEPTED_DOCS) parser.add_argument('-f', '--file_name_fields', action='append', dest='file_name_fields', help='''The field or fields that will be used to name the output files. Otherwise files will be numbered.''') parser.add_argument('-o', '--output', action='store', dest='output', default='stamped', help='Output folder path. Default stamped') parser.add_argument('-n', '--name', action='store', dest='basename', help='''Output files prefix. Default: Template file name.''') parser.add_argument('-d', '--type', choices=['pdf', 'png', 'svg'], action='store', dest='file_type', default='pdf', help='Output file type.') parser.add_argument('-c', '--command', choices=['', 'inkscape', 'pdflatex', 'xelatex'], action='store', dest='command', default='', help='The rendering command to be used in case file name extension is not specific.') parser.add_argument('--idx', action='append', dest='index', default=[], help='''Index/es of the CSV file that you want to create the document from. Note that the samples numbers start from 0 and the empty ones do not count.''') parser.add_argument('--dpi', type=int, action='store', dest='dpi', default=150, help='Output file resolution') parser.add_argument('-g', '--google', action='store_true', help='Fetch data from Google Drive') parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', default=False, help='Output debug logs.') return parser if __name__ == '__main__': parser = create_argparser() try: args = parser.parse_args() except argparse.ArgumentError as exc: log.exception('Error parsing arguments.') parser.error(str(exc.message)) exit(-1) if args.google: input_file = Google_data.getCSV() else: input_file = args.input if not op.exists(input_file): log.exception('Could not find file {}'.format(input_file)) exit(-1) # get args output_dir = args.output file_type = args.file_type template = args.template file_name_fields = args.file_name_fields index = args.index dpi = args.dpi basename = args.basename command = args.command verbose = args.verbose # setup verbose mode verbose_switch(verbose) # init set of template contents items, fieldnames = get_items_from_csv(input_file) # check if got any item if len(items) == 0: print('Quiting because found 0 items.') exit(-1) if not len(file_name_fields): # set the number of zeros that the files will have n_zeros = math.floor(math.log10(len(items))) + 1 else: # check that file_name_fields has all valid fields for field_name in file_name_fields: if field_name not in fieldnames: raise ValueError('Field name {} not found in input file ' ' header.'.format(field_name)) # filter the items if index if index: myitems = {int(idx): items[int(idx)] for idx in index} items = myitems log.debug('Using the elements with index {} of the input file.'.format(index)) # make output folder if not os.path.exists(output_dir): os.mkdir(output_dir) # create template document model log.debug('Creating the template object using the file {}.'.format(template)) template_doc = TextDocument.from_template_file(template, command) # get file extension file_extension = get_extension(template) log.debug('Created an object of type {}.'.format(type(template_doc))) # let's stamp them! for idx in items: item = items[idx] if not len(file_name_fields): file_name = str(idx).zfill(n_zeros) else: field_values = [] try: for field_name in file_name_fields: field_values.append(item[field_name].replace(' ', '')) except: log.exception('Could not get field {} value from' ' {}'.format(field_name, item)) exit(-1) file_name = '_'.join(field_values) log.debug('Filling template {} with values of item {}.'.format(file_name, idx)) try: template_doc.fill(item) except: log.exception('Error filling document for {}th item'.format(idx)) continue # set output file path if basename is None: basename = op.basename(template).replace('.svg', '') file_name = basename + '_' + file_name file_path = os.path.join(output_dir, file_name + '.' + file_type) kwargs = {'file_type': file_type, 'dpi': dpi} log.debug('Rendering file {}.'.format(file_path)) try: template_doc.render(file_path, **kwargs) except: log.exception('Error creating {} for {}.'.format(file_path, item)) exit(-1) PK=KH9%KUU)docstamp-0.3.0.data/scripts/svg_export.py#!python # -*- coding: utf-8 -*- import logging import argparse from docstamp.inkscape import svg2pdf, svg2png logging.basicConfig(level=logging.INFO) log = logging.getLogger(__name__) def create_argparser(): parser = argparse.ArgumentParser() parser.add_argument('-i', '--input', action='store', dest='input', help='Input .svg file path') parser.add_argument('-o', '--output', action='store', dest='output', help='Output file path') parser.add_argument('-t', '--type', choices=['pdf', 'png'], action='store', dest='file_type', default='pdf', help='Output file type') #parser.add_argument('--dpi', type=int, action='store', dest='dpi', # default=150, help='Output file resolution') return parser if __name__ == '__main__': parser = create_argparser() try: args = parser.parse_args() except argparse.ArgumentError as exc: log.exception('Error parsing arguments.') parser.error(str(exc.message)) exit(-1) input_file = args.input output_file = args.output file_type = args.file_type dpi = args.dpi if file_type == 'png': svg2png(input_file, output_file, dpi=dpi) elif file_type == 'pdf': svg2pdf(input_file, output_file, dpi=dpi) PK@KHMl(docstamp-0.3.0.dist-info/DESCRIPTION.rstdocstamp ====== .. image:: https://pypip.in/v/docstamp/badge.png :target: https://pypi.python.org/pypi/docstamp :alt: Latest PyPI version .. image:: ''.png :target: '' :alt: Latest Travis CI build status Initially it was a conference badge creator based on SVG templates (https://github.com/PythonSanSebastian/pydger), but we thought it could be more generic and have many other applications. DocStamp is a generic template renderer which takes the data from a .CSV file or a Google Spreadsheet and creates one rendered template file for each row of the data. It needs Inkscape for .SVG templates and PDFLateX or XeLateX for LateX templates. Usage ----- The CSV header fields must match the ones in the template file.:: docstamp -i registrations.csv -t my_template.svg -f name -f surname -o out_folder -n talk_certificate --idx 10 Installation ------------ To install the development version:: pip install git+https://www.github.com/PythonSanSebastian/docstamp.git To install the latest release:: pip install docstamp Requirements ------------ .. include:: ./requirements.txt :literal: gspread Pillow jinja2 Compatibility ------------- DocStamp is compatible with Python 2 and 3. I could not test it on Windows. Licence ------- New BSD license Authors ------- The author of `docstamp` is `Alexandre M. Savio @alexsavio`_. Contributors: Oier Etxaniz @oechaniz Luis Javier Salvatierra @ljsalvatierra PK@KHt&docstamp-0.3.0.dist-info/metadata.json{"classifiers": ["Development Status :: 4 - Beta", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4"], "extensions": {"python.details": {"contacts": [{"email": "alexsavio@gmail.com", "name": "Alexandre M. Savio", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/PythonSanSebastian/docstamp.git"}}}, "extras": ["testing"], "generator": "bdist_wheel (0.28.0)", "license": "new BSD", "metadata_version": "2.0", "name": "docstamp", "platform": "Linux/MacOSX", "run_requires": [{"requires": ["Pillow", "jinja2"]}, {"extra": "testing", "requires": ["pytest-cov", "pytest"]}], "summary": "A SVG and LateX template renderer from table data based on Inkscape and Jinja2.", "test_requires": [{"requires": ["pytest"]}], "version": "0.3.0"}PK@KHA &docstamp-0.3.0.dist-info/top_level.txtdocstamp PK@KH>nndocstamp-0.3.0.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.28.0) Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any PK@KHM!docstamp-0.3.0.dist-info/METADATAMetadata-Version: 2.0 Name: docstamp Version: 0.3.0 Summary: A SVG and LateX template renderer from table data based on Inkscape and Jinja2. Home-page: https://github.com/PythonSanSebastian/docstamp.git Author: Alexandre M. Savio Author-email: alexsavio@gmail.com License: new BSD Platform: Linux/MacOSX Classifier: Development Status :: 4 - Beta Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Requires-Dist: Pillow Requires-Dist: jinja2 Provides-Extra: testing Requires-Dist: pytest; extra == 'testing' Requires-Dist: pytest-cov; extra == 'testing' docstamp ====== .. image:: https://pypip.in/v/docstamp/badge.png :target: https://pypi.python.org/pypi/docstamp :alt: Latest PyPI version .. image:: ''.png :target: '' :alt: Latest Travis CI build status Initially it was a conference badge creator based on SVG templates (https://github.com/PythonSanSebastian/pydger), but we thought it could be more generic and have many other applications. DocStamp is a generic template renderer which takes the data from a .CSV file or a Google Spreadsheet and creates one rendered template file for each row of the data. It needs Inkscape for .SVG templates and PDFLateX or XeLateX for LateX templates. Usage ----- The CSV header fields must match the ones in the template file.:: docstamp -i registrations.csv -t my_template.svg -f name -f surname -o out_folder -n talk_certificate --idx 10 Installation ------------ To install the development version:: pip install git+https://www.github.com/PythonSanSebastian/docstamp.git To install the latest release:: pip install docstamp Requirements ------------ .. include:: ./requirements.txt :literal: gspread Pillow jinja2 Compatibility ------------- DocStamp is compatible with Python 2 and 3. I could not test it on Windows. Licence ------- New BSD license Authors ------- The author of `docstamp` is `Alexandre M. Savio @alexsavio`_. Contributors: Oier Etxaniz @oechaniz Luis Javier Salvatierra @ljsalvatierra PK@KHy++docstamp-0.3.0.dist-info/RECORDdocstamp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 docstamp/collections.py,sha256=l0brgDKS-bcb1aE1f1ah37Igp0xu8XcqT3d1qYtWCFc,1438 docstamp/commands.py,sha256=Nbj3uyiAzlSSRoM88zkXAXdgGVu7iT4Gdh93qCVduE0,2753 docstamp/config.py,sha256=w4-dzv-8sheQtdosbP1KUDk1Q2ceNxqk1LojMEVT5R8,4158 docstamp/data_source.py,sha256=QXSz7qOWMlOfrjd-rmLaN7eSOUymfPqkSS8IBVv0n58,966 docstamp/file_utils.py,sha256=jUn92DXfuIXS-9pt71q4YiR9rgISdcdAT0MLwP5n9Q8,4778 docstamp/gdrive.py,sha256=y925pL50FN3e_iTSGbgyUw0oz_eaMz7PphyPIwy3zfY,1977 docstamp/inkscape.py,sha256=T9ld_98yDtZ533bxTda7ulDAeNETbh_ru8gyRmWvw-E,3104 docstamp/model.py,sha256=3gMZ80TOguY8j3UFHzyjblPs-OC2exmt9iYMKC1baGI,2215 docstamp/pdf_utils.py,sha256=TRvanYQf8YGA4NZZen2PPcBS2TERbc3yNvTGFNa4Bc0,623 docstamp/pdflatex.py,sha256=DRj95dIT54K04djWXcUmsf5ZHemtx3lOekavK__2UXU,4130 docstamp/qrcode.py,sha256=wFvjQwjhFtUHn8WVfCHSUZMvAy5nWJYdAW6urM5Vl9I,1692 docstamp/svg_utils.py,sha256=-GZsmL8XC1Av0j6-ROJyEpNp69zxTMrkkY90JspZS4g,2377 docstamp/template.py,sha256=kZ_RT10YCrkw0bwwCUOMwRCkQo6mgxx7HlAjHOwrHl0,7313 docstamp/unicode_csv.py,sha256=bo-jViSjUQqgm47xUeO0EBT8t1ZSv1ezj5t3NZBsTUE,1982 docstamp/vcard.py,sha256=L6EkdumGHqqtTIzR1H5ifZI69JUH0HtXO03GfpMkDQQ,1748 docstamp/version.py,sha256=JFHQhjtiZP4LgEv9DGKah_tOB6US83pC-XgtfqsOW9Q,119 docstamp/xml_utils.py,sha256=eSLSXqY2v6ANGkQCWKGldCZu9_jdlQCWzYJyYynvhBw,1328 docstamp-0.3.0.data/scripts/docstamp,sha256=42NZhTwzeReAhHVnXbu6ggoMIP9pX4Sv1h4nZnU4X2E,8401 docstamp-0.3.0.data/scripts/svg_export.py,sha256=nka_JY9_fDddTh9F39zQspgDWn-uKGOD3_FznEggw4A,1365 docstamp-0.3.0.dist-info/DESCRIPTION.rst,sha256=LIpwllaP3sj6W7B6uHSQmbMM2iNsSKz0vvp02pcOfuU,1456 docstamp-0.3.0.dist-info/METADATA,sha256=fFTWiOz9b-ywUOKp27rBCbZC-p8USnpXn7RYq_zqsSQ,2249 docstamp-0.3.0.dist-info/RECORD,, docstamp-0.3.0.dist-info/WHEEL,sha256=c5du820PMLPXFYzXDp0SSjIjJ-7MmVRpJa1kKfTaqlc,110 docstamp-0.3.0.dist-info/metadata.json,sha256=lx4v3ee4mOA6NK2_fizHeT1FKZug42mQIEm7Fpf1TuU,982 docstamp-0.3.0.dist-info/top_level.txt,sha256=qBJR0MszDR1W23K2duhIGs1zVQvVjaBUAGe6JYDXHsU,9 PK FlFdocstamp/__init__.pyPK FlF.2docstamp/collections.pyPK{r1H docstamp/commands.pyPK4FG`Ý>>docstamp/config.pyPK*oFuf!docstamp/data_source.pyPKKH& a%docstamp/file_utils.pyPKyr1H?8docstamp/gdrive.pyPK{r1H (@docstamp/inkscape.pyPKϩoFvzLdocstamp/model.pyPKKH ooPUdocstamp/pdf_utils.pyPKƜ3H'6""Wdocstamp/pdflatex.pyPKYEH嫬Fhdocstamp/qrcode.pyPK1GHfBI I odocstamp/svg_utils.pyPK{r1HMxdocstamp/template.pyPKyr1H}5,Qdocstamp/unicode_csv.pyPKKHJDdocstamp/vcard.pyPK*KHG wwGdocstamp/version.pyPKGov00docstamp/xml_utils.pyPK=KH]I` $Rdocstamp-0.3.0.data/scripts/docstampPK=KH9%KUU)edocstamp-0.3.0.data/scripts/svg_export.pyPK@KHMl(docstamp-0.3.0.dist-info/DESCRIPTION.rstPK@KHt&docstamp-0.3.0.dist-info/metadata.jsonPK@KHA &docstamp-0.3.0.dist-info/top_level.txtPK@KH>nn^docstamp-0.3.0.dist-info/WHEELPK@KHM!docstamp-0.3.0.dist-info/METADATAPK@KHy++docstamp-0.3.0.dist-info/RECORDPK4x