v1.0.2 (2013-09-12)

  * setup.py, pyppd/runner.py, pyppd/pyppd-ppdfile.in: Changing version to
  1.0.2

  * MANIFEST.in: Without it, the ISSUES aren't packed in the distribution file,
  and setup.py breaks as it needs it to generate the long_description.

  * contrib/git2changes.py: Fix git2changes when commits only modified one file

v1.0.1 (2013-09-05)

  * setup.py, pyppd/runner.py, pyppd/pyppd-ppdfile.in: Changing version to
  1.0.1

  * pyppd/ppd.py: - if a device ID has no MDL/MODEL field, it is not taken into
  account as an extra model - If in a device ID a DRV: field is found (Foomatic
  info) this is added to the device IDs of all model entries. - Model names get
  normalized before comparing: all lowercase, leading and trailing white space
  stripped, manufacturer name in the beginning removed, then only really
  different models get an extra entry. - If a PPD contains one or more
  "*1284DeviceID:" lines and only one "*Product:" This removes tons of bogus
  lines, especially in the PPD from Foomatic )both generated from XML and
  ready-made PPDs from printer manufacturers) and HPLIP.1

v1.0.0 (2012-06-15)

  * setup.py, pyppd/runner.py: pyppd is being used for a couple years now, so
  it is stable. And, with Martin Pitt's patches (thanks!), it's working for
  Python 3. So I guess it deserves this big version bump :)

  * README: They have to have 4 spaces to be treated as code.

  * setup.py, ISSUES.txt, ISSUES: Renaming ISSUES.txt -> ISSUES.

  * setup.py, README.txt, README.md, README: GitHub only parses as Markdown if
  it's README.md, but PyPI complains if it's neither README nor README.txt. To
  make everyone happy, I symlinked.

  * setup.py: Changing repository URL to github.

  * README.txt: Updating README.txt about the Python 3.x support.

  * setup.py: Changing pypi status to Production/Stable.

  * setup.py, LICENSE.txt: This might make pyppd more useful for the *BSD guys.

  * setup.py: setup.py build crashed with UnicodeDecodeError, as open()
  defaults to the system locale, and README.txt contains non-ASCII characters.

  * pyppd/runner.py, pyppd/pyppd-ppdfile.in, pyppd/ppd.py, pyppd/compressor.py,
  pyppd/archiver.py: Use bytes for reading and writing the archive and PPD
  files.  Do not try to decode/encode whole PPDs. Many of them are not in
  UTF-8, and not even in the encoding they claim to be in, so it's best to not
  assume anything about their encoding at all and just keep them as bytes. Only
  decode the minimal pieces This makes pyppd work with Python 3 and is also
  more robust against encoding Avoid calling str() on a PPD object, as that
  will cause UTF-8 encoding again on an unicode object. Just call __str__()
  directly. This is a bit ugly, but works with both Python 2 and 3.

  * setup.py, pyppd/pyppd-ppdfile.in, pyppd/ppd.py, pyppd/archiver.py: Fix
  syntax to be Python 3 compatible.

  * pyppd/runner.py, pyppd/pyppd-ppdfile.in, pyppd/archiver.py: Fix import
  statements to be Python 3 compatible.

  * pyppd/pyppd-ppdfile.in: Only exit if the IOError caught was a Broken Pipe.

  * pyppd/pyppd-ppdfile.in, bin/pyppd: 1. pyppd-ppdfile now catches IO errors
  at the actual print commands and not globally for the whole program. So real
  bugs will not be masked by the 2. pyppd catches only KeyboardInterrupt as it
  is writing to an actual disk file and so here the crash on broken pipe
  problem does not exist (a better error handling would still be nice though).

v0.4.9 (2010-09-21)

  * setup.py, pyppd/runner.py, pyppd/pyppd-ppdfile.in: Changing version to
  0.4.9

  * pyppd/pyppd-ppdfile.in, bin/pyppd: KeyboardInterrupts are raised when the
  user uses, for example, CTRL+C. But when you do "./pyppd-ppdfile list | less"
  and, before the end of file, types "q", it raises an IOError. We don't want
  neither throwing a traceback into stdout. Thanks for the tip, Till.

  * bin/pyppd: Oops! Error in the indentation.

v0.4.8 (2010-09-20)

  * setup.py, pyppd/runner.py, pyppd/pyppd-ppdfile.in: Changing version to
  0.4.8

  * pyppd/pyppd-ppdfile.in, bin/pyppd: As noted by Till Kamppeter, generating a
  traceback triggers an automatic bug report feature in some distributions
  (like Ubuntu). We avoid generating false bug reports by suppressing those
  KeyboardInterrupt's tracebacks.

v0.4.7 (2010-09-20)

  * setup.py, pyppd/runner.py, pyppd/pyppd-ppdfile.in: Changing version to
  0.4.7

  * setup.py: Removing useless whitespaces.

  * contrib/git2changes.py: Removes ending whitespace in CHANGES.txt's commit
  messages.

  * pyppd/pyppd-ppdfile.in, pyppd/ppd.py: Make sure that the "pyppd-ppdfile
  list" output does not contain two lines with the same PPD URI

  * ISSUES.txt: Adds missing period.

v0.4.6 (2010-08-17)

  * setup.py, pyppd/runner.py, pyppd/pyppd-ppdfile.in: Changing version to
  0.4.6

  * setup.py: If we don't do this, the resulting string isn't valid rST.

v0.4.5 (2010-08-17)

  * setup.py, pyppd/runner.py, pyppd/pyppd-ppdfile.in: Changing version to
  0.4.5

  * ISSUES.txt: Adds "Generate manpage" issue.

  * setup.py: Letting our issues be known to everyone might encourage
  contributions.

  * ISSUES.txt: Minor esthetic changes.

  * README.txt: Adds Till, Hin-Tak, Flávio, Diógenes and OSPO as
  Contributors.

  * README.txt: Adds # and $ to indicate commands done in the shell.

  * README.txt: Adds multiple archives usage's instructions.

v0.4.4 (2010-08-11)

  * setup.py, pyppd/runner.py, pyppd/pyppd-ppdfile.in: Changing version to
  0.4.4

  * pyppd/pyppd-ppdfile.in, pyppd/ppd.py: 1. The PPD parser confused "NickName"
  and "ShortNickName". This leads to the "ShortNickName" being used in
  "pyppd-ppdfile list" and so important information is missing in the listings
  and the listings are 2. "pyppd-ppdfile list" lists PPD URIs with the full
  path of pyppd-ppdfile, usually /usr/lib/cups/driver/pyppd-ppdfile. This makes
  the PPD generator not working correctly with CUPS.

v0.4.3 (2010-08-10)

  * setup.py, pyppd/runner.py, pyppd/pyppd-ppdfile.in: Changing version to
  0.4.3

  * pyppd/ppd.py, pyppd/archiver.py: Renaming filename -> uri.

  * pyppd/archiver.py: So we avoid the problem of having PPDs with the same
  filename. It's working as: if I call pyppd with the folder /usr/share/ppd, it
  removes this part of each PPD path and uses the rest as the URI. For example,
  /usr/share/ppd/cups-included/textonly.ppd.gz will have the URI
  "cups-included/textonly.ppd".

v0.4.2 (2010-08-09)

  * setup.py, pyppd/runner.py, pyppd/pyppd-ppdfile.in: Changing version to
  0.4.2

  * pyppd/archiver.py: Oops! Forgot to change the variable name.

  * setup.py: Adds classifiers to setup.py.

  * ISSUES.txt: Removing resolved issue.

  * pyppd/runner.py: I don't think logging to a file is worthwile to pyppd, as
  it isn't a daemon. If you encounter some problem, you'll be running it
  yourself, Also, I prefer using -v and -vv instead of -v and -d. I think it
  makes things more standard.

  * pyppd/runner.py, pyppd/ppd.py, pyppd/archiver.py, ISSUES.txt: Now, pyppd
  prints to stdout the WARNING, ERROR and FATAL messages, and logs those and
  every other to a log file (defaults to pyppd.log), which has a size limit of
  2 MB. You can use --verbose and --debug to make pyppd more verbose.

v0.4.1 (2010-08-09)

  * setup.py, pyppd/runner.py, pyppd/pyppd-ppdfile.in: Changing version to
  0.4.1

  * pyppd/archiver.py: When we have two PPDs with the same filename, we create
  add a random number to one of them, so we can add both to our archive. But,
  if this PPD which'll have the new name have more than one entry in the list,
  each entry will have a different random name. This commit fixes this.

  * ISSUES.txt: Adds new issue.

  * ISSUES.txt: "setup.py install" shouldn't link pyppd to
  /usr/lib/cups/driver. What should be there isn't pyppd, but the archive
  generated by it.

v0.4.0 (2010-08-09)

  * setup.py, pyppd/runner.py, pyppd/pyppd-ppdfile.in: Changing version to
  0.4.0

  * pyppd/ppd.py: Now, when parsing a PPD, it adds as many *1284DeviceID lines
  as it can find. Afterwards, it looks for *Product lines. If found, check if
  it's unique (if there wasn't an *1284DeviceID line with the same product
  model), and adds it if so.

  * pyppd/archiver.py, ISSUES.txt: If we found a PPD with the same filename, we
  simply add a random number (between 0 and 99) to the end of its filename.

  * pyppd/pyppd-ppdfile.in: Renaming index -> value.

v0.3.0 (2010-08-07)

  * setup.py, pyppd/runner.py, pyppd/pyppd-ppdfile.in: Changing version to
  0.3.0

  * pyppd/ppd.py, pyppd/archiver.py, ISSUES.txt: There are some of PPDs which
  are in different files but contains the same "*PCFileName:". So, without this
  change, pyppd overwritten them in the index, making them impossible to
  recover. Now, it'll only happen when there're two PPDs with the same
  basename. I'm also writing at ISSUES.txt that we need to figure out how to
  solve this possible problem.

  * pyppd/ppd.py: Fixing typo deviceid -> deviceid_re.

  * pyppd/ppd.py, pyppd/archiver.py: Renaming name -> filename.

  * pyppd/pyppd-ppdfile.in, pyppd/ppd.py, pyppd/archiver.py: As Till Kamppeter
  pointed out, a single PPD might have multiple descriptions, following the
  rules: a) If the PPD has an "*1284DeviceID:" line, create a single list entry
  with this device ID; b) If the PPD has one or more "*Product:" lines, create
  a pseudo device ID using the content of the "*Manufacturer:" line as
  manufacturer and "*Product:" line as model, whichout the parentheses.

  * ISSUES.txt: Removing "support for .ppd.gz" from our ISSUES list.

v0.2.2 (2010-08-07)

  * setup.py, pyppd/runner.py, pyppd/pyppd-ppdfile.in: Changing version to
  0.2.2

  * pyppd/archiver.py, README.txt: Now, instead of looking for only *.ppd in
  the directory passed as parameter, it looks also for *.ppd.gz. When found, it
  tests: does it ends with ".gz" (case-sensitive (I have to change this))? If
  so, opens with gzip. If not, opens as usual. When we archive gzipped PPDs, we
  save them uncompressed.

  * pyppd/archiver.py: Refactoring some variable names.

v0.2.1 (2010-08-07)

  * setup.py, pyppd/runner.py, pyppd/pyppd-ppdfile.in: Changing version to
  0.2.1

  * pyppd/archiver.py: Updating archiver.compress() documentation.

v0.2.0 (2010-08-07)

  * setup.py, pyppd/runner.py, pyppd/pyppd-ppdfile.in: Changing version to
  0.2.0

  * pyppd/archiver.py: Removing useless try/except block.

  * pyppd/pyppd-ppdfile.in, pyppd/archiver.py: Instead of creating a .tar
  archive, we simply concatenate all PPDs found in a big text file, and save
  each's start and length inside of it. Then, when we have to "cat" it, we
  decompress the text file and print only In my tests, it's around 8% faster
  than using .tar. Also, it makes it easier, in the future, to support not only
  files .ppd, but also .ppd.gz.

  * pyppd/runner.py, pyppd/archiver.py: Rename method create_archive -> archive

  * pyppd/archiver.py: Reorganizing methods, the most important at the top

  * pyppd/compressor.py: Updating decompress() documentation

  * pyppd/pyppd-ppdfile.in, pyppd/lzma_proxy.py, pyppd/compressor.py,
  pyppd/archiver.py: Renaming lzma_proxy.py to compressor.py

  * pyppd/runner.py, pyppd/compressor.py, pyppd/archiver.py: The runner.py
  shouldn't have to use compressor.py. It should simply ask archiver.py to
  create the archive. Compression is an implementation detail of archiver.

  * pyppd/runner.py, pyppd/pyppd-ppdfile.in: Fixing version number 0.1.0 ->
  0.1.6

  * pyppd/ppd.py: Removing space between DeviceID's field name and content.

  * setup.py, contrib/git2changes.py: I made git2changes which simply gets the
  commit messages and organizes them into something similar to the GNU
  ChangeLog style. But, instead of separating the commits by day, I organize
  them by version. I think this way, even as it's not standard, generates a
  file that is more useful to pyppd's users.

  * CHANGES.txt, .gitignore: As CHANGES.txt is created from the git log, we
  have no use to maintain it under version control.

v0.1.6 (2010-08-03)

  * setup.py: Changing version to 0.1.6

  * MANIFEST.in: So, pyppd/pyppd-ppdfile.in gets into the distribution archive.

  * pyppd/pyppd-ppdfile.in: As now we don't uncompress the .tar.xz archive at
  ls(), when we tested if ppd.__class__ == tarfile.TarFile, it always failed.
  Now, at the iteration, we get the key AND the value (ppd). If the key is
  ARCHIVE, we continue.

  * ISSUES.txt: Adding Till's suggestions and some ideas to ISSUES.txt

  * setup.py: Making setup.py executable

v0.1.5 (2010-08-01)

  * setup.py: Adding a description and bumping version to 0.1.4

v0.1.4 (2010-08-01)

  * setup.py: Changing version to 0.1.4

  * README.txt: Adding instructions to install with pip

v0.1.3 (2010-08-01)

  * setup.py: Changing version to 0.1.3

  * pyppd/compressor.py: Instead of silently ignoring exceptions, raises them

  * README.txt: Minor reStructuredText fixes

v0.1.1 (2010-08-01)

  * setup.py: Renaming the license in setup.py to GPLv3

  * setup.py: Forgot to add read() to the long_description

  * setup.py, README.txt: Adding a README.txt and setting it as
  long_description

  * pyppd/pyppd-ppdfile.in: When you tried ./pyppd-ppdfile cat my-ppd.ppd, it
  raised a IndexError, as it tried to "my-ppd.ppd".split(":")[1], which don't
  exists. Now, instead of trying to read the second element ([1]), we read the
  last element, which works in both cases.

  * setup.py, pyppd/test/__init__.py: Remove pyppd/test, as I am not using
  tests (yet)

  * ISSUES.txt: As Gitorious don't offers us one. Maybe we should consider
  switching to github or something...

  * pyppd/pyppd-ppdfile.in: As the file lzma_proxy.py is inserted into
  pyppd-compressor, we don't use "import lzma_proxy as lzma". So, the methods
  are in the local namespace, thus we use compress/decompress, and not
  lzma.compress/decompress.

  * pyppd/lzma_proxy.py: Now, I need to change it's name. Lzma_proxy seems
  unnatural to me, and we already use compressor for another file. Maybe xz.py?

  * pyppd/pyppd-ppdfile.in: We used to decompress the PPDs in load(). So, even
  if the user simply wanted to list the PPDs (which we use only the index), he
  had to decompress everything. Now, only when the user asks to cat a file, we
  decompress the PPDs.

  * pyppd/pyppd-ppdfile.in, pyppd/compressor.py: This way, we compress 212mb to
  3.2mb, instead of 8,2mb. This makes the program a bit slower (0.2s in my
  tests), but I think it's worthy.

  * pyppd/runner.py, pyppd/pyppd-ppdfile.in: Adding the \ to concatenate the
  multiline strings

  * pyppd/pyppd-ppdfile.in: Checks if pyppd-ppdfile is run directly and, if so,
  run main()

  * pyppd/pyppd-ppdfile.in: The name 'list()' is used for instantiate list
  objects.

  * pyppd/ppd.py: Ppd: Add object inheritance

  * pyppd/runner.py, pyppd/pyppd-ppdfile.in: Fitting to 80 columns

  * pyppd/archiver.py: Archiver: Removes unused import

  * pyppd/compressor.py: When we add the PPDs to the .tar file, /usr/share/ppd
  becomes usr/share/ppd. This way, we were unable to find it afterwards. Now,
  we convert every path passed as argument to absolute, and simply ignore the
  first slash when creating the index.

  * pyppd/ppd.py: Removes the "(" and ")" from the Product line

  * pyppd/ppd.py: Use Product instead of NickName in the 1284DeviceID fallback

  * pyppd/pyppd-ppdfile.in, pyppd/compressor.py: It's better than the sqlite3
  solution because we can decompress and load the tar file from memory, without
  the need to write to decompress, write As tar doesn't have an index, it needs
  to do a sequential search to find the files. It might be better to use 7z
  instead.

  * pyppd/lzma_proxy.py: Adds compress_file(path), which compresses path with
  xz binary

  * pyppd/compressor.py: This way, sqlite3 indexes its values, making the
  selects much faster.

  * pyppd/pyppd-ppdfile.in, pyppd/compressor.py: It's much faster to load the
  sqlite3 DB and begin issuing SQL statements than to load the .sql dump into a
  new sqlite3 DB.

  * pyppd/pyppd-ppdfile.in, pyppd/compressor.py: cPickle uses too much memory
  when pickling/unpickling. And, as we work with large files, it becomes a
  problem. SQLite3 is more efficient in this regard.

  * pyppd/runner.py: Fixing bug of undefined variable when running pyppd with
  unexistent directory as argument

  * pyppd/ppd.py: This is probably wrong [1] but, as I found many PPDs which
  1284DeviceID [1]
  http://www.undocprint.org/formats/communication_protocols/ieee_1284#ieee_1284_device_
   

  * pyppd/compressor.py: Sets ppds = None when we don't need it anymore, trying
  to keep memory usage low.

  * pyppd/ppd.py: Adding languages not found in ISO 639, but found in some PPDs

  * pyppd/ppd.py: Converts PPD's language name to it's ISO 639 code

  * pyppd/ppd.py, pyppd/compressor.py: When compressing, ignore the PPDs which
  couldn't be parsed.

  * pyppd/pyppd-ppdfile.in, pyppd/ppd.py: Instead of having the PPDs names
  hardcoded with the executable's, prefix them at execution time. Also, remove
  the "./" of executable's name, when called like ./pyppd-ppdfile.

  * setup.py, pyppd/runner.py, pyppd/pyppd-ppdfile.in, pyppd/archiver.py: Now,
  everything will be in only one file by default. When running pyppd, it'll
  parse the folder passed as argument and create, by default, pyppd-ppdfile.
  You can run it with list or cat URI to get the compressed PPDs.

  * pyppd/runner.py: Refactoring (extract method parse_args())

  * pyppd/ppd.py: We don't need to save the ppd_file anymore

  * pyppd/compressor.py: So, when loading the resulting pickle file, we don't
  need the PPD class definition. We just load a tuple with two strings.

  * pyppd/runner.py: Adds command-line argument parsing

  * pyppd/ppd.py, pyppd/compressor.py: PPD class now saves its PPD file in an
  attribute

  * pyppd/lzma_proxy.py: communicate() returns a tuple (stdout, stderr).
  Returns stdout.

  * pyppd/compressor.py: Refactoring import lzma_proxy

  * pyppd/ppd.py, pyppd/compressor.py: The PPD class parses a string with the
  ppd, looking for its attributes. Now, instead of saving a dictionary with
  ppd_path => {size: ppd_size, start: ppd_start}, it creates a PPD object for
  each ppd, with it's attributes, so we can in the future list them as foomatic
  does.

  * pyppd/lzma_proxy.py, pyppd/compressor.py: This way, we can fallback to
  using the xz binary, if there isn't python-lzma available. It also has the
  advantage of using the fastest, if both are available. I need to do some
  tests to check which is better.

  * pyppd/compressor.py: Adding compressor.compress() docstring.

  * pyppd/compressor.py: Forgot the final period.

  * pyppd/compressor.py: Removing garbage

  * pyppd/compressor.py: Minor typo fix

v0.1.0 (2010-06-12)

  * setup.py, pyppd/test/__init__.py, pyppd/runner.py, pyppd/compressor.py,
  pyppd/__init__.py, bin/pyppd, MANIFEST.in, LICENSE.txt, CHANGES.txt,
  .gitignore: Initial release.

