root / pkpgcounter / trunk / pkpgpdls / analyzer.py @ 524

Revision 524, 10.8 kB (checked in by jerome, 16 years ago)

Added helper function to later detect the presence of executable dependencies at runtime.

  • Property svn:keywords set to Auth Date Id Rev
Line 
1#
2# pkpgcounter : a generic Page Description Language parser
3#
4# (c) 2003, 2004, 2005, 2006, 2007 Jerome Alet <alet@librelogiciel.com>
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17#
18# $Id$
19#
20
21"""This is the main module of pkpgcounter.
22
23It defines the PDLAnalyzer class, which provides a generic way to parse
24input files, by automatically detecting the best parser to use."""
25
26import sys
27import os
28import tempfile
29
30import version, pdlparser, postscript, pdf, pcl345, pclxl, hbp, pil, mscrap, \
31       lidil, escp2, dvi, tiff, ooo, zjstream, qpdl, spl1, escpages03, plain
32import inkcoverage
33
34class AnalyzerOptions :
35    """A class for use as the options parameter to PDLAnalyzer's constructor."""
36    def __init__(self, debug=None,
37                       colorspace=None,
38                       resolution=None) :
39        """Sets initial attributes."""
40        self.debug = debug
41        self.colorspace = colorspace
42        self.resolution = resolution
43   
44   
45class PDLAnalyzer :   
46    """Class for PDL autodetection."""
47    def __init__(self, filename, options=AnalyzerOptions()) :
48        """Initializes the PDL analyzer.
49       
50           filename is the name of the file or '-' for stdin.
51           filename can also be a file-like object which
52           supports read() and seek().
53        """
54        self.options = options
55        self.filename = filename
56        self.workfile = None 
57        self.mustclose = None
58       
59    def findExecutable(self, command) :
60        """Finds an executable in the PATH and returns True if found else False."""
61        for path in os.environ.get("PATH", "").split(":") :
62            fullname = os.path.abspath(os.path.join(os.path.expanduser(path), command))
63            if os.path.isfile(fullname) and os.access(fullname, os.X_OK) :
64                return True
65        return False
66       
67    def getJobSize(self) :   
68        """Returns the job's size."""
69        size = 0
70        self.openFile()
71        try :
72            try :
73                pdlhandler = self.detectPDLHandler()
74                size = pdlhandler.getJobSize()
75            except pdlparser.PDLParserError, msg :   
76                raise pdlparser.PDLParserError, "Unknown file format for %s (%s)" % (self.filename, msg)
77        finally :   
78            self.closeFile()
79        return size
80           
81    def getInkCoverage(self, colorspace=None, resolution=None) :
82        """Extracts the percents of ink coverage from the input file."""
83        result = None
84        cspace = colorspace or self.options.colorspace
85        res = resolution or self.options.resolution
86        if (not cspace) or (not res) :
87            raise ValueError, "Invalid colorspace (%s) or resolution (%s)" % (cspace, res)
88        self.openFile()
89        try :
90            try :
91                pdlhandler = self.detectPDLHandler()
92                dummyfile = tempfile.NamedTemporaryFile(mode="w+b")
93                filename = dummyfile.name
94                try :
95                    pdlhandler.convertToTiffMultiPage24NC(filename, self.options.resolution)
96                    result = inkcoverage.getInkCoverage(filename, cspace)
97                finally :   
98                    dummyfile.close()
99            except pdlparser.PDLParserError, msg :   
100                raise pdlparser.PDLParserError, "Unknown file format for %s (%s)" % (self.filename, msg)
101        finally :
102            self.closeFile()
103        return result
104       
105    def openFile(self) :   
106        """Opens the job's data stream for reading."""
107        self.mustclose = False  # by default we don't want to close the file when finished
108        if hasattr(self.filename, "read") and hasattr(self.filename, "seek") :
109            # filename is in fact a file-like object
110            infile = self.filename
111        elif self.filename == "-" :
112            # we must read from stdin
113            infile = sys.stdin
114        else :   
115            # normal file
116            self.workfile = open(self.filename, "rb")
117            self.mustclose = True
118            return
119           
120        # Use a temporary file, always seekable contrary to standard input.
121        self.workfile = tempfile.NamedTemporaryFile(mode="w+b")
122        self.filename = self.workfile.name
123        while True :
124            data = infile.read(pdlparser.MEGABYTE) 
125            if not data :
126                break
127            self.workfile.write(data)
128        self.workfile.flush()   
129        self.workfile.seek(0)
130           
131    def closeFile(self) :       
132        """Closes the job's data stream if we have to."""
133        if self.mustclose :
134            self.workfile.close()   
135       
136    def readFirstAndLastBlocks(self, inputfile) :
137        """Reads the first and last blocks of data."""
138        # Now read first and last block of the input file
139        # to be able to detect the real file format and the parser to use.
140        firstblock = inputfile.read(pdlparser.FIRSTBLOCKSIZE)
141        try :
142            inputfile.seek(-pdlparser.LASTBLOCKSIZE, 2)
143            lastblock = inputfile.read(pdlparser.LASTBLOCKSIZE)
144        except IOError :   
145            lastblock = ""
146        return (firstblock, lastblock)     
147           
148    def detectPDLHandler(self) :   
149        """Tries to autodetect the document format.
150       
151           Returns the correct PDL handler class or None if format is unknown
152        """   
153        if not os.stat(self.filename).st_size :
154            raise pdlparser.PDLParserError, "input file %s is empty !" % str(self.filename)
155        (firstblock, lastblock) = self.readFirstAndLastBlocks(self.workfile)
156           
157        # IMPORTANT : the order is important below. FIXME.
158        for module in (postscript, \
159                       pclxl, \
160                       pdf, \
161                       qpdl, \
162                       spl1, \
163                       dvi, \
164                       tiff, \
165                       zjstream, \
166                       ooo, \
167                       hbp, \
168                       lidil, \
169                       pcl345, \
170                       escp2, \
171                       escpages03, \
172                       pil, \
173                       mscrap, \
174                       plain) :     # IMPORTANT : don't move this one up !
175            try :               
176                return module.Parser(self, (firstblock, lastblock))
177            except pdlparser.PDLParserError :
178                pass # try next parser
179        raise pdlparser.PDLParserError, "Analysis of first data block failed."
180           
181def main() :   
182    """Entry point for PDL Analyzer."""
183    import optparse
184    from copy import copy
185   
186    def check_cichoice(option, opt, value) :
187        """To add a CaseIgnore Choice option type."""
188        valower = value.lower()
189        if valower in [v.lower() for v in option.cichoices] :
190            return valower
191        else :   
192            choices = ", ".join([repr(o) for o in option.cichoices])
193            raise optparse.OptionValueError(
194                "option %s: invalid choice: %r (choose from %s)"
195                % (opt, value, choices))
196   
197    class MyOption(optparse.Option) :
198        """New Option class, with CaseIgnore Choice type."""
199        TYPES = optparse.Option.TYPES + ("cichoice",)
200        ATTRS = optparse.Option.ATTRS + ["cichoices"]
201        TYPE_CHECKER = copy(optparse.Option.TYPE_CHECKER)
202        TYPE_CHECKER["cichoice"] = check_cichoice
203       
204    parser = optparse.OptionParser(option_class=MyOption, 
205                                   usage="python analyzer.py [options] file1 [file2 ...]")
206    parser.add_option("-v", "--version", 
207                            action="store_true", 
208                            dest="version",
209                            help="Show pkpgcounter's version number and exit.")
210    parser.add_option("-d", "--debug", 
211                            action="store_true", 
212                            dest="debug",
213                            help="Activate debug mode.")
214    parser.add_option("-c", "--colorspace", 
215                            dest="colorspace",
216                            type="cichoice",
217                            cichoices=["bw", "rgb", "cmyk", "cmy", "gc"],
218                            help="Activate the computation of ink usage, and defines the colorspace to use. Supported values are 'BW', 'RGB', 'CMYK', 'CMY', and 'GC'.")
219    parser.add_option("-r", "--resolution", 
220                            type="int", 
221                            default=72, 
222                            dest="resolution",
223                            help="The resolution in DPI to use when checking ink usage. Lower resolution is faster but less accurate. Default is 72 dpi.")
224    (options, arguments) = parser.parse_args()
225    if options.version :
226        print "%s" % version.__version__
227    elif not (72 <= options.resolution <= 1200) :   
228        sys.stderr.write("ERROR: the argument to the --resolution command line option must be between 72 and 1200.\n")
229        sys.stderr.flush()
230    else :
231        if (not arguments) or ((not sys.stdin.isatty()) and ("-" not in arguments)) :
232            arguments.append("-")
233        totalsize = 0   
234        lines = []
235        try :
236            for arg in arguments :
237                try :
238                    parser = PDLAnalyzer(arg, options)
239                    if not options.colorspace :
240                        totalsize += parser.getJobSize()
241                    else :
242                        (cspace, pages) = parser.getInkCoverage()
243                        for page in pages :
244                            lineparts = []
245                            for k in cspace : # NB : this way we preserve the order of the planes
246                                try :
247                                    lineparts.append("%s : %s%%" % (k, ("%f" % page[k]).rjust(10)))
248                                except KeyError :
249                                    pass
250                            lines.append("      ".join(lineparts))     
251                except (IOError, pdlparser.PDLParserError), msg :   
252                    sys.stderr.write("ERROR: %s\n" % msg)
253                    sys.stderr.flush()
254        except KeyboardInterrupt :           
255            sys.stderr.write("WARN: Aborted at user's request.\n")
256            sys.stderr.flush()
257        if not options.colorspace :   
258            print "%i" % totalsize
259        else :   
260            print "\n".join(lines)
261   
262if __name__ == "__main__" :   
263    main()
Note: See TracBrowser for help on using the browser.