From 2989c874562b7920b3da60993666716c2d6b0c4e Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Mon, 13 Jun 2011 19:56:03 +0100 Subject: [PATCH] Added '0install digest' command This is similar to "0store manifest", except that it doesn't print the manifest itself, just the final digest. It can also be used directly on an archvie. --- 0install.1 | 19 ++++++++++++++++ tests/testinstall.py | 24 +++++++++++++++++++- zeroinstall/cmd/__init__.py | 3 ++- zeroinstall/cmd/digest.py | 55 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 zeroinstall/cmd/digest.py diff --git a/0install.1 b/0install.1 index 97dec86..29fa1ae 100644 --- a/0install.1 +++ b/0install.1 @@ -28,6 +28,8 @@ .B 0install list-feeds \fBURI\fP +.B 0install digest \fBDIRECTORY\fP | \fBARCHIVE\fP [\fBEXTRACT\fP] + .SH DESCRIPTION .PP Zero Install is a decentralised cross-distribution software installation @@ -302,6 +304,23 @@ With no arguments, `0install config' displays all configuration settings. With one argument, it displays the current value of the named setting. With two arguments, it sets the setting to the given value. +.SS 0install digest DIRECTORY | ARCHIVE [EXTRACT] + +.PP +Calculate the secure hash of an implementation. This is a unique "fingerprint" of +a directory and all the files and subdirectories it contains. When publishing a +program using 0install, this value must be placed in the XML file. + +.TP +\fB\-m\fP, \fB\-\-algorithm=HASH\fP +Select the secure hash function to be used. Supported values are "sha1new" (the +default) or "sha256". + +.PP +If an archive is given then the hash is for the directory that would be created if +the archive were unpacked (or the EXTRACT subdirectory of it, if one is specified). +See also: 0store(1)'s manifest command. + .SS 0install --version This can be used (without any command) the get version of 0install itself: diff --git a/tests/testinstall.py b/tests/testinstall.py index 3ecc024..2b6033d 100755 --- a/tests/testinstall.py +++ b/tests/testinstall.py @@ -1,6 +1,6 @@ #!/usr/bin/env python from basetest import BaseTest, TestStores -import sys +import sys, os, tempfile from StringIO import StringIO import unittest @@ -8,6 +8,8 @@ sys.path.insert(0, '..') from zeroinstall import cmd from zeroinstall.injector import model, selections, qdom, reader, policy, handler, gpg +mydir = os.path.dirname(__file__) + class Reply: def __init__(self, reply): self.reply = reply @@ -328,5 +330,25 @@ class TestInstall(BaseTest): assert 'arg-for-runner' in out, out assert '--help' in out, out + def testDigest(self): + hw = os.path.join(mydir, 'HelloWorld.tgz') + out, err = self.run_0install(['digest', '--algorithm=sha1', hw]) + assert out == 'sha1=3ce644dc725f1d21cfcf02562c76f375944b266a\n', out + assert not err, err + + out, err = self.run_0install(['digest', hw]) + assert out == 'sha1new=290eb133e146635fe37713fd58174324a16d595f\n', out + assert not err, err + + out, err = self.run_0install(['digest', hw, 'HelloWorld']) + assert out == 'sha1new=491678c37f77fadafbaae66b13d48d237773a68f\n', out + assert not err, err + + tmp = tempfile.mkdtemp(prefix = '0install') + out, err = self.run_0install(['digest', tmp]) + assert out == 'sha1new=da39a3ee5e6b4b0d3255bfef95601890afd80709\n', out + assert not err, err + os.rmdir(tmp) + if __name__ == '__main__': unittest.main() diff --git a/zeroinstall/cmd/__init__.py b/zeroinstall/cmd/__init__.py index d4d4841..91cf00f 100644 --- a/zeroinstall/cmd/__init__.py +++ b/zeroinstall/cmd/__init__.py @@ -13,7 +13,8 @@ import logging from zeroinstall import SafeException valid_commands = ['select', 'download', 'run', 'update', - 'config', 'import', 'list', 'add-feed', 'remove-feed', 'list-feeds'] + 'config', 'import', 'list', 'add-feed', 'remove-feed', 'list-feeds', + 'digest'] class UsageError(Exception): pass diff --git a/zeroinstall/cmd/digest.py b/zeroinstall/cmd/digest.py new file mode 100644 index 0000000..22fe057 --- /dev/null +++ b/zeroinstall/cmd/digest.py @@ -0,0 +1,55 @@ +""" +The B{0install digest} command-line interface. +""" + +# Copyright (C) 2011, Thomas Leonard +# See the README file for details, or visit http://0install.net. + +import os, tempfile + +from zeroinstall import SafeException, _ +from zeroinstall.zerostore import manifest, unpack +from zeroinstall.cmd import UsageError +from zeroinstall import support + +syntax = "DIRECTORY | ARCHIVE [EXTRACT]" + +def add_options(parser): + parser.add_option("", "--algorithm", help=_("the hash function to use"), metavar="HASH") + +def handle(config, options, args): + if len(args) == 1: + extract = None + elif len(args) == 2: + extract = args[1] + else: + raise UsageError() + + source = args[0] + alg = manifest.algorithms.get(options.algorithm or 'sha1new', None) + if alg is None: + raise SafeException(_('Unknown algorithm "%s"') % alg) + + def do_manifest(d): + if extract is not None: + d = os.path.join(d, extract) + digest = alg.new_digest() + for line in alg.generate_manifest(d): + digest.update(line + '\n') + print alg.getID(digest) + + if os.path.isdir(source): + if extract is not None: + raise SafeException("Can't use extract with a directory") + do_manifest(source) + else: + data = None + tmpdir = tempfile.mkdtemp() + try: + data = open(args[0], 'rb') + unpack.unpack_archive(source, data, tmpdir, extract) + do_manifest(tmpdir) + finally: + support.ro_rmtree(tmpdir) + if data: + data.close() -- 2.11.4.GIT