-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathcommon.py
More file actions
782 lines (655 loc) · 26.6 KB
/
common.py
File metadata and controls
782 lines (655 loc) · 26.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
#!python
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
# License for the specific language governing rights and limitations
# under the License.
#
# The Original Code is Komodo code.
#
# The Initial Developer of the Original Code is ActiveState Software Inc.
# Portions created by ActiveState Software Inc are Copyright (C) 2000-2007
# ActiveState Software Inc. All Rights Reserved.
#
# Contributor(s):
# ActiveState Software Inc
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
"""Code Intelligence: common definitions"""
# Dev Notes:
# - XXX Need some good top-level logging control functions for this package.
# - XXX Rationalize exceptions.
# - XXX Coding style name changes.
from __future__ import absolute_import
__all__ = [
"Trigger", "Definition", "CILEDriver", "Evaluator",
"EvalController", "LogEvalController",
"canonicalizePath", "parseAttributes", "isUnsavedPath",
"TRG_FORM_CPLN", "TRG_FORM_CALLTIP", "TRG_FORM_DEFN",
"PRIORITY_CONTROL", "PRIORITY_IMMEDIATE", "PRIORITY_CURRENT",
"PRIORITY_OPEN", "PRIORITY_BACKGROUND",
"CodeIntelDeprecationWarning",
"CodeIntelError", "NotATriggerError", "EvalError", "EvalTimeout",
"VirtualMethodError", "CitadelError", "NoBufferAccessorError",
"CILEError", "CIXError", "CIDBError", "DatabaseError",
"CorruptDatabase", "NotFoundInDatabase", "CITDLError",
"NoModuleEntry", "NoCIDBModuleEntry",
"LazyClassAttribute",
"ENABLE_HEURISTICS",
"_xpcom_",
]
import os
from os.path import dirname, join, normpath, exists, basename
import sys
import re
import stat
import time
import threading
import logging
import warnings
try:
from zope.cachedescriptors.property import Lazy
class LazyClassAttribute(Lazy):
"""Lazy Class Attributes.
"""
def __get__(self, inst, class_):
if inst is None:
return self
if hasattr(self, '_value'):
return self._value
func, name = self.data
value = func(inst)
try:
class_.__dict__[name] = value
except Exception:
pass
self._value = value
return value
except ImportError:
import warnings
warnings.warn("Unable to import zope.cachedescriptors.property")
# Fallback to regular properties.
Lazy = property
LazyClassAttribute = property
import SilverCity
from SilverCity.Lexer import Lexer
from SilverCity import ScintillaConstants
if "CODEINTEL_NO_PYXPCOM" in os.environ:
_xpcom_ = False
else:
try:
from xpcom import components
from xpcom.server import UnwrapObject
_xpcom_ = True
except ImportError:
_xpcom_ = False
#XXX Should only do this hack for non-Komodo local codeintel usage.
#XXX We need to have a better mechanism for rationalizing and sharing
# common lexer style classes. For now we'll just HACKily grab from
# Komodo's styles.py. Some of this is duplicating logic in
# KoLanguageServiceBase.py.
_ko_src_dir = normpath(join(dirname(__file__), *([os.pardir]*3)))
sys.path.insert(0, join(_ko_src_dir, "schemes"))
try:
import styles
finally:
del sys.path[0]
del _ko_src_dir
#---- general codeintel pragmas
# Allow the CILEs to generate type guesses based on type names (e.g.
# "event" is an Event in JS).
ENABLE_HEURISTICS = True
#---- warnings
class CodeIntelDeprecationWarning(DeprecationWarning):
pass
# Here is how to disable these warnings in your code:
# import warnings
# from codeintel2.common import CodeIntelDeprecationWarning
# warnings.simplefilter("ignore", CodeIntelDeprecationWarning)
warnings.simplefilter("ignore", CodeIntelDeprecationWarning) # turn off for now
#---- exceptions
class CodeIntelError(Exception):
"""Base Code Intelligence system error."""
pass
Error = CodeIntelError #XXX Remove uses of this in favour of CodeIntelError.
class NotATriggerError(CodeIntelError):
pass
class EvalError(CodeIntelError):
pass
class EvalTimeout(EvalError):
pass
class VirtualMethodError(CodeIntelError):
#TODO: pull out the method and class name from the stack for errmsg
# tell user what needs to be implemented
pass
class CitadelError(CodeIntelError):
pass
class NoBufferAccessorError(CodeIntelError):
"""The accessor has no buffer/content to access."""
pass
class CILEError(CitadelError):
"""CILE processing error."""
#XXX Should add some relevant data to the exception. Perhaps
# the request should be passed in and this c'tor can extract
# data it wants to keep. This could be used to facilitate
# submitting bug reports on our Language Engines.
pass
class CIXError(CitadelError):
"""Code Intelligence XML error."""
pass
class CIDBError(CitadelError):
"""Code Intelligence Database error."""
#TODO: Transition to DatabaseError and ensure that the change in
# base class doesn't cause problems.
pass
class DatabaseError(CodeIntelError):
pass
class CorruptDatabase(DatabaseError):
"""Corruption in some part of the database was found."""
#XXX Should add attributes that indicate which part
# was corrupt and/or one of a known set of possible corrupts.
# Then add a Database.recover() function that could attempt
# to recover with that argument.
pass
class NotFoundInDatabase(DatabaseError):
"""No data for the buffer was found in the database."""
pass
class CITDLError(CitadelError): #XXX Just drop in favour of CitadelError?
"""CITDL syntax error."""
pass
class NoModuleEntry(CIDBError):
"""There is no entry for this module in the CIDB.
The "module_path" second constructor argument (possibly None) is required
to allow completion handling (which will be trapping these errors) to use
that path to kick off a scan for it. This shouldn't be a burden as the
import handlers that raise this will just have looked for this path.
"""
def __init__(self, module_name, module_path):
CIDBError.__init__(self)
self.module_name = module_name # the module name
self.module_path = module_path
def __str__(self):
path_info = ""
if self.module_path:
path_info = " (%s)" % os.path.basename(self.module_path)
return "no module entry for '%s'%s in CIDB"\
% (self.module_name, path_info)
class NoCIDBModuleEntry(CIDBError): #XXX change name to NoModuleEntryForPath
"""There is no module entry for the given path in the CIDB."""
def __init__(self, path):
CIDBError.__init__(self)
self.path = path
def __str__(self):
return "no module entry for '%s' in CIDB"\
% os.path.basename(self.path)
#---- globals
# Trigger forms - must be kept in sync with values in koICodeIntel.idl.
TRG_FORM_CPLN, TRG_FORM_CALLTIP, TRG_FORM_DEFN = range(3)
# Priorities at which scanning requests can be scheduled.
PRIORITY_CONTROL = 0 # Special sentinal priority to control scheduler
PRIORITY_IMMEDIATE = 1 # UI is requesting info on this file now
PRIORITY_CURRENT = 2 # UI requires info on this file soon
PRIORITY_OPEN = 3 # UI will likely require info on this file soon
PRIORITY_BACKGROUND = 4 # info may be needed sometime
#TODO: these are unused, drop them
# CIDB base type constants
BT_CLASSREF, BT_INTERFACEREF = range(2)
#TODO: These are unused, drop them, the symbolType2Name below and its dead
# usage in cb.py.
# CIDB symbol type constants
(ST_FUNCTION, ST_CLASS, ST_INTERFACE, ST_VARIABLE, ST_ARGUMENT) = range(5)
_symbolType2Name = {
ST_FUNCTION: "function",
ST_CLASS: "class",
ST_INTERFACE: "interface",
ST_VARIABLE: "variable",
ST_ARGUMENT: "argument"
}
#---- common codeintel base classes
class Trigger(object):
if _xpcom_:
_com_interfaces_ = [components.interfaces.koICodeIntelTrigger]
lang = None # e.g. "Python", "CSS"
form = None # TRG_FORM_CPLN or TRG_FORM_CALLTIP
type = None # e.g. "object-members"
pos = None # Trigger position, in bytes (of UTF 8)
implicit = None
# The number characters of the trigger. For most (but not all) triggers
# there is a clear distinction between a trigger token and a preceding
# context token. For example:
# foo.<|> # trigger token is '.', length = 1
# Foo::Bar-><|> # trigger token is '->', length = 2
# This default to 1.
length = None
# The number of characters after pos that should be replaced. Most of the
# time this will be zero. For example
# foo.<|>prop # extentLength is 4, for "prop"
# Note that this goes in the opposite direction of .length
extentLength = None
retriggerOnCompletion = False
def __init__(self, lang, form, type, pos, implicit, length=1, extentLength=0,
**extra):
self.lang = lang
self.form = form
self.type = type
self.pos = pos
self.implicit = implicit
self.length = length
self.extentLength = extentLength
self.extra = extra # Trigger-specific extra data, if any
@property
def id(self):
return (self.lang, self.form, self.type)
__name = None
@property
def name(self):
"""A more user-friendly name for this trigger, e.g.
'python-complete-object-members'
"""
if self.__name is None:
form_str = {TRG_FORM_CPLN: "complete",
TRG_FORM_DEFN: "defn",
TRG_FORM_CALLTIP: "calltip"}[self.form]
self.__name = "%s-%s-%s" % (self.lang.lower(), form_str,
self.type)
return self.__name
def __repr__(self):
explicit_str = (not self.implicit) and " (explicit)" or ""
return "<Trigger '%s' at %d%s>" % (self.name, self.pos, explicit_str)
def is_same(self, trg):
"""Return True iff the given trigger is (effectively) the same
as this one.
Dev Note: "Effective" is currently left a little fuzzy. Just
comparing enough to fix Komodo Bug 55378.
"""
if _xpcom_:
trg = UnwrapObject(trg)
if (self.pos == trg.pos
and self.type == trg.type
and self.form == trg.form
and self.lang == trg.lang):
return True
else:
return False
def to_dict(self):
"""Serialize this trigger as a dictionary
This is used for out-of-process codeintel
"""
return dict(lang=self.lang, form=self.form, type=self.type,
pos=self.pos, implicit=self.implicit, length = self.length,
extentLength=self.extentLength,
retriggerOnCompletion=self.retriggerOnCompletion,
**self.extra)
class Definition(object):
if _xpcom_:
_com_interfaces_ = [components.interfaces.koICodeIntelDefinition]
lang = None # e.g. "Python", "CSS"
path = None # e.g. "/usr/local/..."
blobname = None # e.g. "sys"
lpath = None # lookup tuple in blob, e.g. ["MyClass", "afunc"]
name = None # e.g. "path"
line = None # e.g. 345 (1-based)
ilk = None # e.g. "function"
citdl = None # e.g. "int"
signature = None # e.g. "function xyz(...)"
doc = None # e.g. "Xyz is just nasty stuff..."
attributes = None # e.g. "local private"
returns = None # e.g. "int"
scopestart = None # e.g. 320 (1-based)
scopeend = None # e.g. 355 (1-based)
def __init__(self, lang, path, blobname, lpath, name, line, ilk,
citdl, doc, signature=None, attributes=None,
returns=None, scopestart=None, scopeend=None):
self.lang = lang
self.path = path
self.blobname = blobname
self.lpath = lpath
self.name = name
self.line = line
self.ilk = ilk
self.citdl = citdl
self.doc = doc
self.signature = signature
self.attributes = attributes
self.returns = returns
self.scopestart = scopestart
self.scopeend = scopeend
def __repr__(self):
if self.path is None:
return "<Definition: %s '%s' at %s#%s lpath=%s>"\
% (self.ilk, self.name, self.blobname, self.line, self.lpath)
else:
return "<Definition: %s '%s' at %s#%s in %s lpath=%s>"\
% (self.ilk, self.name, self.blobname, self.line,
basename(self.path), self.lpath)
def equals(self, other):
""" Equality comparision for XPCOM """
if _xpcom_:
try:
other = UnwrapObject(other)
except:
pass
for attr in ("lang", "path", "blobname", "lpath", "name", "line", "ilk",
"citdl", "doc", "signature", "attributes", "returns"):
if getattr(self, attr) != getattr(other, attr):
return False
return True
def toString(self):
""" toString implementation for XPCOM """
return repr(self)
@classmethod
def unique_definitions(cls, defns):
"""Takes a collection of defns and returns the unique list of defns."""
unique_defns = []
for defn in defns:
for unique_defn in unique_defns:
if unique_defn.path == defn.path and unique_defn == defn:
# defn is already in the unique_defn list.
break
else:
unique_defns.append(defn)
return unique_defns
class CILEDriver(object):
"""Base class for all CILE drivers.
CILE stands for "CodeIntel Language Engine". A CILE is the thing that
knows how to convert content of a specific language to CIX (the XML data
loaded into the CIDB, then used for completion, code browsers, etc.)
A CILE *driver* is a class that implements this interface on top of a
language's CILE. A CILE might be a Python module, a separate executable,
whatever.
"""
def __init__(self, mgr):
self.mgr = mgr
#DEPRECATED
def scan(self, request):
"""Scan the given file and return data as a CIX document.
"request" is a ScanRequest instance.
This method MUST be re-entrant. The scheduler typically runs a pool
of scans simultaneously so individual drivers can be called into from
multiple threads.
If the scan was successful, returns a CIX document (XML). Note: the
return value should be unicode string, i.e. NOT an encoded byte
string -- encoding to UTF-8 is done as necessary elsewhere.
Raises a CILEError if there was a problem scanning. I.e. a driver
should be resistant to CILE hangs and crashes.
"""
raise VirtualMethodError("CILEDriver.scan")
def scan_purelang(self, buf):
"""Scan the given buffer and return a CIX element tree.
"buf" is an instance of this language's Buffer class.
"""
raise VirtualMethodError("CILEDriver.scan_purelang")
def scan_binary(self, buf):
"""Scan the given binary buffer and return a CIX element tree.
"buf" is an instance of this language's BinaryBuffer clas
"""
raise VirtualMethodError("CILEDriver.scan_binary")
def scan_multilang(self, buf, csl_cile_driver=None, css_cile_driver=None):
"""Scan the given multilang (UDL-based) buffer and return a CIX
element tree.
"buf" is the multi-lang UDLBuffer instance (e.g.
lang_rhtml.RHTMLBuffer for RHTML).
"csl_cile_driver" (optional) is the CSL (client-side language)
CILE driver. While scanning, CSL tokens should be gathered and,
if any, passed to the CSL scanner like this:
csl_cile_driver.scan_csl_tokens(
file_elem, blob_name, csl_tokens)
The CSL scanner will append a CIX <scope ilk="blob">
element to the <file> element.
"css_cile_driver" (optional) is the CSS (style language)
CILE driver. While scanning, CSS tokens should be gathered and,
if any, passed to the CSS scanner like above.
A language that supports being part of a multi-lang document
must implement this method.
"""
raise VirtualMethodError("CILEDriver.scan_multilang")
def scan_csl_tokens(self, file_elem, blob_name, csl_tokens):
"""Generate a CIX <scope ilk="blob"> tree for the given CSL
(client-side language) tokens and append the blob to the given
file element.
A language that supports being a client-side language in a
multi-lang document must implement this method. Realistically
this just means JavaScript for now, but could eventually include
Python for the new Mozilla DOM_AGNOSTIC work.
"""
raise VirtualMethodError("CILEDriver.scan_csl_tokens")
def scan_css_tokens(self, file_elem, blob_name, css_tokens):
"""Generate a CIX <scope ilk="blob"> tree for the given CSS
(style language) tokens and append the blob to the given
file element.
A language that supports being a style language in a
multi-lang document must implement this method. Realistically
this just means CSS for now.
"""
raise VirtualMethodError("CILEDriver.scan_css_tokens")
class EvalController(object):
"""A class for interaction with an asynchronous evaluation of completions
or calltips. Typically for "interesting" interaction on would subclass
this and pass an instance of that class to Buffer.async_eval_at_trg().
"""
def __init__(self):
self.complete_event = threading.Event() # use a pool?
self._done = False
self._aborted = False
self.buf = None
self.trg = None
self.cplns = None
self.calltips = None
self.defns = None
self.desc = None
self.keep_existing = False
def close(self):
"""Done with this eval controller, clear any references"""
pass
def start(self, buf, trg):
"""Called by the evaluation engine to indicate the beginning of
evaluation and to pass in data the controller might need.
"""
self.buf = buf
self.trg = trg
def set_desc(self, desc):
self.desc = desc
def done(self, reason):
"""Called by the evaluation engine to indicate completion handling
has finished."""
self.info("done eval: %s", reason)
self._done = True
self.buf = None
self.trg = None
self.complete_event.set()
def is_done(self):
return self._done
def abort(self):
"""Signal to completion handling system to abort the current
completion session.
"""
self._aborted = True
def is_aborted(self):
return self._aborted
def wait(self, timeout=None):
"""Block until this completion session is done or
until the timeout is reached.
"""
self.complete_event.wait(timeout)
def debug(self, msg, *args): pass
def info(self, msg, *args): pass
def warn(self, msg, *args): pass
def error(self, msg, *args): pass
#XXX Perhaps this capturing should be in a sub-class used only for
# testing. Normal IDE behaviour is to fwd the data in set_*().
def set_cplns(self, cplns):
self.cplns = cplns
def set_calltips(self, calltips):
self.calltips = calltips
def set_defns(self, defns):
self.defns = defns
class LogEvalController(EvalController):
def __init__(self, logger_or_log_name=None):
if isinstance(logger_or_log_name, logging.getLoggerClass()):
self.logger = logger_or_log_name
else:
self.logger = logging.getLogger(logger_or_log_name)
EvalController.__init__(self)
def debug(self, msg, *args):
self.logger.debug(msg, *args)
def info(self, msg, *args):
self.logger.info(msg, *args)
def warn(self, msg, *args):
self.logger.warn(msg, *args)
def error(self, msg, *args):
self.logger.error(msg, *args)
class Evaluator(object):
"""To do asynchronous autocomplete/calltip evaluation you create an
Evaluator instance (generally a specialized subclass of) and pass it
to Manager.request_eval() and/or Manager.request_reeval().
At a minimum a subclass must implement the eval() method making sure
that the rules described for Buffer.async_eval_at_trg() are followed
(see buffer.py). Typically this just means:
- ensuring ctlr.done() is called,
- reacting to ctlr.is_aborted(), and
- optionally calling the other EvalController methods as appropriate.
A subclass should also implement readable __str__ output.
The manager handles:
- co-ordinating a queue of evaluation requests
- only ever running one evaluation at a time (because it only makes sense
in an IDE to have one on the go)
- calling the evaluator's eval() method in a subthread
- calling ctlr.done(<reason>) if the eval terminates with an exception
One important base class is the CitadelEvaluator (see citadel.py) that
knows how to do CITDL evaluation using the CIDB. Citadel languages
(e.g. Perl, Python, ...) will generally use CitadelEvaluators for most
of their triggers.
"""
def __init__(self, ctlr, buf, trg):
assert isinstance(ctlr, EvalController)
self.ctlr = ctlr
#assert isinstance(buf, Buffer) # commented out to avoid circular dep
self.buf = buf
assert isinstance(trg, Trigger)
self.trg = trg
def eval(self):
self.ctlr.done("eval not implemented")
raise VirtualMethodError("Evaluator.eval")
def close(self):
"""Done with this evaluator, clear any references"""
if self.ctlr is not None:
self.ctlr.close()
#---- helper methods
#TODO: drop this (see note above)
def symbolType2Name(st):
return _symbolType2Name[st]
#TODO: drop this, see similar func in parseutil.py
def xmlattrstr(attrs):
"""Construct an XML-safe attribute string from the given attributes
"attrs" is a dictionary of attributes
The returned attribute string includes a leading space, if necessary,
so it is safe to use the string right after a tag name.
"""
#XXX Should this be using
from xml.sax.saxutils import quoteattr
s = ''
names = list(attrs.keys())
names.sort() # dump attrs sorted by key, not necessary but is more stable
for name in names:
s += ' %s=%s' % (name, quoteattr(str(attrs[name])))
return s
def isUnsavedPath(path):
"""Return true if the given path is a special <Unsaved>\sub\path file."""
tag = "<Unsaved>"
length = len(tag)
if path.startswith(tag) and (len(path)==length or path[length] in "\\/"):
return True
else:
return False
#TODO: move this utils.py
_uriMatch = re.compile("^\w+://")
def canonicalizePath(path, normcase=True):
r"""Return what CodeIntel considers a canonical version of the given path.
"path" is the path to canonicalize.
"normcase" (optional, default True) is a boolean indicating if the
case should be normalized.
"Special" paths are ones of the form "<Tag>\sub\path". Supported special
path tags:
<Unsaved> Used when the given path isn't a real file: e.g.
unsaved document buffers.
Raises a ValueError if it cannot be converted to a canonical path.
>>> canonicalizePath(r"C:\Python22\Lib\os.py") # normcase on Windows
'c:\\python22\\lib\\os.py'
>>> canonicalizePath(r"<Unsaved>\Python-1.py")
'<Unsaved>\\python-1.py'
>>> canonicalizePath("<Unsaved>")
'<Unsaved>'
>>> canonicalizePath("<Unsaved>\\")
'<Unsaved>'
>>> canonicalizePath("ftp://ftp.ActiveState.com/pub")
'ftp://ftp.ActiveState.com/pub'
"""
if path is None:
raise ValueError("cannot canonicalize path, path is None")
if path.startswith('<'): # might be a special path
first, rest = None, None
for i in range(1, len(path)):
if path[i] in "\\/":
first, rest = path[:i], path[i+1:]
break
else:
first, rest = path, None
if first.endswith('>'):
tag = first
subpath = rest
if tag == "<Unsaved>":
pass # leave tag unchanged
else:
raise ValueError("unknown special path tag: %s" % tag)
cpath = tag
if subpath:
subpath = os.path.normpath(subpath)
if normcase:
subpath = os.path.normcase(subpath)
cpath = os.path.join(cpath, subpath)
return cpath
if _uriMatch.match(path): # ftp://, koremote://
#XXX Should we normcase() a UR[LI]
return path
else:
cpath = os.path.normpath(os.path.abspath(path))
if normcase:
cpath = os.path.normcase(cpath)
return cpath
#TODO: move this utils.py
def parseAttributes(attrStr=None):
"""Parse the given attributes string (from CIX) into an attribute dict."""
attrs = {}
if attrStr is not None:
for token in attrStr.split():
if '=' in token:
key, value = token.split('=', 1)
else:
key, value = token, 1
attrs[key] = value
return attrs
#---- self-test code
if __name__ == '__main__':
def _test():
import doctest, common
return doctest.testmod(common)
_test()