1 #-------------------------------------------------------------------------------
3 # | ____| _| |_ / __ \ /\ | \/ |
4 # | |__ _ __ ___ ___ / \| | | | / \ | \ / |
5 # | __| '__/ _ \/ _ ( (| |) ) | | |/ /\ \ | |\/| |
6 # | | | | | __/ __/\_ _/| |__| / ____ \| | | |
7 # |_| |_| \___|\___| |_| \____/_/ \_\_| |_|
9 # FreeFOAM: The Cross-Platform CFD Toolkit
11 # Copyright (C) 2008-2012 Michael Wild <themiwi@users.sf.net>
12 # Gerber van der Graaf <gerber_graaf@users.sf.net>
13 #-------------------------------------------------------------------------------
15 # This file is part of FreeFOAM.
17 # FreeFOAM is free software: you can redistribute it and/or modify it
18 # under the terms of the GNU General Public License as published by the
19 # Free Software Foundation, either version 3 of the License, or (at your
20 # option) any later version.
22 # FreeFOAM is distributed in the hope that it will be useful, but WITHOUT
23 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
24 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
27 # You should have received a copy of the GNU General Public License
28 # along with FreeFOAM. If not, see <http://www.gnu.org/licenses/>.
31 # Useful Python functionality
33 #-------------------------------------------------------------------------------
35 """Classes and functions to manage FreeFOAM cases."""
37 from FreeFOAM
.compat
import *
45 class NoPolyMeshDirectoryError(Exception):
46 """Thrown if no polyMesh directory could be found"""
48 Exception.__init
__(self
)
51 return 'Failed to find a polyMesh directory'
54 """A simple class which writes both to a file and standard output.
56 It only implements the following functions from `file`:
63 def __init__(self
, fname
, *args
):
64 """Initializes a new instance of `Tee`.
68 fname : name of the file to write to
69 mode : file mode (as in `open()`)
70 buffering : buffering option (as in `open()`)
73 self
._file
= open(fname
, *args
)
74 self
._stdout
= _os
.fdopen(_os
.dup(_sys
.stdout
.fileno()), 'w')
76 def write(self
, string
):
77 """Write `string` to the file and standard output"""
78 self
._file
.write(string
)
79 self
._stdout
.write(string
)
81 def writelines(self
, strings
):
82 """Write `strings` to the file and standard output"""
83 self
._file
.writelines(strings
)
84 self
._stdout
.writelines(strings
)
87 """Flush the streams"""
92 """Return the handle of the file stream"""
93 return self
._file
.fileno()
95 def clone_case(old_case
, new_case
, verbose
=True):
96 """Clone a FreeFOAM case if the `new_case+'/system'` directory doesn't exist
101 old_case : The source case to be cloned.
102 new_case : The destination case to be created.
105 if not _op
.exists(_op
.join(new_case
, 'system')):
106 if verbose
: echo('Cloning case %s'%old
_case
)
107 for d
in ['0', 'system', 'constant', 'chemkin']:
108 if _op
.isdir(_op
.join(old_case
, d
)):
110 _op
.join(old_case
, d
),
111 _op
.join(new_case
, d
),
114 def remove_case(case
='.', verbose
=True):
115 """Removes a case directory"""
116 echo('Removing case %s'%case
)
119 def clean_case(case
='.', verbose
=True):
120 """Clean a case by removing generated files and directories"""
123 case
=_op
.abspath(case
)
124 if verbose
: echo('Cleaning case', case
)
125 patterns
= ['[1-9]*', '-[1-9]*', 'log', 'log.*', 'log-*', 'logSummary.*',
126 '.fxLock', '*.xml', 'ParaView*', 'paraFoam*', '*.OpenFOAM',
127 'processor*', 'probes*', 'forces*', 'system/machines', 'VTK']
128 for nZeros
in range(8):
129 timeDir
= '0.'+nZeros
*'0'+'[1-9]*'
131 patterns
.append(_op
.join(case
, p
+timeDir
))
132 for p
in ('allOwner cell face meshModifiers owner neighbour point edge'+
133 'cellLevel pointLevel refinementHistory surfaceIndex sets').split():
134 patterns
.append(_op
.join(case
, 'constant', 'polyMesh', p
+'*'))
135 for p
in 'cellToRegion cellLevel pointLevel'.split():
136 patterns
.append(_op
.join(case
, 'constant', p
+'*'))
138 for dd
in glob
.glob(_op
.join(case
, p
)):
141 def clean_samples(case
='.'):
142 """Remove sample directories"""
143 for d
in 'sets samples sampleSurfaces'.split():
146 def rmtree(path
, ignore_errors
=False, onerror
=None):
147 """Recursively delete all files/directories under `path`
148 (if `path` exists)"""
151 path
= _op
.abspath(path
)
153 shutil
.rmtree(path
, ignore_errors
, onerror
)
154 elif _op
.isfile(path
) or _op
.islink(path
):
157 def copytree(src
, dst
, symlinks
=True):
158 """Recursively copies `src` to `dst`. If `dst` exists,
159 it is removed first."""
161 src
= _op
.abspath(src
)
162 dst
= _op
.abspath(dst
)
164 parent
= _op
.dirname(dst
)
165 if not _op
.isdir(parent
):
168 shutil
.copytree(src
, dst
, symlinks
)
170 shutil
.copy2(src
, dst
)
172 def rename(src
, dst
):
173 """Forcebly renames `src` to `dst`. `dst` is removed first if it exists."""
175 src
= _op
.normpath(src
)
176 dst
= _op
.normpath(dst
)
179 shutil
.move(src
, dst
)
182 def clear_polymesh(case
=None, region
=None):
183 """Remove the contents of the constant/polyMesh directory as per the
184 Foam::polyMesh::removeFiles() method.
188 case : Case directory from which to clear the mesh
189 region : Mesh region to clear.
194 meshDir
= _op
.join(region
, meshDir
)
195 tmp
= _op
.join('constant', meshDir
)
197 meshDir
= _op
.join(case
, tmp
)
201 elif _op
.isdir(meshDir
):
202 # probably already in 'constant'
204 elif _op
.basename(_os
.getcwd()) == 'polyMesh':
205 # probably already in 'polyMesh'
206 meshDir
= _os
.getcwd()
208 if not _op
.isdir(meshDir
):
209 raise NoPolyMeshDirectoryError()
211 for f
in """points faces owner neighbour cells boundary pointZones faceZones
212 cellZones meshModifiers parallelData sets cellLevel pointLevel
213 refinementHistory surfaceIndex""".split():
214 f
= _op
.join(meshDir
, f
)
218 def make_short_path(path
, maxlen
=40):
219 """Abbreviate `path` to a maximum length of `maxlen` characters."""
220 path
= _op
.abspath(path
)
222 if len(path
) > maxlen
:
223 idx
= path
.find(_os
.sep
, first
)
226 path
= '...'+path
[idx
:]
229 class TerminalController
:
231 A class that can be used to portably generate formatted output to
234 `TerminalController` defines a set of instance variables whose
235 values are initialized to the control sequence necessary to
236 perform a given action. These can be simply included in normal
237 output to the terminal:
239 >>> term = TerminalController()
240 >>> print 'This is '+term.GREEN+'green'+term.NORMAL
242 Alternatively, the `render()` method can used, which replaces
243 '${action}' with the string required to perform 'action':
245 >>> term = TerminalController()
246 >>> print term.render('This is ${GREEN}green${NORMAL}')
248 If the terminal doesn't support a given action, then the value of
249 the corresponding instance variable will be set to ''. As a
250 result, the above code will still work on terminals that do not
251 support color, except that their output will not be colored.
252 Also, this means that you can test whether the terminal supports a
253 given action by simply testing the truth value of the
254 corresponding instance variable:
256 >>> term = TerminalController()
257 >>> if term.CLEAR_SCREEN:
258 ... print 'This terminal supports clearning the screen.'
260 Finally, if the width and height of the terminal are known, then
261 they will be stored in the `COLS` and `LINES` attributes.
263 Copied from http://code.activestate.com/recipes/475116-using-terminfo-for-portable-color-output-cursor-co
266 BOL
= '' #: Move the cursor to the beginning of the line
267 UP
= '' #: Move the cursor up one line
268 DOWN
= '' #: Move the cursor down one line
269 LEFT
= '' #: Move the cursor left one char
270 RIGHT
= '' #: Move the cursor right one char
273 CLEAR_SCREEN
= '' #: Clear the screen and move to home position
274 CLEAR_EOL
= '' #: Clear to the end of the line.
275 CLEAR_BOL
= '' #: Clear to the beginning of the line.
276 CLEAR_EOS
= '' #: Clear to the end of the screen
279 BOLD
= '' #: Turn on bold mode
280 BLINK
= '' #: Turn on blink mode
281 DIM
= '' #: Turn on half-bright mode
282 REVERSE
= '' #: Turn on reverse-video mode
283 NORMAL
= '' #: Turn off all modes
286 HIDE_CURSOR
= '' #: Make the cursor invisible
287 SHOW_CURSOR
= '' #: Make the cursor visible
290 COLS
= None #: Width of the terminal (None for unknown)
291 LINES
= None #: Height of the terminal (None for unknown)
294 BLACK
= BLUE
= GREEN
= CYAN
= RED
= MAGENTA
= YELLOW
= WHITE
= ''
297 BG_BLACK
= BG_BLUE
= BG_GREEN
= BG_CYAN
= ''
298 BG_RED
= BG_MAGENTA
= BG_YELLOW
= BG_WHITE
= ''
300 _STRING_CAPABILITIES
= """
301 BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1
302 CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold
303 BLINK=blink DIM=dim REVERSE=rev UNDERLINE=smul NORMAL=sgr0
304 HIDE_CURSOR=civis SHOW_CURSOR=cnorm""".split()
305 _COLORS
= """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split()
306 _ANSICOLORS
= "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split()
308 def __init__(self
, term_stream
=_sys
.stdout
):
310 Create a `TerminalController` and initialize its attributes
311 with appropriate values for the current terminal.
312 `term_stream` is the stream that will be used for terminal
313 output; if this stream is not a tty, then the terminal is
314 assumed to be a dumb terminal (i.e., have no capabilities).
316 # Curses isn't available on all platforms
320 # If the stream isn't a tty, then assume it has no capabilities.
321 if hasattr(_sys
.stdout
, 'isatty'):
322 if not term_stream
.isatty(): return
324 # Check the terminal type. If we fail, then assume that the
325 # terminal has no capabilities.
326 try: curses
.setupterm()
329 # Look up numeric capabilities.
330 self
.COLS
= curses
.tigetnum('cols')
331 self
.LINES
= curses
.tigetnum('lines')
333 # Look up string capabilities.
334 for capability
in self
._STRING
_CAPABILITIES
:
335 (attrib
, cap_name
) = capability
.split('=')
336 setattr(self
, attrib
, self
._tigetstr
(cap_name
) or '')
339 set_fg
= self
._tigetstr
('setf')
341 for i
,color
in enumerate(self
._COLORS
):
343 self
._tparm
(set_fg
, i
) or '')
344 set_fg_ansi
= self
._tigetstr
('setaf')
346 for i
,color
in enumerate(self
._ANSICOLORS
):
348 self
._tparm
(set_fg_ansi
, i
) or '')
349 set_bg
= self
._tigetstr
('setb')
351 for i
,color
in enumerate(self
._COLORS
):
352 setattr(self
, 'BG_'+color
,
353 self
._tparm
(set_bg
, i
) or '')
354 set_bg_ansi
= self
._tigetstr
('setab')
356 for i
,color
in enumerate(self
._ANSICOLORS
):
357 setattr(self
, 'BG_'+color
,
358 self
._tparm
(set_bg_ansi
, i
) or '')
360 def _tigetstr(self
, cap_name
):
361 # String capabilities can include "delays" of the form "$<2>".
362 # For any modern terminal, we should be able to just ignore
363 # these, so strip them out.
365 cap
= curses
.tigetstr(cap_name
)
370 return _re
.sub(r
'\$<\d+>[/*]?', '', cap
)
372 def _tparm(self
, parm
, i
):
374 res
= curses
.tparm(parm
, i
)
379 def render(self
, template
):
381 Replace each $-substitutions in the given template string with
382 the corresponding terminal control string (if it's defined) or
385 return _re
.sub(r
'\$\$|\${\w+}', self
._render
_sub
, template
)
387 def _render_sub(self
, match
):
389 if s
== '$$': return s
391 return getattr(self
, s
[2:-1])
393 def cecho(*args
, **kwargs
):
394 """print() replacement which does colored output if supported.
396 The following sequences will be replaced in the `args`:
397 * ${BOLD} : Turn on bold mode
398 * ${BLINK} : Turn on blink mode
399 * ${DIM} : Turn on half-bright mode
400 * ${REVERSE} : Turn on reverse-video mode
401 * ${NORMAL} : Turn off all modes
402 * ${<COLOR>} : Set text color to <COLOR>
403 * ${<BG_COLOR>} : Set background color to <BG_COLOR>
405 The available colors are:
415 The valid background colors are:
426 term
= TerminalController()
428 for i
, s
in enumerate(parts
):
430 parts
[i
] = term
.render(s
)
431 echo(*parts
, **kwargs
)
433 def cerror(*args
, **kwargs
):
434 """Print a message, preceded with a bold, red '*** Error***' flag"""
435 cecho("${RED}${BOLD}*** Error ***${NORMAL}", *args
, **kwargs
)
437 # ------------------------- vim: set sw=3 sts=3 et: --------------- end-of-file