From: Lingchao Xin Date: Thu, 28 Nov 2013 08:04:07 +0000 (+0800) Subject: Rename package name to snapdiff X-Git-Tag: submit/devel/20190730.075508~41 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=6b67cb26db6be27df8abbfc747326fec1414e45a;p=services%2Fpython-snapdiff.git Rename package name to snapdiff Change-Id: I7edbeed41482b37d968c583ec04a712fecf0df6c --- diff --git a/MANIFEST.in b/MANIFEST.in index ef63250..410a988 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,2 @@ -include builddiff/templates/*.html +include snapdiff/templates/*.html diff --git a/builddiff/__init__.py b/builddiff/__init__.py deleted file mode 100644 index 1e5a5e7..0000000 --- a/builddiff/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ - -__all__ = ['render', 'repo'] diff --git a/builddiff/render.py b/builddiff/render.py deleted file mode 100644 index 707a806..0000000 --- a/builddiff/render.py +++ /dev/null @@ -1,16 +0,0 @@ -from jinja2 import Environment, FileSystemLoader -import os - -_SETTINGS = { - 'template_path': os.path.join(os.path.dirname(__file__), 'templates'), - } - -def output_html(template_name, **kwargs): - """Render a template file with given kwargs""" - env = Environment( - loader = FileSystemLoader(_SETTINGS['template_path']), - # auto_reload = True, - ) - template = env.get_template(template_name) - return template.render(kwargs) - diff --git a/builddiff/repo.py b/builddiff/repo.py deleted file mode 100644 index 4d20365..0000000 --- a/builddiff/repo.py +++ /dev/null @@ -1,148 +0,0 @@ -from . import utils -from .render import output_html - -import gzip -import json -import os -import tempfile -import urllib2 -import xml.etree.cElementTree as ET - - -class RepoError(Exception): - """Local custom Exception class, handle Repo Errors""" - pass - - -def _get_primary_md(url, workspace, name): - """Get primary.xml.gz file from remote repodata directory""" - tree = ET.ElementTree(file=_download(url + '/repodata/repomd.xml', \ - workspace, name)) - for elem in tree.iter(tag='{http://linux.duke.edu/metadata/repo}data'): - if elem.attrib['type'] == 'primary': - for c in elem: - if c.tag == '{http://linux.duke.edu/metadata/repo}location': - href = c.attrib['href'] - if href: - return _download(url + href, workspace, \ - href.split('/')[-1]) - else: - raise RepoError('Repo primary metadata can\'t be found !') - -def _download(url, workspace, name): - """Download needed xml file to local by given url""" - xs_file = os.path.join(workspace, name) - - try: - rf = urllib2.urlopen(url) - except urllib2.HTTPError: - xs_file = None - else: - with open(xs_file, 'wb') as xs: - xs.write(rf.read()) - - return xs_file - - -class Repo(object): - """Stuff packages' info""" - - def __init__(self, url): - workspace = tempfile.mkdtemp(dir='/var/tmp') - primary_md = _get_primary_md(url, workspace, 'repomd.xml') - self._et = utils.xml2obj(gzip.open(primary_md)) - - @property - def packages(self): - packages_info = {} - for _package in self._et.package: - packages_info[_package.name] = (packages_info.get(_package.name) \ - or []) + [_package] - return packages_info - -def diff_to_JSON(old_url, new_url): - """Output diffs' json format""" - - if not old_url or not new_url: - return - - old, new = Repo(old_url).packages, Repo(new_url).packages - - added, removed, modified, rebuilded = [], [], [], [] - package_names = set(old.keys() + new.keys()) - - def _pair_old_new(): - for name in package_names: - if old.get(name) is None: - for pkg in new[name]: - yield (None, pkg) - elif new.get(name) is None: - for pkg in old[name]: - yield (pkg, None) - else: - for old_pkg in old[name]: - for new_pkg in new[name]: - if old_pkg.version.ver == new_pkg.version.ver: - yield (old_pkg, new_pkg) - old[name].remove(old_pkg) - new[name].remove(new_pkg) - for pair in map(None, old[name], new[name]): - yield pair - for old_pkg, new_pkg in _pair_old_new(): - if old_pkg is None: - added.append(new_pkg) - elif new_pkg is None: - removed.append(old_pkg) - elif old_pkg.version.ver == new_pkg.version.ver and \ - old_pkg.version.vcs == new_pkg.version.vcs: - rebuilded.append((old_pkg, new_pkg)) - else: - modified.append((old_pkg, new_pkg)) - - obj = {'repo': {'old': old_url, 'new': new_url}, - 'diff': { - 'added': [{'oldpkg': None, 'newpkg': {'name': _new.name, \ - 'version': {'epoch': _new.version.epoch, \ - 'rel': _new.version.rel, 'ver': _new.version.rel}, \ - 'vcs': _new.version.vcs}, 'codediff': None} \ - for _new in added], - 'removed': [{'oldpkg': {'name': _old.name, \ - 'version': {'epoch': _old.version.epoch, \ - 'rel': _old.version.rel, 'ver': _old.version.ver}, \ - 'vcs': _old.version.vcs}, 'newpkg': None, \ - 'codediff': None} for _old in added], - 'modified': [{'oldpkg': {'name': _old.name, \ - 'version': {'epoch': _old.version.epoch, \ - 'rel': _old.version.rel, 'ver': _old.version.ver}, \ - 'vcs': _old.version.vcs}, \ - 'newpkg': {'name': _new.name, \ - 'version': {'epoch': _new.version.epoch, \ - 'rel': _new.version.rel, 'ver': _new.version.ver}, - 'vcs': _new.version.vcs}, 'codediff': None} \ - for _old, _new in modified], - 'rebuilded': [{'oldpkg': {'name': _old.name, \ - 'version': {'epoch': _old.version.epoch, \ - 'rel': _old.version.rel, 'ver': _old.version.ver}, \ - 'vcs': _old.version.vcs}, \ - 'newpkg': {'name': _new.name, \ - 'version': {'epoch': _new.version.epoch, \ - 'rel': _new.version.rel, 'ver': _new.version.ver}, - 'vcs': _new.version.vcs}, 'codediff': None} \ - for _old, _new in rebuilded], - } - } - - return json.dumps(obj, indent=4) - -def diff_to_HTML(old_url, new_url): - """Output diffs' html format""" - - data = json.loads(diff_to_JSON(old_url, new_url)) - - context = {'old_url': old_url, - 'new_url': new_url, - 'diff': data['diff'], - } - - return output_html('diff.html', **context) - diff --git a/builddiff/templates/diff.html b/builddiff/templates/diff.html deleted file mode 100644 index 664bc0e..0000000 --- a/builddiff/templates/diff.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - -
-
- -
-
-

- Repository Difference and Changelogs -

- Go back...
-

Difference between - {{ old_url }} and {{ new_url }} -

-

Highlights

- - - - diff --git a/builddiff/utils.py b/builddiff/utils.py deleted file mode 100644 index 256c371..0000000 --- a/builddiff/utils.py +++ /dev/null @@ -1,86 +0,0 @@ -import re -import xml.sax.handler - -def xml2obj(src): - """ - A simple function to converts XML data into native Python object, - from http://goo.gl/Ymc6Nl, Licensed under the PSF License. - """ - - non_id_char = re.compile('[^_0-9a-zA-Z]') - def _name_mangle(name): - return non_id_char.sub('_', name) - - class DataNode(object): - def __init__(self): - self._attrs = {} # XML attributes and child elements - self.data = None # child text data - def __len__(self): - # treat single element as a list of 1 - return 1 - def __getitem__(self, key): - if isinstance(key, basestring): - return self._attrs.get(key, None) - else: - return [self][key] - def __contains__(self, name): - return self._attrs.has_key(name) - def __nonzero__(self): - return bool(self._attrs or self.data) - def __getattr__(self, name): - if name.startswith('__'): - # need to do this for Python special methods??? - raise AttributeError(name) - return self._attrs.get(name, None) - def _add_xml_attr(self, name, value): - if name in self._attrs: - # multiple attribute of the same name are represented by a list - children = self._attrs[name] - if not isinstance(children, list): - children = [children] - self._attrs[name] = children - children.append(value) - else: - self._attrs[name] = value - def __str__(self): - return self.data or '' - def __repr__(self): - items = sorted(self._attrs.items()) - if self.data: - items.append(('data', self.data)) - return u'{%s}' % ', '.join([u'%s:%s' % (k, repr(v)) for k, v in items]) - - class TreeBuilder(xml.sax.handler.ContentHandler): - def __init__(self): - self.stack = [] - self.root = DataNode() - self.current = self.root - self.text_parts = [] - def startElement(self, name, attrs): - self.stack.append((self.current, self.text_parts)) - self.current = DataNode() - self.text_parts = [] - # xml attributes --> python attributes - for k, v in attrs.items(): - self.current._add_xml_attr(_name_mangle(k), v) - def endElement(self, name): - text = ''.join(self.text_parts).strip() - if text: - self.current.data = text - if self.current._attrs: - obj = self.current - else: - # a text only node is simply represented by the string - obj = text or '' - self.current, self.text_parts = self.stack.pop() - self.current._add_xml_attr(_name_mangle(name), obj) - def characters(self, content): - self.text_parts.append(content) - - builder = TreeBuilder() - if isinstance(src, basestring): - xml.sax.parseString(src, builder) - else: - xml.sax.parse(src, builder) - return builder.root._attrs.values()[0] - diff --git a/packaging/builddiff.changes b/packaging/builddiff.changes deleted file mode 100644 index a90f011..0000000 --- a/packaging/builddiff.changes +++ /dev/null @@ -1,2 +0,0 @@ -* Wed Nov 20 2013 Lingchao Xin - 0.1 - - Initial packaging diff --git a/packaging/builddiff.spec b/packaging/builddiff.spec deleted file mode 100644 index 1e0dcda..0000000 --- a/packaging/builddiff.spec +++ /dev/null @@ -1,41 +0,0 @@ -# -# spec file for package builddiff -# -# Copyright (c) 2013 Lingchao Xin . -# - -Name: builddiff -Version: 0.1 -Release: 0 -License: GPL-2.0+ -Summary: Generate image and repo diffs -Url: http://www.tizen.org -Group: Development/Tools/Other -Source: %{name}-%{version}.tar.gz -BuildRequires: python-devel -Requires: python >= 2.7 -Requires: python-jinja2 -BuildRoot: %{_tmppath}/%{name}-%{version}-build -BuildArch: noarch -%{!?python_sitelib: %define python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} - -%description -Builddiff is used to generate image and repo diffs. - -%prep -%setup -q - -%build -CFLAGS="%{optflags}" python setup.py build - -%install -python setup.py install --prefix=%{_prefix} --root=%{buildroot} - -%clean -rm -rf %{buildroot} - -%files -%defattr(-,root,root) -%{_bindir}/* -%{python_sitelib}/* - diff --git a/packaging/snapdiff.changes b/packaging/snapdiff.changes new file mode 100644 index 0000000..a90f011 --- /dev/null +++ b/packaging/snapdiff.changes @@ -0,0 +1,2 @@ +* Wed Nov 20 2013 Lingchao Xin - 0.1 + - Initial packaging diff --git a/packaging/snapdiff.spec b/packaging/snapdiff.spec new file mode 100644 index 0000000..b8825f2 --- /dev/null +++ b/packaging/snapdiff.spec @@ -0,0 +1,41 @@ +# +# spec file for package python-snapdiff +# +# Copyright (c) 2013 Lingchao Xin . +# + +Name: python-snapdiff +Version: 0.1 +Release: 0 +License: GPL-2.0+ +Summary: Generate image and repo diffs +Url: http://www.tizen.org +Group: Development/Tools/Other +Source: %{name}-%{version}.tar.gz +BuildRequires: python-devel +Requires: python >= 2.7 +Requires: python-jinja2 +BuildRoot: %{_tmppath}/%{name}-%{version}-build +BuildArch: noarch +%{!?python_sitelib: %define python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} + +%description +Builddiff is used to generate image and repo diffs. + +%prep +%setup -q + +%build +CFLAGS="%{optflags}" python setup.py build + +%install +python setup.py install --prefix=%{_prefix} --root=%{buildroot} + +%clean +rm -rf %{buildroot} + +%files +%defattr(-,root,root) +%{_bindir}/* +%{python_sitelib}/* + diff --git a/setup.py b/setup.py index a4c41a7..ef7d6ee 100644 --- a/setup.py +++ b/setup.py @@ -5,14 +5,14 @@ try: except ImportError: from distutils.core import setup -setup(name = 'builddiff', +setup(name = 'python-snapdiff', version = '0.1', description = 'Generate image and repo diffs', author = 'Lingchao Xin', author_email = 'lingchaox.xin@intel.com', url = 'http://www.tizen.org/', scripts = ['tools/repo-diff',], - packages=['builddiff',], + packages=['snapdiff',], include_package_data=True, install_requires=['Jinja2>=2.6'], zip_safe=False, diff --git a/snapdiff/__init__.py b/snapdiff/__init__.py new file mode 100644 index 0000000..1e5a5e7 --- /dev/null +++ b/snapdiff/__init__.py @@ -0,0 +1,2 @@ + +__all__ = ['render', 'repo'] diff --git a/snapdiff/render.py b/snapdiff/render.py new file mode 100644 index 0000000..707a806 --- /dev/null +++ b/snapdiff/render.py @@ -0,0 +1,16 @@ +from jinja2 import Environment, FileSystemLoader +import os + +_SETTINGS = { + 'template_path': os.path.join(os.path.dirname(__file__), 'templates'), + } + +def output_html(template_name, **kwargs): + """Render a template file with given kwargs""" + env = Environment( + loader = FileSystemLoader(_SETTINGS['template_path']), + # auto_reload = True, + ) + template = env.get_template(template_name) + return template.render(kwargs) + diff --git a/snapdiff/repo.py b/snapdiff/repo.py new file mode 100644 index 0000000..4d20365 --- /dev/null +++ b/snapdiff/repo.py @@ -0,0 +1,148 @@ +from . import utils +from .render import output_html + +import gzip +import json +import os +import tempfile +import urllib2 +import xml.etree.cElementTree as ET + + +class RepoError(Exception): + """Local custom Exception class, handle Repo Errors""" + pass + + +def _get_primary_md(url, workspace, name): + """Get primary.xml.gz file from remote repodata directory""" + tree = ET.ElementTree(file=_download(url + '/repodata/repomd.xml', \ + workspace, name)) + for elem in tree.iter(tag='{http://linux.duke.edu/metadata/repo}data'): + if elem.attrib['type'] == 'primary': + for c in elem: + if c.tag == '{http://linux.duke.edu/metadata/repo}location': + href = c.attrib['href'] + if href: + return _download(url + href, workspace, \ + href.split('/')[-1]) + else: + raise RepoError('Repo primary metadata can\'t be found !') + +def _download(url, workspace, name): + """Download needed xml file to local by given url""" + xs_file = os.path.join(workspace, name) + + try: + rf = urllib2.urlopen(url) + except urllib2.HTTPError: + xs_file = None + else: + with open(xs_file, 'wb') as xs: + xs.write(rf.read()) + + return xs_file + + +class Repo(object): + """Stuff packages' info""" + + def __init__(self, url): + workspace = tempfile.mkdtemp(dir='/var/tmp') + primary_md = _get_primary_md(url, workspace, 'repomd.xml') + self._et = utils.xml2obj(gzip.open(primary_md)) + + @property + def packages(self): + packages_info = {} + for _package in self._et.package: + packages_info[_package.name] = (packages_info.get(_package.name) \ + or []) + [_package] + return packages_info + +def diff_to_JSON(old_url, new_url): + """Output diffs' json format""" + + if not old_url or not new_url: + return + + old, new = Repo(old_url).packages, Repo(new_url).packages + + added, removed, modified, rebuilded = [], [], [], [] + package_names = set(old.keys() + new.keys()) + + def _pair_old_new(): + for name in package_names: + if old.get(name) is None: + for pkg in new[name]: + yield (None, pkg) + elif new.get(name) is None: + for pkg in old[name]: + yield (pkg, None) + else: + for old_pkg in old[name]: + for new_pkg in new[name]: + if old_pkg.version.ver == new_pkg.version.ver: + yield (old_pkg, new_pkg) + old[name].remove(old_pkg) + new[name].remove(new_pkg) + for pair in map(None, old[name], new[name]): + yield pair + for old_pkg, new_pkg in _pair_old_new(): + if old_pkg is None: + added.append(new_pkg) + elif new_pkg is None: + removed.append(old_pkg) + elif old_pkg.version.ver == new_pkg.version.ver and \ + old_pkg.version.vcs == new_pkg.version.vcs: + rebuilded.append((old_pkg, new_pkg)) + else: + modified.append((old_pkg, new_pkg)) + + obj = {'repo': {'old': old_url, 'new': new_url}, + 'diff': { + 'added': [{'oldpkg': None, 'newpkg': {'name': _new.name, \ + 'version': {'epoch': _new.version.epoch, \ + 'rel': _new.version.rel, 'ver': _new.version.rel}, \ + 'vcs': _new.version.vcs}, 'codediff': None} \ + for _new in added], + 'removed': [{'oldpkg': {'name': _old.name, \ + 'version': {'epoch': _old.version.epoch, \ + 'rel': _old.version.rel, 'ver': _old.version.ver}, \ + 'vcs': _old.version.vcs}, 'newpkg': None, \ + 'codediff': None} for _old in added], + 'modified': [{'oldpkg': {'name': _old.name, \ + 'version': {'epoch': _old.version.epoch, \ + 'rel': _old.version.rel, 'ver': _old.version.ver}, \ + 'vcs': _old.version.vcs}, \ + 'newpkg': {'name': _new.name, \ + 'version': {'epoch': _new.version.epoch, \ + 'rel': _new.version.rel, 'ver': _new.version.ver}, + 'vcs': _new.version.vcs}, 'codediff': None} \ + for _old, _new in modified], + 'rebuilded': [{'oldpkg': {'name': _old.name, \ + 'version': {'epoch': _old.version.epoch, \ + 'rel': _old.version.rel, 'ver': _old.version.ver}, \ + 'vcs': _old.version.vcs}, \ + 'newpkg': {'name': _new.name, \ + 'version': {'epoch': _new.version.epoch, \ + 'rel': _new.version.rel, 'ver': _new.version.ver}, + 'vcs': _new.version.vcs}, 'codediff': None} \ + for _old, _new in rebuilded], + } + } + + return json.dumps(obj, indent=4) + +def diff_to_HTML(old_url, new_url): + """Output diffs' html format""" + + data = json.loads(diff_to_JSON(old_url, new_url)) + + context = {'old_url': old_url, + 'new_url': new_url, + 'diff': data['diff'], + } + + return output_html('diff.html', **context) + diff --git a/snapdiff/templates/diff.html b/snapdiff/templates/diff.html new file mode 100644 index 0000000..664bc0e --- /dev/null +++ b/snapdiff/templates/diff.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + +
+
+ +
+
+

+ Repository Difference and Changelogs +

+ Go back...
+

Difference between + {{ old_url }} and {{ new_url }} +

+

Highlights

+ + + + diff --git a/snapdiff/utils.py b/snapdiff/utils.py new file mode 100644 index 0000000..256c371 --- /dev/null +++ b/snapdiff/utils.py @@ -0,0 +1,86 @@ +import re +import xml.sax.handler + +def xml2obj(src): + """ + A simple function to converts XML data into native Python object, + from http://goo.gl/Ymc6Nl, Licensed under the PSF License. + """ + + non_id_char = re.compile('[^_0-9a-zA-Z]') + def _name_mangle(name): + return non_id_char.sub('_', name) + + class DataNode(object): + def __init__(self): + self._attrs = {} # XML attributes and child elements + self.data = None # child text data + def __len__(self): + # treat single element as a list of 1 + return 1 + def __getitem__(self, key): + if isinstance(key, basestring): + return self._attrs.get(key, None) + else: + return [self][key] + def __contains__(self, name): + return self._attrs.has_key(name) + def __nonzero__(self): + return bool(self._attrs or self.data) + def __getattr__(self, name): + if name.startswith('__'): + # need to do this for Python special methods??? + raise AttributeError(name) + return self._attrs.get(name, None) + def _add_xml_attr(self, name, value): + if name in self._attrs: + # multiple attribute of the same name are represented by a list + children = self._attrs[name] + if not isinstance(children, list): + children = [children] + self._attrs[name] = children + children.append(value) + else: + self._attrs[name] = value + def __str__(self): + return self.data or '' + def __repr__(self): + items = sorted(self._attrs.items()) + if self.data: + items.append(('data', self.data)) + return u'{%s}' % ', '.join([u'%s:%s' % (k, repr(v)) for k, v in items]) + + class TreeBuilder(xml.sax.handler.ContentHandler): + def __init__(self): + self.stack = [] + self.root = DataNode() + self.current = self.root + self.text_parts = [] + def startElement(self, name, attrs): + self.stack.append((self.current, self.text_parts)) + self.current = DataNode() + self.text_parts = [] + # xml attributes --> python attributes + for k, v in attrs.items(): + self.current._add_xml_attr(_name_mangle(k), v) + def endElement(self, name): + text = ''.join(self.text_parts).strip() + if text: + self.current.data = text + if self.current._attrs: + obj = self.current + else: + # a text only node is simply represented by the string + obj = text or '' + self.current, self.text_parts = self.stack.pop() + self.current._add_xml_attr(_name_mangle(name), obj) + def characters(self, content): + self.text_parts.append(content) + + builder = TreeBuilder() + if isinstance(src, basestring): + xml.sax.parseString(src, builder) + else: + xml.sax.parse(src, builder) + return builder.root._attrs.values()[0] + diff --git a/tools/repo-diff b/tools/repo-diff index f553cbb..e73d272 100755 --- a/tools/repo-diff +++ b/tools/repo-diff @@ -1,6 +1,6 @@ #!/usr/bin/env python -from builddiff.repo import diff_to_JSON, diff_to_HTML +from snapdiff.repo import diff_to_JSON, diff_to_HTML import argparse import sys