(trunk-1599/1600/1601)

* setup.py can install the manual (and by extension do can debuild)
* setup.py has (inactive) code for generating the html from the source
  but this will break if rtd theme is not available
* A few changes to doc strings to make the autodoc prettier
* Added help shortcut, by default F1 to open the local manual
* Added button to About tab to launch manual
* A couple of additional string to translate related to manual/help

* Small tweak to setup.py to seperate build and install, and always attempt
  to install manual by default.

* Small fix for systems (i.e. my 12.04 LTS) that don't set LANGUAGE for
  whatever reason. This breaks the manual lookup
This commit is contained in:
Stephen Boddy 2015-09-01 22:50:09 +02:00
parent 825214da1e
commit f64d3e67fa
87 changed files with 18467 additions and 17762 deletions

464
po/af.po

File diff suppressed because it is too large Load Diff

467
po/ar.po

File diff suppressed because it is too large Load Diff

464
po/ast.po

File diff suppressed because it is too large Load Diff

464
po/az.po

File diff suppressed because it is too large Load Diff

465
po/be.po

File diff suppressed because it is too large Load Diff

468
po/bg.po

File diff suppressed because it is too large Load Diff

468
po/bn.po

File diff suppressed because it is too large Load Diff

464
po/bs.po

File diff suppressed because it is too large Load Diff

464
po/ca.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

464
po/ckb.po

File diff suppressed because it is too large Load Diff

468
po/cs.po

File diff suppressed because it is too large Load Diff

464
po/da.po

File diff suppressed because it is too large Load Diff

468
po/de.po

File diff suppressed because it is too large Load Diff

464
po/el.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

464
po/eo.po

File diff suppressed because it is too large Load Diff

467
po/es.po

File diff suppressed because it is too large Load Diff

464
po/et.po

File diff suppressed because it is too large Load Diff

471
po/eu.po

File diff suppressed because it is too large Load Diff

464
po/fa.po

File diff suppressed because it is too large Load Diff

464
po/fi.po

File diff suppressed because it is too large Load Diff

464
po/fo.po

File diff suppressed because it is too large Load Diff

468
po/fr.po

File diff suppressed because it is too large Load Diff

464
po/fy.po

File diff suppressed because it is too large Load Diff

464
po/ga.po

File diff suppressed because it is too large Load Diff

467
po/gl.po

File diff suppressed because it is too large Load Diff

464
po/he.po

File diff suppressed because it is too large Load Diff

467
po/hi.po

File diff suppressed because it is too large Load Diff

467
po/hr.po

File diff suppressed because it is too large Load Diff

464
po/hu.po

File diff suppressed because it is too large Load Diff

464
po/hy.po

File diff suppressed because it is too large Load Diff

464
po/id.po

File diff suppressed because it is too large Load Diff

464
po/is.po

File diff suppressed because it is too large Load Diff

471
po/it.po

File diff suppressed because it is too large Load Diff

479
po/ja.po

File diff suppressed because it is too large Load Diff

464
po/jv.po

File diff suppressed because it is too large Load Diff

464
po/ka.po

File diff suppressed because it is too large Load Diff

464
po/kk.po

File diff suppressed because it is too large Load Diff

481
po/ko.po

File diff suppressed because it is too large Load Diff

464
po/la.po

File diff suppressed because it is too large Load Diff

464
po/lt.po

File diff suppressed because it is too large Load Diff

464
po/lv.po

File diff suppressed because it is too large Load Diff

464
po/mk.po

File diff suppressed because it is too large Load Diff

464
po/ml.po

File diff suppressed because it is too large Load Diff

464
po/mr.po

File diff suppressed because it is too large Load Diff

471
po/ms.po

File diff suppressed because it is too large Load Diff

472
po/nb.po

File diff suppressed because it is too large Load Diff

464
po/nl.po

File diff suppressed because it is too large Load Diff

464
po/nn.po

File diff suppressed because it is too large Load Diff

464
po/oc.po

File diff suppressed because it is too large Load Diff

464
po/pl.po

File diff suppressed because it is too large Load Diff

468
po/pt.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

467
po/ro.po

File diff suppressed because it is too large Load Diff

474
po/ru.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

464
po/si.po

File diff suppressed because it is too large Load Diff

468
po/sk.po

File diff suppressed because it is too large Load Diff

464
po/sl.po

File diff suppressed because it is too large Load Diff

464
po/sq.po

File diff suppressed because it is too large Load Diff

464
po/sr.po

File diff suppressed because it is too large Load Diff

464
po/su.po

File diff suppressed because it is too large Load Diff

468
po/sv.po

File diff suppressed because it is too large Load Diff

485
po/ta.po

File diff suppressed because it is too large Load Diff

464
po/te.po

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-08-04 20:49+0200\n"
"POT-Creation-Date: 2015-09-01 22:49+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -208,11 +208,11 @@ msgstr ""
msgid "Launch"
msgstr ""
#: ../terminatorlib/notebook.py:348
#: ../terminatorlib/notebook.py:350
msgid "tab"
msgstr ""
#: ../terminatorlib/notebook.py:564
#: ../terminatorlib/notebook.py:566
msgid "Close Tab"
msgstr ""
@ -1015,14 +1015,6 @@ msgstr ""
#: ../terminatorlib/preferences.glade.h:145
msgid ""
"<a href=\"http://gnometerminator.blogspot.com/p/introduction.html"
"\">Homepage</a>\n"
"<a href=\"http://gnometerminator.blogspot.com/\">Blog / News</a>\n"
"<a href=\"https://launchpad.net/terminator\">Development</a>"
msgstr ""
#: ../terminatorlib/preferences.glade.h:148
msgid ""
"The goal of this project is to produce a useful tool for arranging "
"terminals. It is inspired by programs such as gnome-multi-term, quadkonsole, "
"etc. in that the main focus is arranging terminals in grids (tabs is the "
@ -1035,7 +1027,19 @@ msgid ""
"the Development link)"
msgstr ""
#: ../terminatorlib/preferences.glade.h:151
#: ../terminatorlib/preferences.glade.h:148
msgid "The Manual"
msgstr ""
#: ../terminatorlib/preferences.glade.h:149
msgid ""
"<a href=\"http://gnometerminator.blogspot.com/p/introduction.html"
"\">Homepage</a>\n"
"<a href=\"http://gnometerminator.blogspot.com/\">Blog / News</a>\n"
"<a href=\"https://launchpad.net/terminator\">Development</a>"
msgstr ""
#: ../terminatorlib/preferences.glade.h:152
msgid "About"
msgstr ""
@ -1307,11 +1311,15 @@ msgstr ""
msgid "Switch to previous profile"
msgstr ""
#: ../terminatorlib/prefseditor.py:1032 ../terminatorlib/prefseditor.py:1037
#: ../terminatorlib/prefseditor.py:153
msgid "Open the manual"
msgstr ""
#: ../terminatorlib/prefseditor.py:1033 ../terminatorlib/prefseditor.py:1038
msgid "New Profile"
msgstr ""
#: ../terminatorlib/prefseditor.py:1077 ../terminatorlib/prefseditor.py:1082
#: ../terminatorlib/prefseditor.py:1078 ../terminatorlib/prefseditor.py:1083
msgid "New Layout"
msgstr ""
@ -1439,77 +1447,77 @@ msgstr ""
msgid "Other Encodings"
msgstr ""
#: ../terminatorlib/terminal.py:455
#: ../terminatorlib/terminal.py:454
msgid "N_ew group..."
msgstr ""
#: ../terminatorlib/terminal.py:461
#: ../terminatorlib/terminal.py:460
msgid "_None"
msgstr ""
#: ../terminatorlib/terminal.py:481
#: ../terminatorlib/terminal.py:480
#, python-format
msgid "Remove group %s"
msgstr ""
#: ../terminatorlib/terminal.py:486
#: ../terminatorlib/terminal.py:485
msgid "G_roup all in tab"
msgstr ""
#: ../terminatorlib/terminal.py:491
#: ../terminatorlib/terminal.py:490
msgid "Ungro_up all in tab"
msgstr ""
#: ../terminatorlib/terminal.py:496
#: ../terminatorlib/terminal.py:495
msgid "Remove all groups"
msgstr ""
#: ../terminatorlib/terminal.py:503
#: ../terminatorlib/terminal.py:502
#, python-format
msgid "Close group %s"
msgstr ""
#: ../terminatorlib/terminal.py:513
#: ../terminatorlib/terminal.py:512
msgid "Broadcast _all"
msgstr ""
#: ../terminatorlib/terminal.py:514
#: ../terminatorlib/terminal.py:513
msgid "Broadcast _group"
msgstr ""
#: ../terminatorlib/terminal.py:515
#: ../terminatorlib/terminal.py:514
msgid "Broadcast _off"
msgstr ""
#: ../terminatorlib/terminal.py:531
#: ../terminatorlib/terminal.py:530
msgid "_Split to this group"
msgstr ""
#: ../terminatorlib/terminal.py:536
#: ../terminatorlib/terminal.py:535
msgid "Auto_clean groups"
msgstr ""
#: ../terminatorlib/terminal.py:543
#: ../terminatorlib/terminal.py:542
msgid "_Insert terminal number"
msgstr ""
#: ../terminatorlib/terminal.py:547
#: ../terminatorlib/terminal.py:546
msgid "Insert _padded terminal number"
msgstr ""
#: ../terminatorlib/terminal.py:1359
#: ../terminatorlib/terminal.py:1355
msgid "Unable to find a shell"
msgstr ""
#: ../terminatorlib/terminal.py:1390
#: ../terminatorlib/terminal.py:1386
msgid "Unable to start shell:"
msgstr ""
#: ../terminatorlib/terminal.py:1783
#: ../terminatorlib/terminal.py:1779
msgid "Rename Window"
msgstr ""
#: ../terminatorlib/terminal.py:1792
#: ../terminatorlib/terminal.py:1788
msgid "Enter a new title for the Terminator window..."
msgstr ""

464
po/th.po

File diff suppressed because it is too large Load Diff

464
po/tr.po

File diff suppressed because it is too large Load Diff

464
po/tyv.po

File diff suppressed because it is too large Load Diff

471
po/uk.po

File diff suppressed because it is too large Load Diff

464
po/ur.po

File diff suppressed because it is too large Load Diff

464
po/vi.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -18,13 +18,18 @@ from terminatorlib.version import APP_NAME, APP_VERSION
PO_DIR = 'po'
MO_DIR = os.path.join('build', 'mo')
DOC_DIR = 'doc'
class TerminatorDist(Distribution):
global_options = Distribution.global_options + [
("build-documentation", None, "Build the documentation"),
("install-documentation", None, "Install the documentation"),
("without-gettext", None, "Don't build/install gettext .mo files"),
("without-icon-cache", None, "Don't attempt to run gtk-update-icon-cache")]
def __init__ (self, *args):
self.build_documentation = False
self.install_documentation = True
self.without_gettext = False
self.without_icon_cache = False
Distribution.__init__(self, *args)
@ -34,36 +39,50 @@ class BuildData(build):
def run (self):
build.run (self)
if self.distribution.without_gettext:
return
if not self.distribution.without_gettext:
# Build the translations
for po in glob.glob (os.path.join (PO_DIR, '*.po')):
lang = os.path.basename(po[:-3])
mo = os.path.join(MO_DIR, lang, 'terminator.mo')
for po in glob.glob (os.path.join (PO_DIR, '*.po')):
lang = os.path.basename(po[:-3])
mo = os.path.join(MO_DIR, lang, 'terminator.mo')
directory = os.path.dirname(mo)
if not os.path.exists(directory):
info('creating %s' % directory)
os.makedirs(directory)
directory = os.path.dirname(mo)
if not os.path.exists(directory):
info('creating %s' % directory)
os.makedirs(directory)
if newer(po, mo):
info('compiling %s -> %s' % (po, mo))
try:
rc = subprocess.call(['msgfmt', '-o', mo, po])
if rc != 0:
raise Warning, "msgfmt returned %d" % rc
except Exception, e:
error("Building gettext files failed. Try setup.py --without-gettext [build|install]")
error("Error: %s" % str(e))
sys.exit(1)
if newer(po, mo):
info('compiling %s -> %s' % (po, mo))
try:
rc = subprocess.call(['msgfmt', '-o', mo, po])
if rc != 0:
raise Warning, "msgfmt returned %d" % rc
except Exception, e:
error("Building gettext files failed. Try setup.py --without-gettext [build|install]")
error("Error: %s" % str(e))
sys.exit(1)
TOP_BUILDDIR='.'
INTLTOOL_MERGE='intltool-merge'
desktop_in='data/terminator.desktop.in'
desktop_data='data/terminator.desktop'
os.system ("C_ALL=C " + INTLTOOL_MERGE + " -d -u -c " + TOP_BUILDDIR +
"/po/.intltool-merge-cache " + TOP_BUILDDIR + "/po " +
desktop_in + " " + desktop_data)
appdata_in='data/terminator.appdata.xml.in'
appdata_data='data/terminator.appdata.xml'
os.system ("C_ALL=C " + INTLTOOL_MERGE + " -x -u -c " + TOP_BUILDDIR +
"/po/.intltool-merge-cache " + TOP_BUILDDIR + "/po " +
appdata_in + " " + appdata_data)
TOP_BUILDDIR='.'
INTLTOOL_MERGE='intltool-merge'
desktop_in='data/terminator.desktop.in'
desktop_data='data/terminator.desktop'
os.system ("C_ALL=C " + INTLTOOL_MERGE + " -d -u -c " + TOP_BUILDDIR +
"/po/.intltool-merge-cache " + TOP_BUILDDIR + "/po " +
desktop_in + " " + desktop_data)
if self.distribution.build_documentation:
# Build the documentation
for doc_folder in (glob.glob (os.path.join (DOC_DIR, 'manual*')) + [os.path.join (DOC_DIR, 'apidoc')]):
if os.path.isfile(os.path.join(doc_folder, 'Makefile')):
old_cwd = os.getcwd()
os.chdir(doc_folder)
os.system("make clean")
os.system("make html")
os.chdir(old_cwd)
class Uninstall(Command):
description = "Attempt an uninstall from an install --record file"
@ -126,6 +145,7 @@ class Uninstall(Command):
class InstallData(install_data):
def run (self):
self.data_files.extend (self._find_mo_files ())
self.data_files.extend (self._find_doc_files ())
install_data.run (self)
if not self.distribution.without_icon_cache:
self._update_icon_cache ()
@ -149,6 +169,27 @@ class InstallData(install_data):
return data_files
def _find_doc_files (self):
data_files = []
if self.distribution.install_documentation:
for doc_folder in (glob.glob (os.path.join (DOC_DIR, 'manual*')) + [os.path.join (DOC_DIR, 'apidoc')]):
# construct new path
src = os.path.join(doc_folder, '_build', 'html')
dest_sub = os.path.split(doc_folder[:])[1]
if dest_sub[:6] == 'manual':
dest_sub = 'html'+dest_sub[6:]
dest = os.path.join('share', 'doc', 'terminator', dest_sub)
if os.path.isdir(src):
for dirpath, dirnames, filenames in os.walk(src):
cut_elem_count = len(doc_folder.split(os.sep)) + 2
dest_sub = os.path.join(dirpath.split(os.sep)[cut_elem_count:])
full_dest_folder = os.path.join(dest, *dest_sub)
full_src_filenames = [os.path.join(dirpath, filename) for filename in filenames]
data_files.append((full_dest_folder, full_src_filenames))
return data_files
class Test(Command):
user_options = []
def initialize_options(self):

View File

@ -2,10 +2,11 @@
# Terminator by Chris Jones <cmsj@tenshu.net>
# GPL v2 only
"""borg.py - We are the borg. Resistance is futile.
http://code.activestate.com/recipes/66531/
ActiveState's policy appears to be that snippets
exist to encourage re-use, but I can not find any
specific licencing terms.
http://code.activestate.com/recipes/66531/
ActiveState's policy appears to be that snippets
exist to encourage re-use, but I can not find any
specific licencing terms.
"""
from util import dbg
@ -16,20 +17,18 @@ class Borg:
"""Definition of a class that can never be duplicated. Correct usage is
thus:
from borg import Borg
class foo(Borg):
# All attributes on a borg class *must* = None
attribute = None
def __init__(self):
Borg.__init__(self, self.__class__.__name__)
def prepare_attributes(self):
if not self.attribute:
self.attribute = []
bar = foo()
bar.prepare_attributes()
>>> from borg import Borg
>>> class foo(Borg):
... # All attributes on a borg class *must* = None
... attribute = None
... def __init__(self):
... Borg.__init__(self, self.__class__.__name__)
... def prepare_attributes(self):
... if not self.attribute:
... self.attribute = []
...
>>> bar = foo()
>>> bar.prepare_attributes()
The important thing to note is that all attributes of borg classes *must* be
declared as being None. If you attempt to use static class attributes you

View File

@ -191,7 +191,8 @@ DEFAULTS = {
'edit_window_title': '<Alt>t',
'layout_launcher' : '<Alt>l',
'next_profile' : '',
'previous_profile' : ''
'previous_profile' : '',
'help' : 'F1'
},
'profiles': {
'default': {

View File

@ -1191,12 +1191,6 @@ class ConfigObj(Section):
write_empty_values=False, _inspec=False):
"""
Parse a config file or create a config file object.
``ConfigObj(infile=None, configspec=None, encoding=None,
interpolation=True, raise_errors=False, list_values=True,
create_empty=False, file_error=False, stringify=True,
indent_type=None, default_encoding=None, unrepr=False,
write_empty_values=False, _inspec=False)``
"""
self._inspec = _inspec
# init the superclass

View File

@ -160,7 +160,6 @@ __all__ = (
'is_ip_addr_list',
'is_mixed_list',
'is_option',
'__docformat__',
)
@ -481,7 +480,7 @@ class Validator(object):
... if not value <= max:
... raise VdtValueTooBigError(value)
... return value
...
>>> fdict = {'int_range': int_range_check}
>>> vtr1 = Validator(fdict)
>>> vtr1.check('int_range(20, 40)', '30')
@ -571,6 +570,7 @@ class Validator(object):
Arguments:
check: string representing check to apply (including arguments)
value: object to be checked
Returns value, converted to correct type if necessary
If the check fails, raises a ``ValidateError`` subclass.

View File

@ -3864,25 +3864,6 @@
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label29">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="xalign">1</property>
<property name="yalign">0</property>
<property name="label" translatable="yes">&lt;a href="http://gnometerminator.blogspot.com/p/introduction.html"&gt;Homepage&lt;/a&gt;
&lt;a href="http://gnometerminator.blogspot.com/"&gt;Blog / News&lt;/a&gt;
&lt;a href="https://launchpad.net/terminator"&gt;Development&lt;/a&gt;</property>
<property name="use_markup">True</property>
<property name="justify">right</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label27">
<property name="visible">True</property>
@ -3904,6 +3885,50 @@ Much of the behavior of Terminator is based on GNOME Terminal, and we are adding
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox19">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkButton" id="button1">
<property name="label" translatable="yes">The Manual</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_action_appearance">False</property>
<signal name="clicked" handler="on_open_manual" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label29">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="yalign">0</property>
<property name="label" translatable="yes">&lt;a href="http://gnometerminator.blogspot.com/p/introduction.html"&gt;Homepage&lt;/a&gt;
&lt;a href="http://gnometerminator.blogspot.com/"&gt;Blog / News&lt;/a&gt;
&lt;a href="https://launchpad.net/terminator"&gt;Development&lt;/a&gt;</property>
<property name="use_markup">True</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options"/>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>

View File

@ -149,7 +149,8 @@ class PrefsEditor:
'edit_window_title': _('Edit window title'),
'layout_launcher' : _('Open layout launcher window'),
'next_profile' : _('Switch to next profile'),
'previous_profile' : _('Switch to previous profile')
'previous_profile' : _('Switch to previous profile'),
'help' : _('Open the manual')
}
def __init__ (self, term):
@ -1411,6 +1412,10 @@ class PrefsEditor:
self.config['keybindings'][binding] = None
self.config.save()
def on_open_manual(self, widget):
"""Open the fine manual"""
self.term.key_help()
class LayoutEditor:
profile_ids_to_profile = None
profile_profile_to_ids = None

View File

@ -13,7 +13,7 @@ from gi.repository import Vte
import subprocess
import urllib
from util import dbg, err, spawn_new_terminator, make_uuid
from util import dbg, err, spawn_new_terminator, make_uuid, manual_lookup
import util
from config import Config
from cwd import get_default_cwd
@ -1826,6 +1826,11 @@ class Terminal(Gtk.VBox):
def key_line_down(self):
self.scroll_by_line(1)
def key_help(self):
manual_index_page = manual_lookup()
if manual_index_page:
self.open_url('file://%s' % (manual_index_page))
# End key events
GObject.type_register(Terminal)

View File

@ -14,14 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Terminator.util - misc utility functions
>>> a = {'foo': 'bar', 'baz': 'bjonk'}
>>> b = {'foo': 'far', 'baz': 'bjonk'}
>>> dict_diff(a, b)
{'foo': 'far'}
"""
"""Terminator.util - misc utility functions"""
import sys
from gi.repository import Gtk, Gdk
@ -92,6 +85,26 @@ def has_ancestor(widget, wtype):
return(True)
return(False)
def manual_lookup():
'''Choose the manual to open based on LANGUAGE'''
prefix = os.path.join(os.sep, 'usr', 'share', 'doc', 'terminator')
if 'LANGUAGE' in os.environ:
languages = os.environ['LANGUAGE'].split(':')
for language in languages:
full_path = os.path.join(prefix, 'html_%s' % (language), 'index.html')
if os.path.isfile(full_path):
dbg('Found %s manual' % (language))
return full_path
dbg('Couldn\'t find manual for %s language' % (language))
full_path = os.path.join(prefix, 'html', 'index.html')
if os.path.isfile(full_path):
dbg('Falling back to the default manual')
return full_path
else:
dbg('I can\'t find any suitable manual')
return None
def path_lookup(command):
'''Find a command in our path'''
if os.path.isabs(command):
@ -183,7 +196,13 @@ def get_config_dir():
def dict_diff(reference, working):
"""Examine the values in the supplied working set and return a new dict
that only contains those values which are different from those in the
reference dictionary"""
reference dictionary
>>> a = {'foo': 'bar', 'baz': 'bjonk'}
>>> b = {'foo': 'far', 'baz': 'bjonk'}
>>> dict_diff(a, b)
{'foo': 'far'}
"""
result = {}