Repository: ipython/ipython

Issue:
styling prompt, {color.Normal} excepts
version: ipython 0.12

With my prompt set to:
`c.PromptManager.in_template  = '{color.LightGreen} \T {color.Normal}\Y2 {color.LightGreen}\$'`
I get an `AttributeError: NoColors instance has no attribute 'Normal'`

But I checked:

```
>>> from IPython.utils.coloransi import InputTermColors
>>> InputTermColors.Normal  # color has Normal, which is supposed to reset
'\x01\x1b[0m\x02'
```



Discussion:

Resolving patch:
diff --git a/IPython/core/prompts.py b/IPython/core/prompts.py
--- a/IPython/core/prompts.py
+++ b/IPython/core/prompts.py
@@ -30,7 +30,7 @@
 
 from IPython.config.configurable import Configurable
 from IPython.core import release
-from IPython.utils import coloransi
+from IPython.utils import coloransi, py3compat
 from IPython.utils.traitlets import (Unicode, Instance, Dict, Bool, Int)
 
 #-----------------------------------------------------------------------------
@@ -96,6 +96,12 @@ def __call__(self, **kwargs):
     
     def __str__(self):
         return str(self())
+    
+    def __unicode__(self):
+        return unicode(self())
+    
+    def __format__(self, format_spec):
+        return format(self(), format_spec)
 
 def multiple_replace(dict, text):
     """ Replace in 'text' all occurences of any key in the given
@@ -129,13 +135,13 @@ def multiple_replace(dict, text):
 # - I also need to split up the color schemes from the prompt specials
 # somehow.  I don't have a clean design for that quite yet.
 
-HOME = os.environ.get("HOME","//////:::::ZZZZZ,,,~~~")
+HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~"))
 
 # We precompute a few more strings here for the prompt_specials, which are
 # fixed once ipython starts.  This reduces the runtime overhead of computing
 # prompt strings.
-USER           = os.environ.get("USER")
-HOSTNAME       = socket.gethostname()
+USER           = py3compat.str_to_unicode(os.environ.get("USER",''))
+HOSTNAME       = py3compat.str_to_unicode(socket.gethostname())
 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
 ROOT_SYMBOL    = "#" if (os.name=='nt' or os.getuid()==0) else "$"
 
@@ -202,7 +208,7 @@ def cwd_filt(depth):
     $HOME is always replaced with '~'.
     If depth==0, the full path is returned."""
 
-    cwd = os.getcwd().replace(HOME,"~")
+    cwd = os.getcwdu().replace(HOME,"~")
     out = os.sep.join(cwd.split(os.sep)[-depth:])
     return out or os.sep
 
@@ -212,7 +218,7 @@ def cwd_filt2(depth):
     $HOME is always replaced with '~'.
     If depth==0, the full path is returned."""
 
-    full_cwd = os.getcwd()
+    full_cwd = os.getcwdu()
     cwd = full_cwd.replace(HOME,"~").split(os.sep)
     if '~' in cwd and len(cwd) == depth+1:
         depth += 1
@@ -228,9 +234,9 @@ def cwd_filt2(depth):
 #-----------------------------------------------------------------------------
 
 lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"),
-                   'cwd': LazyEvaluate(os.getcwd),
-                   'cwd_last': LazyEvaluate(lambda: os.getcwd().split(os.sep)[-1]),
-                   'cwd_x': [LazyEvaluate(lambda: os.getcwd().replace("%s","~"))] +\
+                   'cwd': LazyEvaluate(os.getcwdu),
+                   'cwd_last': LazyEvaluate(lambda: os.getcwdu().split(os.sep)[-1]),
+                   'cwd_x': [LazyEvaluate(lambda: os.getcwdu().replace(HOME,"~"))] +\
                             [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
                    'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
                    }
diff --git a/IPython/frontend/terminal/interactiveshell.py b/IPython/frontend/terminal/interactiveshell.py
--- a/IPython/frontend/terminal/interactiveshell.py
+++ b/IPython/frontend/terminal/interactiveshell.py
@@ -565,12 +565,15 @@ def raw_input(self, prompt=''):
 
         if self.has_readline:
             self.set_readline_completer()
+        
+        # raw_input expects str, but we pass it unicode sometimes
+        prompt = py3compat.cast_bytes_py2(prompt)
 
         try:
             line = py3compat.str_to_unicode(self.raw_input_original(prompt))
         except ValueError:
             warn("\n********\nYou or a %run:ed script called sys.stdin.close()"
-                 " or sys.stdout.close()!\nExiting IPython!")
+                 " or sys.stdout.close()!\nExiting IPython!\n")
             self.ask_exit()
             return ""
 
diff --git a/IPython/utils/coloransi.py b/IPython/utils/coloransi.py
--- a/IPython/utils/coloransi.py
+++ b/IPython/utils/coloransi.py
@@ -102,6 +102,7 @@ class NoColors:
     """This defines all the same names as the colour classes, but maps them to
     empty strings, so it can easily be substituted to turn off colours."""
     NoColor = ''
+    Normal  = ''
 
 for name, value in color_templates:
     setattr(NoColors, name, '')


Test patch:
diff --git a/IPython/core/tests/test_prompts.py b/IPython/core/tests/test_prompts.py
--- a/IPython/core/tests/test_prompts.py
+++ b/IPython/core/tests/test_prompts.py
@@ -1,12 +1,16 @@
+# -*- coding: utf-8
 """Tests for prompt generation."""
 
 import unittest
 
+import os
 import nose.tools as nt
 
 from IPython.testing import tools as tt, decorators as dec
-from IPython.core.prompts import PromptManager
+from IPython.core.prompts import PromptManager, LazyEvaluate
 from IPython.testing.globalipapp import get_ipython
+from IPython.utils import py3compat
+from IPython.utils.tempdir import TemporaryDirectory
 
 ip = get_ipython()
 
@@ -60,3 +64,47 @@ def test_undefined(self):
     def test_render(self):
         self.pm.in_template = r'\#>'
         self.assertEqual(self.pm.render('in',color=False), '%d>' % ip.execution_count)
+    
+    def test_render_unicode_cwd(self):
+        save = os.getcwdu()
+        with TemporaryDirectory(u'ünicødé') as td:
+            os.chdir(td)
+            self.pm.in_template = r'\w [\#]'
+            p = self.pm.render('in', color=False)
+            self.assertEquals(p, u"%s [%i]" % (os.getcwdu(), ip.execution_count))
+        os.chdir(save)
+    
+    def test_lazy_eval_unicode(self):
+        u = u'ünicødé'
+        lz = LazyEvaluate(lambda : u)
+        # str(lz) would fail
+        self.assertEquals(unicode(lz), u)
+        self.assertEquals(format(lz), u)
+    
+    def test_lazy_eval_nonascii_bytes(self):
+        u = u'ünicødé'
+        b = u.encode('utf8')
+        lz = LazyEvaluate(lambda : b)
+        # unicode(lz) would fail
+        self.assertEquals(str(lz), str(b))
+        self.assertEquals(format(lz), str(b))
+    
+    def test_lazy_eval_float(self):
+        f = 0.503
+        lz = LazyEvaluate(lambda : f)
+        
+        self.assertEquals(str(lz), str(f))
+        self.assertEquals(unicode(lz), unicode(f))
+        self.assertEquals(format(lz), str(f))
+        self.assertEquals(format(lz, '.1'), '0.5')
+    
+    def test_cwd_x(self):
+        self.pm.in_template = r"\X0"
+        save = os.getcwdu()
+        os.chdir(os.path.expanduser('~'))
+        p = self.pm.render('in', color=False)
+        try:
+            self.assertEquals(p, '~')
+        finally:
+            os.chdir(save)
+