-include builddiff/templates/*.html
+include snapdiff/templates/*.html
+++ /dev/null
-
-__all__ = ['render', 'repo']
+++ /dev/null
-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)
-
+++ /dev/null
-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)
-
+++ /dev/null
-<!DOCTYPE HTML>
-<html>
-<head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
- <link href="theme/html-reset.css" media="screen" rel="stylesheet" type="text/css" />
- <link href="theme/layout-liquid.css" media="screen" rel="stylesheet" type="text/css" />
- <link href="theme/tizen.css" media="screen" rel="stylesheet" type="text/css" />
- <link href="theme/style.css" media="screen" rel="stylesheet" type="text/css" />
- <script type="text/javascript" src="theme/js/shCore.js"></script>
- <script type="text/javascript" src="theme/js/shBrushDiff.js"></script>
- <link href="theme/css/shCore.css" rel="stylesheet" type="text/css" />
- <link href="theme/css/shThemeDefault.css" rel="stylesheet" type="text/css" />
-</head>
-<body>
- <script type="text/javascript">SyntaxHighlighter.all()</script>
- <div id="page-wrapper">
- <div id="page">
- <div id="header">
- <div class="section clearfix">
- <h1 id="logo">
- <a href="http://www.tizen.org"><img alt="Tizen-logo" src="theme/tizen-logo.png" width="180px" height="50px"></a>
- </h1>
- </div>
- </div>
- </div>
- </div>
- <h1>
- Repository Difference and Changelogs
- </h1>
- <a href="index.html">Go back</a>...<br>
- <p>Difference between
- <a href="{{old_url}}">{{ old_url }}</a> and <a href="{{new_url}}">{{ new_url }}</a>
- </p>
- <h3>Highlights</h3>
- <ul>
- <li><a href="#added">Added Packages: {{ diff['added']|count }}</a></li>
- <li><a href="#removed">Removed Packages: {{ diff['removed']|count }}</a></li>
- <li><a href="#modified">Modified packages: {{ diff['modified']|count }}</a></li>
- <li><a href="#rebuilded">Packages with Rebuilds: {{ diff['rebuilded']|count }}</a></li>
- </ul>
-</body>
-</html>
-
+++ /dev/null
-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]
-
+++ /dev/null
-* Wed Nov 20 2013 Lingchao Xin <lingchaox.xin@intel.com> - 0.1
- - Initial packaging
+++ /dev/null
-#
-# spec file for package builddiff
-#
-# Copyright (c) 2013 Lingchao Xin <lingchaox.xin@intel.com>.
-#
-
-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}/*
-
--- /dev/null
+* Wed Nov 20 2013 Lingchao Xin <lingchaox.xin@intel.com> - 0.1
+ - Initial packaging
--- /dev/null
+#
+# spec file for package python-snapdiff
+#
+# Copyright (c) 2013 Lingchao Xin <lingchaox.xin@intel.com>.
+#
+
+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}/*
+
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,
--- /dev/null
+
+__all__ = ['render', 'repo']
--- /dev/null
+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)
+
--- /dev/null
+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)
+
--- /dev/null
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <link href="theme/html-reset.css" media="screen" rel="stylesheet" type="text/css" />
+ <link href="theme/layout-liquid.css" media="screen" rel="stylesheet" type="text/css" />
+ <link href="theme/tizen.css" media="screen" rel="stylesheet" type="text/css" />
+ <link href="theme/style.css" media="screen" rel="stylesheet" type="text/css" />
+ <script type="text/javascript" src="theme/js/shCore.js"></script>
+ <script type="text/javascript" src="theme/js/shBrushDiff.js"></script>
+ <link href="theme/css/shCore.css" rel="stylesheet" type="text/css" />
+ <link href="theme/css/shThemeDefault.css" rel="stylesheet" type="text/css" />
+</head>
+<body>
+ <script type="text/javascript">SyntaxHighlighter.all()</script>
+ <div id="page-wrapper">
+ <div id="page">
+ <div id="header">
+ <div class="section clearfix">
+ <h1 id="logo">
+ <a href="http://www.tizen.org"><img alt="Tizen-logo" src="theme/tizen-logo.png" width="180px" height="50px"></a>
+ </h1>
+ </div>
+ </div>
+ </div>
+ </div>
+ <h1>
+ Repository Difference and Changelogs
+ </h1>
+ <a href="index.html">Go back</a>...<br>
+ <p>Difference between
+ <a href="{{old_url}}">{{ old_url }}</a> and <a href="{{new_url}}">{{ new_url }}</a>
+ </p>
+ <h3>Highlights</h3>
+ <ul>
+ <li><a href="#added">Added Packages: {{ diff['added']|count }}</a></li>
+ <li><a href="#removed">Removed Packages: {{ diff['removed']|count }}</a></li>
+ <li><a href="#modified">Modified packages: {{ diff['modified']|count }}</a></li>
+ <li><a href="#rebuilded">Packages with Rebuilds: {{ diff['rebuilded']|count }}</a></li>
+ </ul>
+</body>
+</html>
+
--- /dev/null
+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]
+
#!/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