Module tf.advanced.links
Produce links to Text-Fabric data and links from nodes to web resources.
Expand source code Browse git
"""
Produce links to Text-Fabric data and links from nodes to web resources.
"""
import os
import re
import types
from textwrap import dedent
from ..parameters import (
backendRep,
URL_TFDOC,
SEARCHREF,
APIREF,
APP_APP,
GH,
GL,
)
from ..core.helpers import htmlEsc, unexpanduser as ux
from .repo import Checkout
from .helpers import dh
from ..server.wrap import wrapProvenance
UNSUPPORTED = ""
def linksApi(app, silent):
"""Produce the link API.
The link API provides methods to maps nodes to urls of web resources.
It also computes several provenance and documentation links from the
configuration settings of the corpus.
If a single volume has been loaded, there will be added a provenance notice
to the provenance data of the work as a whole,
essentially stating which volume from the work is being used and what top-level
sections of the work are part of it.
Parameters
----------
app: obj
The high-level API object
silent:
The verbosity mode to perform this operation in.
Normally it is the same as for the app, but when we do an `A.reuse()`
we force `silent=True`.
"""
backend = app.backend
app.showProvenance = types.MethodType(showProvenance, app)
app.header = types.MethodType(header, app)
app.webLink = types.MethodType(webLink, app)
isCompatible = app.isCompatible
aContext = app.context
appName = aContext.appName
appRelative = aContext.relative
appPath = aContext.appPath
apiVersion = aContext.apiVersion
docUrl = aContext.docUrl
org = aContext.org or ""
repo = aContext.repo or ""
version = aContext.version
corpus = aContext.corpus
featureBase = aContext.featureBase
featurePage = aContext.featurePage
charUrl = aContext.charUrl
charText = aContext.charText
tutUrl = (
f"{backendRep(backend, 'urlnb')}/"
f"{org}/{repo}/blob/master/tutorial/start.ipynb"
)
apiVersionRep = "" if apiVersion is None else f" v{apiVersion}"
dataName = repo.upper()
collectionInfo = app.collectionInfo
if collectionInfo:
dataName += f" collection {collectionInfo}"
else:
volumeInfo = app.volumeInfo
if volumeInfo:
dataName += f" volume {volumeInfo}"
dataLink = (
outLink(dataName, docUrl, f"provenance of {corpus}")
if isCompatible and repo is not None and docUrl
else "/".join(
(x or "") for x in (appPath.rsplit("/", 1)[0], appRelative, version)
)
if appName.startswith("app:")
else "/".join((x or "") for x in (appPath, appName, version))
)
charLink = (
(
outLink("Character table", charUrl.format(tfDoc=URL_TFDOC), charText)
if isCompatible
else UNSUPPORTED
)
if charUrl
else ""
)
featureLink = (
(
outLink(
"Feature docs",
featureBase.replace("<feature>", featurePage).format(version=version),
f"{repo.upper()} feature documentation",
)
if isCompatible and repo is not None and featureBase
else UNSUPPORTED
)
if isCompatible
else UNSUPPORTED
)
extraUrl = f"{backendRep(backend, 'url')}/{org}/{repo}/blob/master/{APP_APP}"
appLink = (
outLink(f"{org}/{repo}/app {apiVersionRep}", extraUrl, f"{appName} TF-app")
if isCompatible and repo is not None
else "no app configured"
)
tfLink = outLink(
f"Text-Fabric API {app.TF.version}",
APIREF,
"text-fabric-api",
)
tfsLink = (
outLink(
"Search Reference",
SEARCHREF,
"Search Templates Introduction and Reference",
)
if isCompatible
else UNSUPPORTED
)
tutLink = (
outLink("App tutorial", tutUrl, "App tutorial in Jupyter Notebook")
if isCompatible and repo is not None
else UNSUPPORTED
)
app.appLink = appLink
app.dataLink = dataLink
app.charLink = charLink
app.featureLink = featureLink
app.tfLink = tfLink
app.tfsLink = tfsLink
app.tutLink = tutLink
if not app._browse:
if not silent:
header(app)
def header(app):
"""Generate a colofon of the TF-app.
This colofon will be displayed after initializing the advanced API,
and it is packed with provenance and documentation links.
"""
appLink = app.appLink
dataLink = app.dataLink
charLink = app.charLink
featureLink = app.featureLink
tfsLink = app.tfsLink
tfLink = app.tfLink
tutLink = app.tutLink
if app._browse:
return (
f"""\
<div class="hdlinks">
{dataLink}
{charLink}
{featureLink}
{tfsLink}
{tutLink}
</div>\
""",
'<img class="hdlogo" src="/data/static/logo.png"/>',
'<img class="hdlogo" src="/server/static/icon.png"/>',
)
else:
tfLine = ", ".join(x for x in (tfLink, appLink, tfsLink) if x)
dataLine = ", ".join(x for x in (dataLink, charLink, featureLink) if x)
dh(
f"<b>Text-Fabric:</b> {tfLine}<br>"
f"<b>Data:</b> {dataLine}<br>"
"<b>Features:</b><br>" + _featuresPerModule(app)
)
def webLink(
app, n, text=None, clsName=None, urlOnly=False, _asString=False, _noUrl=False
):
"""Maps a node to a web resource.
Usually called as `A.webLink(...)`
The mapping is quite sophisticated. It will do sensible things for
section nodes and lexeme nodes, dependent on how they are configured in
the app's `config.yaml`.
!!! hint "Customizable"
You can customize the behaviour of `webLink()` to the needs of your corpus
by providing appropriate values in its `config.yaml`, especially for
`webBase`, `webLang`, `webOffset`, `webFeature`, `webUrl`, `webUrlLex`, and `webHint`.
Parameters
----------
n: int
A node
text: string/HTML, optional default `None`
The text of the link. If left out, a suitable text will be derived from
the node.
clsName: string, optional default `None`
A CSS class name to add to the resulting link element
urlOnly: boolean, optional `False`
If True, only the url will be returned.
_asString: boolean, optional `False`
Whether to deliver the result as a piece of HTML or to display the link
on the (Jupyter) interface.
_noUrl: boolean, optional `False`
Whether to put the generated url in the `href` attribute.
It can be inhibited. This is useful for the TF-browser, which may want
to attach an action to the link and navigate to a location based on
other attributes.
See Also
--------
tf.advanced.settings: options allowed in `config.yaml`
"""
api = app.api
TF = api.TF
T = api.T
F = api.F
Fs = api.Fs
aContext = app.context
webBase = aContext.webBase
webLang = aContext.webLang
webOffset = aContext.webOffset
webFeature = aContext.webFeature
webUrl = aContext.webUrl
webUrlZeros = aContext.webUrlZeros or {}
webUrlLex = aContext.webUrlLex
webLexId = aContext.webLexId
webHint = aContext.webHint
lexTypes = aContext.lexTypes
styles = aContext.styles
nType = F.otype.v(n)
passageText = None
if _noUrl:
href = "#"
elif nType in lexTypes:
if webUrlLex and webLexId:
lid = (
app.getLexId(n)
if webLexId is True
else Fs(webLexId).v(n)
if webLexId
else None
)
href = webUrlLex.replace("<lid>", str(lid))
elif webBase:
href = webBase
else:
href = None
else:
href = None
if webFeature:
if TF.isLoaded(webFeature, pretty=False)[webFeature]:
val = Fs(webFeature).v(n)
if val is not None:
href = f"{webBase}{val}"
if href is None:
if webUrl:
href = webUrl
headingTuple = T.sectionFromNode(n, lang=webLang, fillup=True)
for (i, heading) in enumerate(headingTuple):
defaultOffset = 0 if type(heading) is int else ""
offset = (
defaultOffset
if webOffset is None
else webOffset.get(i + 1, {}).get(
headingTuple[i - 1], defaultOffset
)
if i > 0
else defaultOffset
)
value = "" if heading is None else str(heading + offset)
leadingZeros = webUrlZeros.get(i + 1, 0)
if 0 < len(value) < leadingZeros:
value = "0" * (leadingZeros - len(value)) + value
href = href.replace(f"<{i + 1}>", value)
else:
href = None
if nType in lexTypes:
if text is None:
text = app.getText(False, n, nType, False, True, True, "", None, None)
else:
passageText = app.sectionStrFromNode(n)
if text is None:
text = passageText
style = styles.get(nType, None)
if style:
clsName = f"{clsName or ''} {style}"
if href is None:
fullResult = text
else:
atts = dict(target="") if _noUrl else dict(title=webHint)
fullResult = outLink(
text,
href,
clsName=clsName,
passage=passageText,
**atts,
)
result = href if urlOnly else fullResult
if _asString or urlOnly:
return result
dh(result)
def showProvenance(app, jobName="program code", author="program author"):
"""Shows the provenance that is normally displayed during data loading.
This comes in handy if you have started with
`use("org/repo", silence='deep')` and still need to show the provenance.
Moreover, the provenance is shown in a formatted way.
Parameters
----------
jobName: string, optional `program code`
E.g. the name of program in which you call this function.
In the Text-Fabric browser the name of the job will be entered here.
This item will be displayed together with the rest of the provenance.
author: string, optional `program author`
E.g. your own name.
This item will be displayed together with the rest of the provenance.
"""
aContext = app.context
backend = app.backend
org = aContext.org
repo = aContext.repo
commit = aContext.commit
appProvenance = (
(("backend", backend), ("org", org), ("repo", repo), ("commit", commit)),
)
provenance = (appProvenance, app.provenance)
setNames = (
tuple(sorted(app.sets.keys()))
if hasattr(app, "sets") and type(app.sets) is dict
else ()
)
form = dict(jobName=jobName, author=author)
dh(wrapProvenance(form, provenance, setNames)[0])
PATH_RE1 = re.compile(
rf"""
^
(.*?)
/
(?:
(
(?:
text-fabric-data
/
)?
)
(
{GH}
|
{GL}
|
(?:
(?:
[a-zA-Z0-9_-]+\.
)+
[a-zA-Z0-9_-]+
)
)
)
/
(.*)
$
""",
re.X | re.I,
)
PATH_RE2 = re.compile(
r"""
^
([^/]+)
/
([^/]+)
/
(.*)
$
""",
re.X | re.I,
)
def _parseFeaturePath(path, backend):
match1 = PATH_RE1.fullmatch(path)
if not match1:
return (False, backend, "??", "??", "??")
(lead, exDir, theBackend, rest) = match1.groups()
theBackend = theBackend.lower()
match2 = PATH_RE2.fullmatch(rest)
if not match2:
return (False, theBackend, "??", "??", "??")
(org, repo, relative) = match2.groups()
return (True, theBackend, org, repo, relative)
def outLink(text, href, title=None, passage=None, clsName=None, target="_blank"):
"""Produce a formatted link.
Parameters
----------
text: string/HTML
The text of the link.
href: string/URL
The url of the link.
title: string, optional `None`
The hint of the link.
target: string, optional `_blank`
The target window/tab of the link.
clsName: string, optional default `None`
A CSS class name to add to the resulting link element
passage: string, optional `None`
A passage indicator, which will end up in the `sec` attribute of the
link element. Used by the TF-browser.
"""
titleAtt = "" if title is None else f' title="{title}"'
clsAtt = f' class="{clsName.lower()}"' if clsName else ""
targetAtt = f' target="{target}"' if target else ""
passageAtt = f' sec="{passage}"' if passage else ""
return (
f'<a{clsAtt}{targetAtt} href="{htmlEsc(href)}"{titleAtt}{passageAtt}>'
f"{text}</a>"
)
def _featuresPerModule(app):
"""Generate a formatted list of loaded TF features, per module."""
isCompatible = app.isCompatible
if isCompatible is not None and not isCompatible:
return UNSUPPORTED
api = app.api
TF = app.TF
backend = app.backend
aContext = app.context
mOrg = aContext.org
mRepo = aContext.repo
mRelative = aContext.relative
version = aContext.version
moduleSpecs = aContext.moduleSpecs
corpus = aContext.corpus
featureBase = aContext.featureBase
features = api.Fall() + api.Eall()
fixedModuleIndex = {}
for m in moduleSpecs or []:
fixedModuleIndex[
(m["backend"] or backend, m["org"], m["repo"], m["relative"])
] = (
m["corpus"],
m["docUrl"],
)
moduleIndex = {}
mLocations = app.mLocations if hasattr(app, "mLocations") else []
baseLoc = mLocations[0] if hasattr(app, "mLocations") else ()
for mLoc in mLocations:
(parseOK, theBackend, org, repo, relative) = _parseFeaturePath(mLoc, backend)
if not parseOK:
moduleIndex[mLoc] = (theBackend, org, repo, relative, mLoc, "")
else:
mId = (theBackend, org, repo, relative)
(corpus, docUrl) = (
(relative, None)
if org is None or repo is None
else (
(corpus, featureBase.format(version=version))
if featureBase
else (corpus, None)
)
if mLoc == baseLoc
else fixedModuleIndex[mId]
if mId in fixedModuleIndex
else (
f"{backendRep(backend, 'rep')}{org}/{repo}/{relative}",
f"{backendRep(backend, 'url')}/{org}/{repo}/tree/master/{relative}",
)
)
moduleIndex[mId] = (theBackend, org, repo, relative, corpus, docUrl)
featureCat = {}
for feature in features:
added = False
featureInfo = TF.features[feature]
featurePath = featureInfo.path
(parsedOK, fBackend, fOrg, fRepo, relative) = _parseFeaturePath(
os.path.dirname(featurePath), backend
)
if parsedOK:
fRelative = relative.rsplit("/", 1)[0]
mId = (fBackend, fOrg, fRepo, fRelative)
else:
mId = featurePath.rsplit("/", 1)[0]
if type(mId) is str:
for mIId in moduleIndex:
if type(mIId) is str:
if featurePath.startswith(mIId):
featureCat.setdefault(mIId, []).append(feature)
added = True
else:
for mIId in moduleIndex:
if type(mIId) is not str:
(mBackend, mOrg, mRepo, mRelative) = mIId
if (
fBackend == mBackend
and fOrg == mOrg
and fRepo == mRepo
and fRelative.startswith(mRelative)
):
featureCat.setdefault(mIId, []).append(feature)
added = True
if not added:
featureCat.setdefault(mId, []).append(feature)
baseId = (backend, mOrg, mRepo, mRelative)
baseMods = {
mId for mId in featureCat.keys() if type(mId) is tuple and mId == baseId
}
moduleOrder = list(baseMods) + sorted(
(mId for mId in featureCat.keys() if mId not in baseMods),
key=lambda mId: (1, mId) if type(mId) is str else (0, mId),
)
html = ""
for mId in moduleOrder:
catFeats = featureCat[mId]
if not catFeats:
continue
modInfo = moduleIndex.get(mId, None)
if modInfo:
(mBackend, org, repo, relative, corpus, docUrl) = modInfo
else:
corpus = mId if type(mId) is str else "/".join(mId)
docUrl = (
""
if type(mId) is str
else (
f"{backendRep(mBackend, 'url')}/"
f"{mId[0]}/{mId[1]}/tree/master/{mId[2]}"
)
)
html += dedent(
f"""
<details><summary><b>{corpus}</b></summary>
<div class="fcorpus">
"""
)
seen = set()
for feature in catFeats:
if "@" in feature:
dlFeature = f'{feature.rsplit("@", 1)[0]}@ll'
if dlFeature in seen:
continue
seen.add(dlFeature)
featureRep = dlFeature
else:
featureRep = feature
featureInfo = TF.features[feature]
featurePath = ux(featureInfo.path)
isEdge = featureInfo.isEdge
valueType = featureInfo.dataType
edgeValues = featureInfo.edgeValues
typeRep = "none" if isEdge and not edgeValues else valueType
meta = featureInfo.metaData
description = meta.get("description", "")
edgeRep = "edge" if isEdge else ""
html += dedent(
f"""
<div class="frow">
<div class="fnamecat {edgeRep}">
"""
)
html += (
outLink(
featureRep,
docUrl.replace("<feature>", featureRep),
title=featurePath,
)
if docUrl
else f'<span title="{featurePath}">{featureRep}</span>'
)
html += dedent(
f"""
</div>
<div class="fmono">{typeRep}</div>
<details>
<summary>{description}</summary>
<div class="fmeta">
"""
)
for (k, v) in sorted(meta.items()):
if k not in {"valueType", "description"}:
k = htmlEsc(k)
v = htmlEsc(v)
html += dedent(
f"""
<div class="fmetarow">
<div class="fmetakey">{k}:</div>
<div>{v}</div>
</div>
"""
)
html += dedent(
"""
</div>
</details>
</div>
"""
)
html += dedent(
"""
</div>
</details>
"""
)
return html
def provenanceLink(backend, org, repo, version, commit, local, release, relative):
"""Generate a provenance link for a data source.
We assume the data source resides somewhere inside a backend repository.
Parameters
----------
backend: string
`github` or `gitlab` or a GitLab instance such as `gitlab.huc.knaw.nl`.
org: string
Organization on GitHub or group on GitLab
repo: string
Repository on GitHub or project on GitLab
version: string
Version of the data source.
This is not the release or commit of a repo, but the subdirectory
corresponding with a data version under a `tf` directory with feature files.
commit: string
The commit hash of the repository
local: boolean
Whether the data is on the local computer and not necessarily backed up
by a backend repository
release: string
The release tag of the repository
"""
text = (
f"data on local machine {relative}"
if org is None or repo is None
else (
f"{org}/{repo} v:{version}"
f"({Checkout.toString(commit, release, local, backend)})"
)
)
relativeFlat = relative.replace("/", "-")
bUrl = backendRep(backend, "url")
url = (
None
if org is None or repo is None
else f"{bUrl}/{org}/{repo}/tree/master/{relative}"
if local
else (
(
(
f"{bUrl}/{org}/{repo}/releases/download/{release}"
f"/{relativeFlat}-{version}.zip"
)
if backend == GH
else (
f"{bUrl}/{org}/{repo}/-/archive/{release}" f"/{repo}-{version}.zip"
)
)
if release
else f"{bUrl}/{org}/{repo}/tree/{commit}/{relative}"
)
)
return (text, url)
Functions
def header(app)
-
Generate a colofon of the TF-app.
This colofon will be displayed after initializing the advanced API, and it is packed with provenance and documentation links.
Expand source code Browse git
def header(app): """Generate a colofon of the TF-app. This colofon will be displayed after initializing the advanced API, and it is packed with provenance and documentation links. """ appLink = app.appLink dataLink = app.dataLink charLink = app.charLink featureLink = app.featureLink tfsLink = app.tfsLink tfLink = app.tfLink tutLink = app.tutLink if app._browse: return ( f"""\ <div class="hdlinks"> {dataLink} {charLink} {featureLink} {tfsLink} {tutLink} </div>\ """, '<img class="hdlogo" src="/data/static/logo.png"/>', '<img class="hdlogo" src="/server/static/icon.png"/>', ) else: tfLine = ", ".join(x for x in (tfLink, appLink, tfsLink) if x) dataLine = ", ".join(x for x in (dataLink, charLink, featureLink) if x) dh( f"<b>Text-Fabric:</b> {tfLine}<br>" f"<b>Data:</b> {dataLine}<br>" "<b>Features:</b><br>" + _featuresPerModule(app) )
def linksApi(app, silent)
-
Produce the link API.
The link API provides methods to maps nodes to urls of web resources. It also computes several provenance and documentation links from the configuration settings of the corpus.
If a single volume has been loaded, there will be added a provenance notice to the provenance data of the work as a whole, essentially stating which volume from the work is being used and what top-level sections of the work are part of it.
Parameters
app
:obj
- The high-level API object
silent: The verbosity mode to perform this operation in. Normally it is the same as for the app, but when we do an
A.reuse()
we forcesilent=True
.Expand source code Browse git
def linksApi(app, silent): """Produce the link API. The link API provides methods to maps nodes to urls of web resources. It also computes several provenance and documentation links from the configuration settings of the corpus. If a single volume has been loaded, there will be added a provenance notice to the provenance data of the work as a whole, essentially stating which volume from the work is being used and what top-level sections of the work are part of it. Parameters ---------- app: obj The high-level API object silent: The verbosity mode to perform this operation in. Normally it is the same as for the app, but when we do an `A.reuse()` we force `silent=True`. """ backend = app.backend app.showProvenance = types.MethodType(showProvenance, app) app.header = types.MethodType(header, app) app.webLink = types.MethodType(webLink, app) isCompatible = app.isCompatible aContext = app.context appName = aContext.appName appRelative = aContext.relative appPath = aContext.appPath apiVersion = aContext.apiVersion docUrl = aContext.docUrl org = aContext.org or "" repo = aContext.repo or "" version = aContext.version corpus = aContext.corpus featureBase = aContext.featureBase featurePage = aContext.featurePage charUrl = aContext.charUrl charText = aContext.charText tutUrl = ( f"{backendRep(backend, 'urlnb')}/" f"{org}/{repo}/blob/master/tutorial/start.ipynb" ) apiVersionRep = "" if apiVersion is None else f" v{apiVersion}" dataName = repo.upper() collectionInfo = app.collectionInfo if collectionInfo: dataName += f" collection {collectionInfo}" else: volumeInfo = app.volumeInfo if volumeInfo: dataName += f" volume {volumeInfo}" dataLink = ( outLink(dataName, docUrl, f"provenance of {corpus}") if isCompatible and repo is not None and docUrl else "/".join( (x or "") for x in (appPath.rsplit("/", 1)[0], appRelative, version) ) if appName.startswith("app:") else "/".join((x or "") for x in (appPath, appName, version)) ) charLink = ( ( outLink("Character table", charUrl.format(tfDoc=URL_TFDOC), charText) if isCompatible else UNSUPPORTED ) if charUrl else "" ) featureLink = ( ( outLink( "Feature docs", featureBase.replace("<feature>", featurePage).format(version=version), f"{repo.upper()} feature documentation", ) if isCompatible and repo is not None and featureBase else UNSUPPORTED ) if isCompatible else UNSUPPORTED ) extraUrl = f"{backendRep(backend, 'url')}/{org}/{repo}/blob/master/{APP_APP}" appLink = ( outLink(f"{org}/{repo}/app {apiVersionRep}", extraUrl, f"{appName} TF-app") if isCompatible and repo is not None else "no app configured" ) tfLink = outLink( f"Text-Fabric API {app.TF.version}", APIREF, "text-fabric-api", ) tfsLink = ( outLink( "Search Reference", SEARCHREF, "Search Templates Introduction and Reference", ) if isCompatible else UNSUPPORTED ) tutLink = ( outLink("App tutorial", tutUrl, "App tutorial in Jupyter Notebook") if isCompatible and repo is not None else UNSUPPORTED ) app.appLink = appLink app.dataLink = dataLink app.charLink = charLink app.featureLink = featureLink app.tfLink = tfLink app.tfsLink = tfsLink app.tutLink = tutLink if not app._browse: if not silent: header(app)
def outLink(text, href, title=None, passage=None, clsName=None, target='_blank')
-
Produce a formatted link.
Parameters
text
:string/HTML
- The text of the link.
href
:string/URL
- The url of the link.
title
:string
, optionalNone
- The hint of the link.
target
:string
, optional_blank
- The target window/tab of the link.
clsName
:string
, optionaldefault
None``- A CSS class name to add to the resulting link element
passage
:string
, optionalNone
- A passage indicator, which will end up in the
sec
attribute of the link element. Used by the TF-browser.
Expand source code Browse git
def outLink(text, href, title=None, passage=None, clsName=None, target="_blank"): """Produce a formatted link. Parameters ---------- text: string/HTML The text of the link. href: string/URL The url of the link. title: string, optional `None` The hint of the link. target: string, optional `_blank` The target window/tab of the link. clsName: string, optional default `None` A CSS class name to add to the resulting link element passage: string, optional `None` A passage indicator, which will end up in the `sec` attribute of the link element. Used by the TF-browser. """ titleAtt = "" if title is None else f' title="{title}"' clsAtt = f' class="{clsName.lower()}"' if clsName else "" targetAtt = f' target="{target}"' if target else "" passageAtt = f' sec="{passage}"' if passage else "" return ( f'<a{clsAtt}{targetAtt} href="{htmlEsc(href)}"{titleAtt}{passageAtt}>' f"{text}</a>" )
def provenanceLink(backend, org, repo, version, commit, local, release, relative)
-
Generate a provenance link for a data source.
We assume the data source resides somewhere inside a backend repository.
Parameters
backend
:string
github
orgitlab
or a GitLab instance such asgitlab.huc.knaw.nl
.org
:string
- Organization on GitHub or group on GitLab
repo
:string
- Repository on GitHub or project on GitLab
version
:string
- Version of the data source.
This is not the release or commit of a repo, but the subdirectory
corresponding with a data version under a
tf
directory with feature files. commit
:string
- The commit hash of the repository
local
:boolean
- Whether the data is on the local computer and not necessarily backed up by a backend repository
release
:string
- The release tag of the repository
Expand source code Browse git
def provenanceLink(backend, org, repo, version, commit, local, release, relative): """Generate a provenance link for a data source. We assume the data source resides somewhere inside a backend repository. Parameters ---------- backend: string `github` or `gitlab` or a GitLab instance such as `gitlab.huc.knaw.nl`. org: string Organization on GitHub or group on GitLab repo: string Repository on GitHub or project on GitLab version: string Version of the data source. This is not the release or commit of a repo, but the subdirectory corresponding with a data version under a `tf` directory with feature files. commit: string The commit hash of the repository local: boolean Whether the data is on the local computer and not necessarily backed up by a backend repository release: string The release tag of the repository """ text = ( f"data on local machine {relative}" if org is None or repo is None else ( f"{org}/{repo} v:{version}" f"({Checkout.toString(commit, release, local, backend)})" ) ) relativeFlat = relative.replace("/", "-") bUrl = backendRep(backend, "url") url = ( None if org is None or repo is None else f"{bUrl}/{org}/{repo}/tree/master/{relative}" if local else ( ( ( f"{bUrl}/{org}/{repo}/releases/download/{release}" f"/{relativeFlat}-{version}.zip" ) if backend == GH else ( f"{bUrl}/{org}/{repo}/-/archive/{release}" f"/{repo}-{version}.zip" ) ) if release else f"{bUrl}/{org}/{repo}/tree/{commit}/{relative}" ) ) return (text, url)
def showProvenance(app, jobName='program code', author='program author')
-
Shows the provenance that is normally displayed during data loading.
This comes in handy if you have started with
use("org/repo", silence='deep')
and still need to show the provenance. Moreover, the provenance is shown in a formatted way.Parameters
jobName
:string
, optionalprogram code
- E.g. the name of program in which you call this function. In the Text-Fabric browser the name of the job will be entered here. This item will be displayed together with the rest of the provenance.
author
:string
, optionalprogram author
- E.g. your own name. This item will be displayed together with the rest of the provenance.
Expand source code Browse git
def showProvenance(app, jobName="program code", author="program author"): """Shows the provenance that is normally displayed during data loading. This comes in handy if you have started with `use("org/repo", silence='deep')` and still need to show the provenance. Moreover, the provenance is shown in a formatted way. Parameters ---------- jobName: string, optional `program code` E.g. the name of program in which you call this function. In the Text-Fabric browser the name of the job will be entered here. This item will be displayed together with the rest of the provenance. author: string, optional `program author` E.g. your own name. This item will be displayed together with the rest of the provenance. """ aContext = app.context backend = app.backend org = aContext.org repo = aContext.repo commit = aContext.commit appProvenance = ( (("backend", backend), ("org", org), ("repo", repo), ("commit", commit)), ) provenance = (appProvenance, app.provenance) setNames = ( tuple(sorted(app.sets.keys())) if hasattr(app, "sets") and type(app.sets) is dict else () ) form = dict(jobName=jobName, author=author) dh(wrapProvenance(form, provenance, setNames)[0])
def webLink(app, n, text=None, clsName=None, urlOnly=False)
-
Maps a node to a web resource.
Usually called as
A.webLink(…)
The mapping is quite sophisticated. It will do sensible things for section nodes and lexeme nodes, dependent on how they are configured in the app's
config.yaml
.Customizable
You can customize the behaviour of
webLink()
to the needs of your corpus by providing appropriate values in itsconfig.yaml
, especially forwebBase
,webLang
,webOffset
,webFeature
,webUrl
,webUrlLex
, andwebHint
.Parameters
n
:int
- A node
text
:string/HTML
, optionaldefault
None``- The text of the link. If left out, a suitable text will be derived from the node.
clsName
:string
, optionaldefault
None``- A CSS class name to add to the resulting link element
urlOnly
:boolean
, optionalFalse
- If True, only the url will be returned.
_asString
:boolean
, optionalFalse
- Whether to deliver the result as a piece of HTML or to display the link on the (Jupyter) interface.
_noUrl
:boolean
, optionalFalse
- Whether to put the generated url in the
href
attribute. It can be inhibited. This is useful for the TF-browser, which may want to attach an action to the link and navigate to a location based on other attributes.
See Also
tf.advanced.settings
- options allowed in
config.yaml
Expand source code Browse git
def webLink( app, n, text=None, clsName=None, urlOnly=False, _asString=False, _noUrl=False ): """Maps a node to a web resource. Usually called as `A.webLink(...)` The mapping is quite sophisticated. It will do sensible things for section nodes and lexeme nodes, dependent on how they are configured in the app's `config.yaml`. !!! hint "Customizable" You can customize the behaviour of `webLink()` to the needs of your corpus by providing appropriate values in its `config.yaml`, especially for `webBase`, `webLang`, `webOffset`, `webFeature`, `webUrl`, `webUrlLex`, and `webHint`. Parameters ---------- n: int A node text: string/HTML, optional default `None` The text of the link. If left out, a suitable text will be derived from the node. clsName: string, optional default `None` A CSS class name to add to the resulting link element urlOnly: boolean, optional `False` If True, only the url will be returned. _asString: boolean, optional `False` Whether to deliver the result as a piece of HTML or to display the link on the (Jupyter) interface. _noUrl: boolean, optional `False` Whether to put the generated url in the `href` attribute. It can be inhibited. This is useful for the TF-browser, which may want to attach an action to the link and navigate to a location based on other attributes. See Also -------- tf.advanced.settings: options allowed in `config.yaml` """ api = app.api TF = api.TF T = api.T F = api.F Fs = api.Fs aContext = app.context webBase = aContext.webBase webLang = aContext.webLang webOffset = aContext.webOffset webFeature = aContext.webFeature webUrl = aContext.webUrl webUrlZeros = aContext.webUrlZeros or {} webUrlLex = aContext.webUrlLex webLexId = aContext.webLexId webHint = aContext.webHint lexTypes = aContext.lexTypes styles = aContext.styles nType = F.otype.v(n) passageText = None if _noUrl: href = "#" elif nType in lexTypes: if webUrlLex and webLexId: lid = ( app.getLexId(n) if webLexId is True else Fs(webLexId).v(n) if webLexId else None ) href = webUrlLex.replace("<lid>", str(lid)) elif webBase: href = webBase else: href = None else: href = None if webFeature: if TF.isLoaded(webFeature, pretty=False)[webFeature]: val = Fs(webFeature).v(n) if val is not None: href = f"{webBase}{val}" if href is None: if webUrl: href = webUrl headingTuple = T.sectionFromNode(n, lang=webLang, fillup=True) for (i, heading) in enumerate(headingTuple): defaultOffset = 0 if type(heading) is int else "" offset = ( defaultOffset if webOffset is None else webOffset.get(i + 1, {}).get( headingTuple[i - 1], defaultOffset ) if i > 0 else defaultOffset ) value = "" if heading is None else str(heading + offset) leadingZeros = webUrlZeros.get(i + 1, 0) if 0 < len(value) < leadingZeros: value = "0" * (leadingZeros - len(value)) + value href = href.replace(f"<{i + 1}>", value) else: href = None if nType in lexTypes: if text is None: text = app.getText(False, n, nType, False, True, True, "", None, None) else: passageText = app.sectionStrFromNode(n) if text is None: text = passageText style = styles.get(nType, None) if style: clsName = f"{clsName or ''} {style}" if href is None: fullResult = text else: atts = dict(target="") if _noUrl else dict(title=webHint) fullResult = outLink( text, href, clsName=clsName, passage=passageText, **atts, ) result = href if urlOnly else fullResult if _asString or urlOnly: return result dh(result)