@@ -87,8 +87,10 @@ def __call__(cls, *args, **kwds):
87
87
else :
88
88
return type .__call__ (cls , * args , ** kwds )
89
89
90
+
90
91
_trivial_unique_representation_cache = dict ()
91
92
93
+
92
94
class TrivialUniqueRepresentation (metaclass = TrivialClasscallMetaClass ):
93
95
r"""
94
96
A trivial version of :class:`UniqueRepresentation` without Cython dependencies.
@@ -105,6 +107,7 @@ def __classcall__(cls, *args, **options):
105
107
cached = _trivial_unique_representation_cache [key ] = type .__call__ (cls , * args , ** options )
106
108
return cached
107
109
110
+
108
111
class Feature (TrivialUniqueRepresentation ):
109
112
r"""
110
113
A feature of the runtime environment
@@ -150,6 +153,7 @@ def __init__(self, name, spkg=None, url=None, description=None):
150
153
151
154
self ._cache_is_present = None
152
155
self ._cache_resolution = None
156
+ self ._hidden = False
153
157
154
158
def is_present (self ):
155
159
r"""
@@ -186,6 +190,8 @@ def is_present(self):
186
190
sage: TestFeature("other").is_present()
187
191
FeatureTestResult('other', True)
188
192
"""
193
+ if self ._hidden :
194
+ return FeatureTestResult (self , False , reason = "Feature `{name}` is hidden." .format (name = self .name ))
189
195
# We do not use @cached_method here because we wish to use
190
196
# Feature early in the build system of sagelib.
191
197
if self ._cache_is_present is None :
@@ -238,6 +244,27 @@ def __repr__(self):
238
244
description = f'{ self .name !r} : { self .description } ' if self .description else f'{ self .name !r} '
239
245
return f'Feature({ description } )'
240
246
247
+ def _spkg_type (self ):
248
+ r"""
249
+ Return the type of the SPKG corresponding to this feature.
250
+
251
+ EXAMPLES::
252
+
253
+ sage: from sage.features.databases import DatabaseCremona
254
+ sage: DatabaseCremona()._spkg_type()
255
+ 'optional'
256
+
257
+ OUTPUT:
258
+
259
+ The type as a string in ``('base', 'standard', 'optional', 'experimental')``.
260
+ If no SPKG corresponds to this feature ``None`` is returned.
261
+ """
262
+ from sage .misc .package import _spkg_type
263
+ spkg = self .spkg
264
+ if not spkg :
265
+ spkg = self .name
266
+ return _spkg_type (spkg )
267
+
241
268
def resolution (self ):
242
269
r"""
243
270
Return a suggestion on how to make :meth:`is_present` pass if it did not
@@ -253,6 +280,8 @@ def resolution(self):
253
280
sage: Executable(name="CSDP", spkg="csdp", executable="theta", url="https://github.com/dimpase/csdp").resolution() # optional - sage_spkg
254
281
'...To install CSDP...you can try to run...sage -i csdp...Further installation instructions might be available at https://github.com/dimpase/csdp.'
255
282
"""
283
+ if self ._hidden :
284
+ return "Use method `unhide` to make it available again."
256
285
if self ._cache_resolution is not None :
257
286
return self ._cache_resolution
258
287
lines = []
@@ -264,6 +293,109 @@ def resolution(self):
264
293
self ._cache_resolution = "\n " .join (lines )
265
294
return self ._cache_resolution
266
295
296
+ def joined_features (self ):
297
+ r"""
298
+ Return a list of features joined with ``self``.
299
+
300
+ OUTPUT:
301
+
302
+ A (possibly empty) list of instances of :class:`Feature`.
303
+
304
+ EXAMPLES::
305
+
306
+ sage: from sage.features.graphviz import Graphviz
307
+ sage: Graphviz().joined_features()
308
+ [Feature('dot'), Feature('neato'), Feature('twopi')]
309
+ sage: from sage.features.interfaces import Mathematica
310
+ sage: Mathematica().joined_features()
311
+ []
312
+ """
313
+ from sage .features .join_feature import JoinFeature
314
+ if isinstance (self , JoinFeature ):
315
+ return self ._features
316
+ return []
317
+
318
+ def is_standard (self ):
319
+ r"""
320
+ Return whether this feature corresponds to a standard SPKG.
321
+
322
+ EXAMPLES::
323
+
324
+ sage: from sage.features.databases import DatabaseCremona, DatabaseConwayPolynomials
325
+ sage: DatabaseCremona().is_standard()
326
+ False
327
+ sage: DatabaseConwayPolynomials().is_standard()
328
+ True
329
+ """
330
+ if self .name .startswith ('sage.' ):
331
+ return True
332
+ return self ._spkg_type () == 'standard'
333
+
334
+ def is_optional (self ):
335
+ r"""
336
+ Return whether this feature corresponds to an optional SPKG.
337
+
338
+ EXAMPLES::
339
+
340
+ sage: from sage.features.databases import DatabaseCremona, DatabaseConwayPolynomials
341
+ sage: DatabaseCremona().is_optional()
342
+ True
343
+ sage: DatabaseConwayPolynomials().is_optional()
344
+ False
345
+ """
346
+ return self ._spkg_type () == 'optional'
347
+
348
+ def hide (self ):
349
+ r"""
350
+ Hide this feature. For example this is used when the doctest option
351
+ ``--hide``is set. Setting an installed feature as hidden pretends
352
+ that it is not available. To revert this use :meth:`unhide`.
353
+
354
+ EXAMPLES:
355
+
356
+ Benzene is an optional SPKG. The following test fails if it is hidden or
357
+ not installed. Thus, in the second invocation the optional tag is needed::
358
+
359
+ sage: from sage.features.graph_generators import Benzene
360
+ sage: Benzene().hide()
361
+ sage: len(list(graphs.fusenes(2)))
362
+ Traceback (most recent call last):
363
+ ...
364
+ FeatureNotPresentError: benzene is not available.
365
+ Feature `benzene` is hidden.
366
+ Use method `unhide` to make it available again.
367
+
368
+ sage: Benzene().unhide()
369
+ sage: len(list(graphs.fusenes(2))) # optional benzene
370
+ 1
371
+ """
372
+ self ._hidden = True
373
+
374
+ def unhide (self ):
375
+ r"""
376
+ Revert what :meth:`hide` does.
377
+
378
+ EXAMPLES:
379
+
380
+ Polycyclic is a standard GAP package since 4.10 (see :trac:`26856`). The
381
+ following test just fails if it is hidden. Thus, in the second
382
+ invocation no optional tag is needed::
383
+
384
+ sage: from sage.features.gap import GapPackage
385
+ sage: Polycyclic = GapPackage("polycyclic", spkg="gap_packages")
386
+ sage: Polycyclic.hide()
387
+ sage: libgap(AbelianGroup(3, [0,3,4], names="abc"))
388
+ Traceback (most recent call last):
389
+ ...
390
+ FeatureNotPresentError: gap_package_polycyclic is not available.
391
+ Feature `gap_package_polycyclic` is hidden.
392
+ Use method `unhide` to make it available again.
393
+
394
+ sage: Polycyclic.unhide()
395
+ sage: libgap(AbelianGroup(3, [0,3,4], names="abc"))
396
+ Pcp-group with orders [ 0, 3, 4 ]
397
+ """
398
+ self ._hidden = False
267
399
268
400
class FeatureNotPresentError (RuntimeError ):
269
401
r"""
@@ -682,7 +814,9 @@ def absolute_filename(self) -> str:
682
814
A :class:`FeatureNotPresentError` is raised if the file cannot be found::
683
815
684
816
sage: from sage.features import StaticFile
685
- sage: StaticFile(name="no_such_file", filename="KaT1aihu", search_path=(), spkg="some_spkg", url="http://rand.om").absolute_filename() # optional - sage_spkg
817
+ sage: StaticFile(name="no_such_file", filename="KaT1aihu",\
818
+ search_path=(), spkg="some_spkg",\
819
+ url="http://rand.om").absolute_filename() # optional - sage_spkg
686
820
Traceback (most recent call last):
687
821
...
688
822
FeatureNotPresentError: no_such_file is not available.
@@ -694,9 +828,8 @@ def absolute_filename(self) -> str:
694
828
path = os .path .join (directory , self .filename )
695
829
if os .path .isfile (path ) or os .path .isdir (path ):
696
830
return os .path .abspath (path )
697
- raise FeatureNotPresentError (self ,
698
- reason = "{filename!r} not found in any of {search_path}" .format (filename = self .filename , search_path = self .search_path ),
699
- resolution = self .resolution ())
831
+ reason = "{filename!r} not found in any of {search_path}" .format (filename = self .filename , search_path = self .search_path )
832
+ raise FeatureNotPresentError (self , reason = reason , resolution = self .resolution ())
700
833
701
834
702
835
class CythonFeature (Feature ):
0 commit comments