Source code for filetype

#
##
##  SPDX-FileCopyrightText: © 2007-2024 Benedict Verhegghe <bverheg@gmail.com>
##  SPDX-License-Identifier: GPL-3.0-or-later
##
##  This file is part of pyFormex 3.5  (Thu Feb  8 19:11:13 CET 2024)
##  pyFormex is a tool for generating, manipulating and transforming 3D
##  geometrical models by sequences of mathematical operations.
##  Home page: https://pyformex.org
##  Project page: https://savannah.nongnu.org/projects/pyformex/
##  Development: https://gitlab.com/bverheg/pyformex
##  Distributed under the GNU General Public License version 3 or later.
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation, either version 3 of the License, or
##  (at your option) any later version.
##
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this program.  If not, see http://www.gnu.org/licenses/.
##
"""Common file type specifications

This module defines the :class:`Filetype` class, which holds the basic
information about one of the common file types used by pyFormex. The user
can define more file types if needed. All file types are collected in
a single dict :attr:`Filetypes` using a simple mnemonic string as key.
Furthermore this module defines some functions to help in the use of
file types, e.g. in dialogs.
"""
import re

from pyformex.path import Path
from pyformex.metaclass import RegistryMeta


[docs]class Filetype(metaclass=RegistryMeta): """A class for holding file types and the related filename patterns. Parameters ---------- key: str A short and unique mnemonic string, by preference lower case, that wil be used as lookup key for this Filetype definition in the global :attr:`Filetypes` collection. text: str A textual description of the file type. *suffixes: sequence of str All remaining parameters are file suffixes that should be used to filter files that are supposed to be of this type. Any number of suffixes is allowed. If None are provided, all files will match the file type. Examples -------- >>> for key in Filetype.dict: print(f"{key} = '{Filetype[key]}'") all = 'All files (*)' ccx = 'CalCuliX files (*.dat *.inp)' dcm = 'DICOM images (*.dcm)' dxf = 'AutoCAD DXF files (*.dxf)' dxftext = 'Converted AutoCAD files (*.dxftext)' flavia = 'flavia results (*.flavia.msh *.flavia.res)' gts = 'GTS files (*.gts)' gz = 'Compressed files (*.gz *.bz2)' html = 'Web pages (*.html)' icon = 'Icons (*.xpm)' img = 'Images (*.png *.jpg *.jpeg *.eps *.gif *.bmp)' inp = 'Abaqus or CalCuliX input files (*.inp)' neu = 'Gambit Neutral files (*.neu)' obj = 'Wavefront OBJ files (*.obj)' off = 'Geomview object files (*.off)' pgf = 'pyFormex geometry files (*.pgf)' ply = 'Stanford Polygon File Format files (*.ply)' png = 'PNG images (*.png)' poly = 'Polygon files (*.off *.obj *.ply)' postproc = 'Postproc scripts (*.post.py)' project = 'pyFormex Projects (*.pzf *.hdf5 *.pyf)' pyformex = 'pyFormex scripts (*.py)' pyf = 'pyFormex projects (*.pyf)' python = 'Python files (*.py)' pzf = 'pyFormex zip files (*.pzf)' smesh = 'Tetgen surface mesh files (*.smesh)' stl = 'STL files (*.stl)' stlb = 'Binary STL files (*.stl)' surface = 'Surface models (*.gts *.stl *.off *.obj *.ply)' tetsurf = 'Tetgen surface (*.smesh)' tetgen = 'Tetgen files (*.poly *.smesh *.ele *.face *.edge *.node *.neigh)' text = 'Text files (*.txt *.out *.rst *.md *.py *.html)' video = 'Video (*.mp4)' vtk = 'VTK types (*.vtk *.vtp)' vtp = 'vtkPolyData file (*.vtp)' geometry = 'Geometry files (*.gts *.inp *.neu *.obj *.off *.pgf *.ply\ *.pzf *.stl *.vtk *.vtp)' """ def __init__(self, key, text, *suffixes): self.text = text self.suff = suffixes # @classmethod # def fromdesc(clas, desc): @classmethod def combine_suffixes(clas, *args): suff = set() for arg in args: if arg in clas.dict: suff |= set(clas[arg].suffixes()) else: suff |= {arg} return sorted(suff)
[docs] def suffixes(self, compr=False): """Return a list of file suffixes for the Filetype. Parameters ---------- compr: bool If True, the file suffixes for compressed files of this type are automatically added. Examples -------- >>> Filetype['pgf'].suffixes() ['pgf'] >>> Filetype['pgf'].suffixes(compr=True) ['pgf', 'pgf.gz', 'pgf.bz2'] >>> Filetype['all'].suffixes() [] """ suff = list(self.suff) if compr: compr_types = Filetype['gz'].suff suff.extend([f"{s}.{c}" for s in suff for c in compr_types]) return suff
[docs] def patterns(self, compr=False): """Return a list with the file patterns matching the Filetype. Parameters ---------- compr: bool If True, the file suffixes for compressed files of this type are automatically added. Examples -------- >>> Filetype['pgf'].patterns() ['*.pgf'] >>> Filetype['pgf'].patterns(compr=True) ['*.pgf', '*.pgf.gz', '*.pgf.bz2'] >>> Filetype['all'].patterns() ['*'] """ if self.suff: return [f"*.{s}" for s in self.suffixes(compr)] else: return ['*']
[docs] def desc(self, compr=False): """Create a filetype descriptor compatible with Qt Widgets. Parameters ---------- compr: bool If True, the file patterns for compressed files are automatically added. Returns ------- str A string that can be directly used in the Qt File Dialog widgets to filter the selectable files. This string has the format ``file type text (*.ext1 *.ext2)``. Examples -------- >>> Filetype['img'].desc() 'Images (*.png *.jpg *.jpeg *.eps *.gif *.bmp)' >>> fileDescriptor('inp') 'Abaqus or CalCuliX input files (*.inp)' >>> fileDescriptor('doc') 'DOC files (*.doc)' >>> fileDescriptor('*.inp') '*.inp' >>> fileDescriptor('pgf',compr=True) 'pyFormex geometry files (*.pgf *.pgf.gz *.pgf.bz2)' """ return f"{self.text} ({' '.join(self.patterns(compr))})"
__str__ = desc
# The builtin file types. Only types that are relevant for pyFormex # should be added here. Users can of course add their own types. Filetype('all', 'All files') Filetype('ccx', 'CalCuliX files', 'dat', 'inp') Filetype('dcm', 'DICOM images', 'dcm') Filetype('dxf', 'AutoCAD DXF files', 'dxf') Filetype('dxftext', 'Converted AutoCAD files', 'dxftext') Filetype('flavia', 'flavia results', 'flavia.msh', 'flavia.res') Filetype('gts', 'GTS files', 'gts') Filetype('gz', 'Compressed files', 'gz', 'bz2') Filetype('html', 'Web pages', 'html') Filetype('icon', 'Icons', 'xpm') Filetype('img', 'Images', 'png', 'jpg', 'jpeg', 'eps', 'gif', 'bmp') Filetype('inp', 'Abaqus or CalCuliX input files', 'inp') Filetype('neu', 'Gambit Neutral files', 'neu') Filetype('obj', 'Wavefront OBJ files', 'obj') Filetype('off', 'Geomview object files', 'off') Filetype('pgf', 'pyFormex geometry files', 'pgf') Filetype('ply', 'Stanford Polygon File Format files', 'ply') Filetype('png', 'PNG images', 'png') Filetype('poly', 'Polygon files', 'off', 'obj', 'ply') Filetype('postproc', 'Postproc scripts', 'post.py') Filetype('project', 'pyFormex Projects', 'pzf', 'hdf5', 'pyf') Filetype('pyformex', 'pyFormex scripts', 'py') Filetype('pyf', 'pyFormex projects', 'pyf') Filetype('python', 'Python files', 'py') Filetype('pzf', 'pyFormex zip files', 'pzf') Filetype('smesh', 'Tetgen surface mesh files', 'smesh') Filetype('stl', 'STL files', 'stl') Filetype('stlb', 'Binary STL files', 'stl') # Use only for output Filetype('surface', 'Surface models', 'gts', 'stl', 'off', 'obj', 'ply') Filetype('tetsurf', 'Tetgen surface', 'smesh') Filetype('tetgen', 'Tetgen files', 'poly', 'smesh', 'ele', 'face', 'edge', 'node', 'neigh') Filetype('text', 'Text files', 'txt', 'out', 'rst', 'md', 'py', 'html') Filetype('video', 'Video', 'mp4') Filetype('vtk', 'VTK types', 'vtk', 'vtp') Filetype('vtp', 'vtkPolyData file', 'vtp') Filetype('geometry', 'Geometry files', *Filetype.combine_suffixes( 'pzf', 'pgf', 'surface', 'poly', 'vtk', 'inp', 'neu'))
[docs]def fileDescriptor(ftype, compr=False): """Return a description of the specified file type(s). The description is in a format understood by the Qt QFileDialog widget. Parameters ---------- ftype: str or list of str The file type (or types) for which a description is requested. The case of the string(s) is ignored: it is converted to lower case. Returns ------- str or list of str The file description(s) corresponding with the specified file type(s). The return value(s) depend(s) on the value of the input string(s) in the the following way (see Examples below): - if it is a key in the :attr:`file_description` dict, the corresponding value is returned; - if it is a string of only alphanumerical characters: it is interpreted as a file extension and the corresponding return value is ``FTYPE files (*.ftype)``; - any other string is returned as as: this allows the user to compose his filters himself. Examples -------- >>> fileDescriptor('img') 'Images (*.png *.jpg *.jpeg *.eps *.gif *.bmp)' >>> fileDescriptor(['stl','all']) ['STL files (*.stl)', 'All files (*)'] >>> fileDescriptor('inp') 'Abaqus or CalCuliX input files (*.inp)' >>> fileDescriptor('doc') 'DOC files (*.doc)' >>> fileDescriptor('Video (*.mp4 *.ogv)') 'Video (*.mp4 *.ogv)' >>> fileDescriptor('pgf',compr=True) 'pyFormex geometry files (*.pgf *.pgf.gz *.pgf.bz2)' """ if isinstance(ftype, list): return [fileDescriptor(f, compr) for f in ftype] ltype = ftype.lower() if ltype in Filetype.dict: ret = Filetype[ltype].desc(compr) elif ftype.isalnum(): ret = f"{ftype.upper()} files (*.{ftype})" else: ret = ftype return ret
[docs]def fileTypesFromFilter(fdesc): """Extract the filetypes from a file type descriptor. A file type descriptor is a string consisting of an initial part followed by a second part enclosed in parentheses. The second part is a space separated list of glob patterns. An example file descriptor is ``file type text (\\*.ext1 \\*.ext2)``. This is the format as returned by :meth:`Filetype.desc`. Parameters ---------- fdesc: str A file descriptor string. compr: bool,optional If True, the compressed file types are automatically added. Returns ------- desc: str The file type description text. ext: list of str A list of the matching extensions (without dot) for this type. An empty string means that any extension is accepted. Examples -------- >>> fileTypesFromFilter(Filetype['img'].desc()) ['png', 'jpg', 'jpeg', 'eps', 'gif', 'bmp'] >>> fileTypesFromFilter(Filetype['pgf'].desc(compr=True)) ['pgf', 'pgf.gz', 'pgf.bz2'] >>> fileTypesFromFilter('* *.png') ['', 'png'] >>> fileTypesFromFilter('Images (*.png *.jpg *.jpeg)') ['png', 'jpg', 'jpeg'] """ m = re.compile(r'.*\((.*)\).*').match(fdesc) ext = m.groups()[0] if m else fdesc return [e.lstrip('*').lstrip('.') for e in ext.split(' ')]
[docs]def setFiletypeFromFilter(filename, fdesc): """Make sure a filename has an acceptable suffix. Parameters ---------- filename: :class:`~path.Path` The filename to check and set the suffix. fdesc: str A file descriptor string. Returns ------- Path If `filename` had a suffix included in `accept`, returns the input filename unaltered. Else returns the filename with a dot and the first suffix from `accepted` appeded to it. Examples -------- >>> setFiletypeFromFilter('image01.jpg', 'Images (*.png *.jpg *.jpeg)') Path('image01.jpg') >>> setFiletypeFromFilter('image01', 'Images (*.png *.jpg *.jpeg)') Path('image01.png') """ filename = Path(filename) okext = fileTypesFromFilter(fdesc) if not ('' in okext or filename.filetype() in okext): filename = filename.with_suffix('.'+okext[0]) return filename
### End