deskbar-applet r2216 - in trunk: . deskbar/handlers



Author: sebp
Date: Mon Jun 16 12:27:16 2008
New Revision: 2216
URL: http://svn.gnome.org/viewvc/deskbar-applet?rev=2216&view=rev

Log:
Added calculator module with permission from the original authors Callum McKenzie <callum at spooky-possum org>, Michael Hofmann <mh21 at piware de> and Johannes Buchner <buchner.johannes gmx at>

Added:
   trunk/deskbar/handlers/calculator.py
   trunk/deskbar/handlers/test_calculator.py
Modified:
   trunk/ChangeLog
   trunk/deskbar/handlers/Makefile.am

Modified: trunk/deskbar/handlers/Makefile.am
==============================================================================
--- trunk/deskbar/handlers/Makefile.am	(original)
+++ trunk/deskbar/handlers/Makefile.am	Mon Jun 16 12:27:16 2008
@@ -11,6 +11,7 @@
 deskbar_handlers_PYTHON = \
 	beagle-static.py \
 	beagle-live.py \
+	calculator.py \
 	desklicious.py \
 	epiphany.py \
 	files.py \

Added: trunk/deskbar/handlers/calculator.py
==============================================================================
--- (empty file)
+++ trunk/deskbar/handlers/calculator.py	Mon Jun 16 12:27:16 2008
@@ -0,0 +1,207 @@
+#
+#  calculator.py : A calculator module for the deskbar applet.
+#
+#  Copyright (C) 2008 by Johannes Buchner
+#  Copyright (C) 2007 by Michael Hofmann
+#  Copyright (C) 2006 by Callum McKenzie
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+# 
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+# 
+#  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 
+#  Authors: 
+#      Callum McKenzie <callum spooky-possum org> - Original author
+#      Michael Hofmann <mh21 piware de> - compatibility changes for deskbar 2.20
+#      Johannes Buchner <buchner johannes gmx at> - Made externally usable
+#
+#  This version of calculator can be used with converter
+#    read how at http://twoday.tuwien.ac.at/jo/search?q=calculator+converter+deskbar
+#
+
+from __future__ import division
+from deskbar.handlers.actions.CopyToClipboardAction import CopyToClipboardAction
+from deskbar.defs import VERSION
+from gettext import gettext as _
+import deskbar.core.Utils
+import deskbar.interfaces.Match
+import deskbar.interfaces.Module
+import logging
+import math
+import re
+
+LOGGER = logging.getLogger(__name__)
+
+HANDLERS = ["CalculatorModule"]
+
+def bin (n):
+    """A local binary equivalent of the hex and oct builtins."""
+    if (n == 0):
+        return "0b0"
+    s = ""
+    if (n < 0):
+        while n != -1:
+            s = str (n & 1) + s
+            n >>= 1
+        return "0b" + "...111" + s            
+    else:
+        while n != 0:
+            s = str (n & 1) + s
+            n >>= 1
+        return "0b" + s
+
+# These next three make sure {hex, oct, bin} can handle floating point,
+# by rounding. This makes sure things like hex(255/2) behave as a
+# programmer would expect while allowing 255/2 to equal 127.5 for normal
+# people. Abstracting out the body of these into a single function which
+# takes hex, oct or bin as an argument seems to run into problems with
+# those functions not being defined correctly in the resticted eval (?).
+
+def lenient_hex (c):
+    try:
+        return hex (c)
+    except TypeError:
+        return hex (int (c))
+
+def lenient_oct (c):
+    try:
+        return oct (c)
+    except TypeError:
+        return oct (int (c))
+
+def lenient_bin (c):
+    try:
+        return bin (c)
+    except TypeError:
+        return bin (int (c))
+
+class CalculatorAction (CopyToClipboardAction):
+
+    def __init__ (self, text, answer):
+        CopyToClipboardAction.__init__ (self, answer, answer)
+        self.text = text
+
+    def get_verb(self):
+        return _("Copy <b>%(origtext)s = %(name)s</b> to clipboard")
+
+    def get_name(self, text = None):
+        """Because the text variable for history entries contains the text
+        typed for the history search (and not the text of the orginal action),
+        we store the original text seperately."""
+        result = CopyToClipboardAction.get_name (self, text)
+        result["origtext"] = self.text
+        return result
+
+    def get_tooltip(self, text=None):
+        return self._name
+      
+class CalculatorMatch (deskbar.interfaces.Match):
+
+    def __init__ (self, text, answer, **kwargs):
+        deskbar.interfaces.Match.__init__ (self, name = text,
+                icon = "gtk-add", category = "calculator", **kwargs)
+        self.answer = str (answer)
+        self.add_action (CalculatorAction (text, self.answer))
+
+    def get_hash (self):
+        return self.answer
+
+class CalculatorModule (deskbar.interfaces.Module):
+    
+    INFOS = {"icon": deskbar.core.Utils.load_icon ("gtk-add"),
+             "name": _("Calculator"),
+             "description": _("Calculate simple equations"),
+             "version" : VERSION,
+             "categories" : { "calculator" : { "name" : _("Calculator") }}}
+
+    def __init__ (self):
+        deskbar.interfaces.Module.__init__ (self)
+        self.hexre = re.compile ("0[Xx][0-9a-fA-F_]*[0-9a-fA-F]")
+        self.binre = re.compile ("0[bB][01_]*[01]")
+
+    def _number_parser (self, match, base):
+        """A generic number parser, regardless of base. It also ignores the
+        '_' character so it can be used as a separator. Note how we skip
+        the first two characters since we assume it is something like '0x'
+        or '0b' and identifies the base."""
+        table = { '0' : 0, '1' : 1, '2' : 2, '3' : 3, '4' : 4,
+                  '5' : 5, '6' : 6, '7' : 7, '8' : 8, '9' : 9,
+                  'a' : 10, 'b' : 11, 'c' : 12, 'd' : 13,
+                  'e' : 14, 'f' : 15 }
+        d = 0
+        for c in match.group()[2:]:
+            if c != "_":
+                d = d * base + table[c]
+        return str (d)
+
+    def _binsub (self, match):
+        """Because python doesn't handle binary literals, we parse it
+        ourselves and replace it with a decimal representation."""
+        return self._number_parser (match, 2)
+
+    def _hexsub (self, match):
+        """Parse the hex literal ourselves. We could let python do it, but
+        since we have a generic parser we use that instead."""
+        return self._number_parser (match, 16)
+
+    def run_query (self, query):
+       """We evaluate the equation by first replacing hex and binary literals
+       with their decimal representation. (We need to check hex, so we can
+       distinguish 0x10b1 as a hex number, not 0x1 followed by 0b1.) We
+       severely restrict the eval environment.  Any errors are ignored."""
+       restricted_dictionary = { "__builtins__" : None, "abs" : abs,
+                                 "acos" : math.acos, "asin"   : math.asin,
+                                 "atan" : math.atan, "atan2"  : math.atan2,
+                                 "bin"  : lenient_bin,"ceil"  : math.ceil,
+                                 "cos"  : math.cos,  "cosh"   : math.cosh,
+                                 "degrees" : math.degrees,
+                                 "exp"  : math.exp,  "floor"  : math.floor,
+                                 "hex"  : lenient_hex, "int"  : int,
+                                 "log"  : math.log,  "pow"    : math.pow,
+                                 "log10" : math.log10, "oct"  : lenient_oct,
+                                 "pi"   : math.pi,  "radians" : math.radians,
+                                 "round": round,     "sin"    : math.sin,
+                                 "sinh" : math.sinh, "sqrt" : math.sqrt,
+                                 "tan"  : math.tan,  "tanh" : math.tanh}
+       try:
+            scrubbedquery = query.lower()
+            scrubbedquery = self.hexre.sub (self._hexsub, scrubbedquery)
+            scrubbedquery = self.binre.sub (self._binsub, scrubbedquery)
+            for (c1, c2) in (("[", "("), ("{", "("), ("]", ")"), ("}", ")")):
+                scrubbedquery = scrubbedquery.replace (c1, c2)
+
+            answer = eval (scrubbedquery, restricted_dictionary)
+
+            # Try and avoid echoing back simple numbers. Note that this
+            # doesn't work well for floating point, e.g. '3.' behaves badly.
+            if str (answer) == query:
+                return None
+            
+            # We need this check because the eval can return function objects
+            # when we are halfway through typing the expression.
+            if isinstance (answer, (float, int, long, str)):
+                return answer
+            else:
+                return None
+       except Exception, e:
+           LOGGER.debug (e.message)
+           return None
+    
+    def query (self, query):
+		answer = self.run_query(query)
+		if answer != None:
+			result = [CalculatorMatch (query, answer)]
+			self._emit_query_ready (query, result)
+			return answer
+		else:
+			return []
+

Added: trunk/deskbar/handlers/test_calculator.py
==============================================================================
--- (empty file)
+++ trunk/deskbar/handlers/test_calculator.py	Mon Jun 16 12:27:16 2008
@@ -0,0 +1,245 @@
+#!/usr/bin/env python
+#
+# test_calculator.py : Tests for the deskbar calculator handler
+#
+# Copyright (C) 2006 by Callum McKenzie
+#
+# Time-stamp: <2007-10-05 18:37:08 callum>
+#
+
+import unittest
+import re
+from math import pi
+import calculator
+
+class CalculatorBinTest (unittest.TestCase):
+    # Testing the bin function
+    def testBinZero (self):
+        self.assertEqual (calculator.bin (0), "0b0")
+    def testBinPositive (self):
+        self.assertEqual (calculator.bin (42), "0b101010")
+        self.assertEqual (calculator.bin (511), "0b111111111")
+    def testBinNegative (self):
+        self.assertEqual (calculator.bin (-2),
+                         "0b...1110")
+        self.assertEqual (calculator.bin (-1),
+                         "0b...111")
+    def testBinBigPositive (self):
+        self.assertEqual (calculator.bin (4294967296L),
+                          "0b100000000000000000000000000000000")
+        self.assertEqual (calculator.bin (11453246122L),
+                          "0b1010101010101010101010101010101010")
+    def testBinBigNegative (self):
+        self.assertEqual (calculator.bin (-4294967296L),
+                          "0b...11100000000000000000000000000000000")
+        self.assertEqual (calculator.bin (-11453246123L),
+                          "0b...1110101010101010101010101010101010101")
+    def testBinType (self):
+        self.assertRaises (TypeError, calculator.bin, "42")
+        self.assertRaises (TypeError, calculator.bin, 1.0)        
+
+class CalculatorModuleTest (unittest.TestCase):
+    def testHexRe (self):
+        h = calculator.CalculatorModule ()
+        self.failUnless (h.hexre.match ("0x12"))
+        self.failUnless (h.hexre.match ("0X12"))
+        self.failUnless (h.hexre.match ("0x0123456789abcdef"))
+        self.failUnless (h.hexre.match ("0x0123456789ABCDEF"))        
+        self.failUnless (h.hexre.match ("0xfedcba9876543210"))
+        self.failUnless (h.hexre.match ("0xFEDCBA9876543210"))        
+        self.failUnless (h.hexre.match ("0xfedc_ba98_7654_3210"))
+        self.failUnless (h.hexre.match ("0x_fedc_ba98_7654_3210"))
+        self.failIf (h.hexre.match ("0x_"))
+        self.failIf (h.hexre.match ("x1234"))
+        self.failIf (h.hexre.match ("cafebabe"))
+        self.failIf (h.hexre.match ("Purple"))
+
+    def testBinRe (self):
+        h = calculator.CalculatorModule ()
+        self.failUnless (h.binre.match ("0b10"))
+        self.failUnless (h.binre.match ("0B10"))
+        self.failUnless (h.binre.match ("0b01101100"))
+        self.failUnless (h.binre.match ("0b0110_1100"))
+        self.failUnless (h.binre.match ("0b_0110_1100"))
+        self.failUnless (h.binre.match ("0b11111111111111111111111111111111"))
+        self.failUnless (h.binre.match ("0b1111111111111111111111111111111111111111111111111111111111111111"))
+        self.failUnless (h.binre.match ("0b11111111111111111111111111111111111111111111111111111111111111111"))        
+        self.failUnless (h.binre.match ("0b1111111111_111111111111111__111111111111111111111___1111111111"))        
+        self.failIf (h.binre.match ("b1000"))
+        self.failIf (h.binre.match ("b1234"))        
+        self.failIf (h.binre.match ("1010101"))
+        self.failIf (h.binre.match ("Purple"))
+
+    def testHexSub (self):
+        h = calculator.CalculatorModule ()
+        match = h.hexre.match ("0x2f")
+        self.assertEqual (h._hexsub (match), '47')
+        match = h.hexre.match ("0xee789a45")
+        self.assertEqual (h._hexsub (match), '4000881221')
+        match = h.hexre.match ("0xee78_9a45")
+        self.assertEqual (h._hexsub (match), '4000881221')
+        match = h.hexre.match ("0x2f34897ef98d8922")
+        self.assertEqual (h._hexsub (match), '3401494797017254178')
+        match = h.hexre.match ("0x2f34897ef98d89226c")
+        self.assertEqual (h._hexsub (match), '870782668036417069676')
+
+    def testBinSub (self):
+        h = calculator.CalculatorModule ()
+        match = h.binre.match ("0b0")
+        self.assertEqual (h._binsub (match), "0")
+        match = h.binre.match ("0b101010")
+        self.assertEqual (h._binsub (match), "42")
+        match = h.binre.match ("0b11111111111111111111111111111111")
+        self.assertEqual (h._binsub (match), "4294967295")
+        match = h.binre.match ("0b100000000000000000000000000000001")
+        self.assertEqual (h._binsub (match), "4294967297")
+        match = h.binre.match ("0b_")
+        self.assertEqual (match, None)
+        match = h.binre.match ("0b101_010")
+        self.assertEqual (h._binsub (match), "42")
+        match = h.binre.match ("0b1111_1111_1111_1111_1111_1111_1111_1111")
+        self.assertEqual (h._binsub (match), "4294967295")
+        match = h.binre.match ("0b1____000000000000_00___000000000000000001")
+        self.assertEqual (h._binsub (match), "4294967297")
+
+    def testQuery (self):
+        """Generic tests of the query function that don't fit anywhere else."""
+        h = calculator.CalculatorModule ()
+        self.assertEqual (h.query ("print 'Hello World'"), [])
+        # Don't echo back simple identities ...
+        self.assertEqual (h.query ("1"), [])
+        # ... but do base conversions.
+        self.assertEqual (h.query ("0x2"), 2)
+        # This makes sure that hex, oct and bin always do the right
+        # thing in the presence of fractions.
+        self.assertEqual (h.query ("hex(0x5/2)"), "0x2")
+        self.assertEqual (h.query ("oct(05/2)"), "02")
+        self.assertEqual (h.query ("bin(0b101/2)"), "0b10")
+        # Make sure that we accept all brackets.
+        self.assertEqual (h.query ("abs(-1)"), 1)
+        self.assertEqual (h.query ("abs[-1]"), 1)
+        self.assertEqual (h.query ("abs{-1}"), 1)        
+        # Now test some "complex" equations to check that bracketing
+        # roks and try and provoke other issues.
+        self.assertEqual (h.query ("1 + 3*(2+3)"), 16)
+        self.assertEqual (h.query ("1 + 2*(2 - (3 - 2))"), 3)
+        self.assertAlmostEqual (h.query ("sqrt(sin(pi/2)**2 + cos(pi/2)**2)"), 1.0)
+        self.assertAlmostEqual (h.query ("sqrt(2)**sqrt(3)"), 1.8226346549)
+        self.assertAlmostEqual (h.query ("1/(2 + log(3)) + atan(3)"), 1.571770885)
+        
+    def testQueryOperators (self):
+        h = calculator.CalculatorModule ()
+        self.assertEqual (h.query ("1 + 1"), 2)
+        self.assertEqual (h.query ("2 - 4"), -2)
+        self.assertEqual (h.query ("3 * 7"), 21)
+        self.assertEqual (h.query ("35 / 5"), 7)
+        self.assertEqual (h.query ("35 / 2"), 17.5)        
+        self.assertEqual (h.query ("6 % 4"), 2)
+        self.assertEqual (h.query ("-6 % 4"), 2)        
+        self.assertEqual (h.query ("3 // 2"), 1)
+        self.assertEqual (h.query ("-3 // 2"), -2)
+        self.assertEqual (h.query ("3 << 3"), 24)
+        self.assertEqual (h.query ("1 << 65"), 36893488147419103232)
+        self.assertEqual (h.query ("8 >> 3"), 1)
+        self.assertEqual (h.query ("8 >> 4"), 0)
+        self.assertEqual (h.query ("-1 >> 3"), -1)
+        self.assertEqual (h.query ("7 & 3"), 3)
+        self.assertEqual (h.query ("-1 & 6"), 6)
+        self.assertEqual (h.query ("8 & 3"), 0)
+        self.assertEqual (h.query ("36893488147419103233 & 1"), 1)
+        self.assertEqual (h.query ("8 | 4"), 12)
+        self.assertEqual (h.query ("-1 | 5"), -1)
+        self.assertEqual (h.query ("7 | 3"), 7)
+        self.assertEqual (h.query ("36893488147419103232 | 1"),
+                          36893488147419103233)
+        self.assertEqual (h.query ("1 ^ 3"), 2)
+        self.assertEqual (h.query ("7 ^ 7"), 0)
+        self.assertEqual (h.query ("-1 ^ 3"), -4)
+        self.assertEqual (h.query ("36893488147419103233 ^ 1"),
+                          36893488147419103232)
+        self.assertEqual (h.query ("~3"), -4)
+        self.assertEqual (h.query ("~36893488147419103233"),
+                          -36893488147419103234)
+        self.assertEqual (h.query ("~-1"), 0)
+        self.assertEqual (h.query ("~-36893488147419103234"),
+                          36893488147419103233)
+        self.assertEqual (h.query ("~~5"), 5)
+        self.assertEqual (h.query ("~~-5"), -5)
+
+    def testQueryConversions (self):
+        h = calculator.CalculatorModule ()
+        # Start off with some simple stuff even though these have
+        # _theoretically_ been tested above.
+        self.assertEqual (h.query ("0x10"), 16)
+        self.assertEqual (h.query ("0xabcd"), 43981)
+        self.assertEqual (h.query ("0x2f34897ef98d89226c"),
+                          870782668036417069676)
+        self.assertEqual (h.query ("-0x10"), -16)
+
+        self.assertEqual (h.query ("0b101"), 5)
+        self.assertEqual (h.query ("-0b101"), -5)
+        self.assertEqual (h.query ("-0b100000000000000000000000000000001"),
+                          -4294967297)
+
+        # Octal hasn't been exercised above.
+        self.assertEqual (h.query ("0123"), 83)        
+        self.assertEqual (h.query ("01234567012345670123456701234567"),
+                          1616895878810725189668911479)
+        self.assertEqual (h.query ("-0123"), -83)        
+
+        # Floating point identities.
+        self.assertEqual (h.query ("0.12345 + 0"), 0.12345)
+        self.assertEqual (h.query ("-0.12345 + 0"), -0.12345)
+        self.assertEqual (h.query ("-0.12e5 + 0"), -0.12e5)
+        self.assertEqual (h.query ("0.12e-5 + 0"), 0.12e-5)        
+
+        # Now do the really stupid case.
+        self.assertEqual (h.query ("-10 + 0"), -10)
+        self.assertEqual (h.query ("12345678901234567890 + 0"),
+                          12345678901234567890)        
+        
+        # Test that this is counted as one hex literal rather than
+        # one hex literal followed by a binary literal.
+        self.assertEqual (h.query ("0x10b1"), 4273)
+
+    def testQueryFunctions (self):
+        h = calculator.CalculatorModule ()
+        # These aren't so much a test of cosine, as a test of
+        # general parsing prinicples.
+        self.assertAlmostEqual (h.query ("cos(0)"), 1.0, 3)
+        self.assertAlmostEqual (h.query ("cos(0.0)"), 1.0, 3)        
+        self.assertAlmostEqual (h.query ("cos (0)"), 1.0, 3)
+        self.assertAlmostEqual (h.query ("CoS(0)"), 1.0, 3)
+        # We require brackets for functions.
+        self.assertEqual (h.query ("cos 0"), [])
+        self.assertEqual (h.query ("cosine(0)"), [])        
+        # Now for the rest of the functions.
+        self.assertAlmostEqual (h.query ("asin(0.0)"), 0.0, 3)        
+        self.assertAlmostEqual (h.query ("acos(1.0)"), 0.0, 3)        
+        self.assertAlmostEqual (h.query ("atan2(1.0, 0.0)"), pi/2.0, 3)
+        self.assertAlmostEqual (h.query ("atan2(0.0, 1.0)"), 0.0, 3)
+        self.assertAlmostEqual (h.query ("atan2(-1.0, 1.0)"), -pi/4.0, 3)
+        self.assertAlmostEqual (h.query ("sinh(0.0)"), 0.0, 3)        
+        self.assertAlmostEqual (h.query ("cosh(0.0)"), 1.0, 3)        
+        self.assertAlmostEqual (h.query ("tanh(0.0)"), 0.0, 3)
+        self.assertAlmostEqual (h.query ("sin(0.0)"), 0.0, 3)
+        self.assertAlmostEqual (h.query ("cos(pi/2)"), 0.0, 3)
+        self.assertAlmostEqual (h.query ("tan(pi/4)"), 1.0, 3)
+        self.assertAlmostEqual (h.query ("abs(-1.0)"), 1.0, 3)
+        self.assertAlmostEqual (h.query ("sqrt(2.0)"), 1.41421, 3)
+        self.assertAlmostEqual (h.query ("pi"), 3.14159, 3)
+        self.assertAlmostEqual (h.query ("log10(345)"), 2.537819, 3)
+        self.assertAlmostEqual (h.query ("log(pi)"), 1.1447299, 3)
+        self.assertAlmostEqual (h.query ("exp(1.0)"), 2.718282, 3)
+        self.assertAlmostEqual (h.query ("radians(180)"), pi, 3)
+        self.assertAlmostEqual (h.query ("degrees(-pi/2)"), -90, 3)
+        self.assertAlmostEqual (h.query ("ceil(2.3)"), 3.0, 3)
+        self.assertAlmostEqual (h.query ("ceil(-2.3)"), -2.0, 3)
+        self.assertAlmostEqual (h.query ("floor(2.3)"), 2.0, 3)
+        self.assertAlmostEqual (h.query ("floor(-2.3)"), -3.0, 3)
+        self.assertAlmostEqual (h.query ("round(2.3)"), 2.0, 3)
+        self.assertAlmostEqual (h.query ("round(-2.3)"), -2.0, 3)
+        self.assertEqual (h.query ("int(2.3)"), 2)
+        self.assertEqual (h.query ("int(-2.3)"), -2)
+
+unittest.main ()



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]