feat: add install scripts, packaging and an example plugin

install.sh places the package in a version independent directory under
XDG_DATA_HOME instead of site-packages, which would be orphaned the day
the distribution moves to a new Python. It verifies the package imports
before reporting success, and warns when the pre-split extension is still
installed alongside it.

pyproject.toml makes pip install --user an equally valid route; the
bootstrap in each plugin finds the package either way.

examples/nemoRepoLastCommit.py is a complete working plugin in sixty
lines, which is the point of the split.
This commit is contained in:
dualfroz committed 2026-08-05 20:06:41 +02:00
1 parent 4ae3a496af
commit 0cd7a24adb
4 files changed
+201

No files matched your search

+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/python3
"""Example plugin: a "Last Commit" column for Nemo's list view.
Copy this file to ~/.local/share/nemo-python/extensions/ and restart Nemo
with `nemo -q` to try it. It is about sixty lines of real work because
nemo-repo-core already owns the config file, the git call and the cache.
The block above the imports is the core bootstrap. Copy it verbatim into
every plugin: it makes the plugin work whether the core was installed with
its install.sh or with pip, and it is the only piece that knows where the
core lives.
"""
import importlib.util
import os
import sys
def _ensureCoreOnPath():
if importlib.util.find_spec("nemoRepoCore") is not None:
return
dataHome = os.environ.get("XDG_DATA_HOME") or os.path.expanduser("~/.local/share")
dataDirs = (os.environ.get("XDG_DATA_DIRS") or "/usr/local/share:/usr/share").split(":")
for base in [dataHome] + dataDirs:
candidate = os.path.join(base, "nemo-repo-core")
if os.path.isdir(os.path.join(candidate, "nemoRepoCore")):
sys.path.insert(0, candidate)
return
_ensureCoreOnPath()
import gi
gi.require_version("Nemo", "3.0")
from gi.repository import GObject, Nemo
from nemoRepoCore import (findRepoRoot, localDirectoryPath, repoCache,
requireCore)
requireCore("1.0.0")
class LastCommitColumnProvider(GObject.GObject, Nemo.ColumnProvider,
Nemo.InfoProvider, Nemo.NameAndDescProvider):
def get_columns(self):
return [
Nemo.Column(name="NemoRepoExample::last_commit",
attribute="repo_last_commit",
label="Last Commit",
description="Subject and age of the most recent commit"),
]
def update_file_info(self, file):
path = localDirectoryPath(file)
if path is None:
return
root = findRepoRoot(path)
if root is None:
file.add_string_attribute("repo_last_commit", "")
return
repoCache.registerFileObject(root, file)
info = repoCache.get(root)
if info.error or not info.lastCommit:
file.add_string_attribute("repo_last_commit", "")
return
commit = info.lastCommit
file.add_string_attribute("repo_last_commit",
"%s (%s)" % (commit.subject, commit.relativeDate))
def get_name_and_desc(self):
return ["nemo-repo-example:::Example column showing the last commit"]
Executable
+74
View File
@@ -0,0 +1,74 @@
#!/usr/bin/env bash
# Installs the nemoRepoCore library for the current user. No root needed.
#
# ./install.sh link the package (edits in this checkout go live)
# ./install.sh --copy copy the package instead of linking it
#
# The package is placed in a version independent directory under
# XDG_DATA_HOME, so a Python upgrade cannot orphan it. Plugins locate it from
# there; see "Writing a plugin" in README.md.
set -euo pipefail
sourceDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
dataHome="${XDG_DATA_HOME:-$HOME/.local/share}"
installDir="$dataHome/nemo-repo-core"
configDir="${XDG_CONFIG_HOME:-$HOME/.config}"
extensionsDir="$dataHome/nemo-python/extensions"
mode="link"
if [ "${1:-}" = "--copy" ]; then
mode="copy"
elif [ -n "${1:-}" ]; then
echo "usage: $0 [--copy]" >&2
exit 2
fi
if ! command -v python3 >/dev/null 2>&1; then
echo "python3 is required but was not found in PATH" >&2
exit 1
fi
mkdir -p "$installDir"
rm -rf "$installDir/nemoRepoCore"
if [ "$mode" = "copy" ]; then
cp -r "$sourceDir/nemoRepoCore" "$installDir/nemoRepoCore"
echo "Copied nemoRepoCore -> $installDir/nemoRepoCore"
else
ln -s "$sourceDir/nemoRepoCore" "$installDir/nemoRepoCore"
echo "Linked nemoRepoCore -> $installDir/nemoRepoCore"
fi
if ! PYTHONPATH="$installDir" python3 -c "import nemoRepoCore" 2>/dev/null; then
echo
echo "WARNING: the package was installed but could not be imported."
echo "Check that python3-gi and gir1.2-nemo-3.0 are installed:"
echo " sudo apt install python3-gi gir1.2-nemo-3.0 python-nemo"
exit 1
fi
coreVersion="$(PYTHONPATH="$installDir" python3 -c 'import nemoRepoCore; print(nemoRepoCore.coreVersion)')"
echo "Import check passed, core version $coreVersion"
legacyConfig="$configDir/nemo-repo-emblems.json"
newConfig="$configDir/nemo-repo-core.json"
if [ -f "$legacyConfig" ] && [ ! -f "$newConfig" ]; then
echo
echo "Found a config from the pre-split extension:"
echo " $legacyConfig"
echo "It will be copied to $newConfig the first time a plugin loads."
echo "The old file is left untouched."
fi
legacyExtension="$extensionsDir/nemo_repo_emblems.py"
if [ -e "$legacyExtension" ]; then
echo
echo "WARNING: the pre-split single file extension is still installed:"
echo " $legacyExtension"
echo "Running it alongside the new plugins duplicates emblems, columns and"
echo "menu entries. Remove it when you are ready to switch over:"
echo " rm \"$legacyExtension\""
fi
echo
echo "Core installed. It does nothing on its own: install at least one plugin."
+27
View File
@@ -0,0 +1,27 @@
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
[project]
name = "nemo-repo-core"
version = "1.0.0"
description = "Shared foundation for repository-aware Nemo file manager plugins"
readme = "README.md"
requires-python = ">=3.8"
license = { text = "MIT" }
authors = [{ name = "DualFroz" }]
keywords = ["nemo", "cinnamon", "git", "file-manager", "nemo-python"]
classifiers = [
"Environment :: X11 Applications :: GTK",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3",
"Topic :: Desktop Environment :: File Managers",
]
[project.urls]
Homepage = "https://github.com/dualfroz/nemo-repo-core"
[tool.setuptools]
packages = ["nemoRepoCore"]
Executable
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# Removes the installed nemoRepoCore library. The checkout and the user's
# config file are left alone.
set -euo pipefail
dataHome="${XDG_DATA_HOME:-$HOME/.local/share}"
installDir="$dataHome/nemo-repo-core"
extensionsDir="$dataHome/nemo-python/extensions"
if [ -e "$installDir/nemoRepoCore" ]; then
rm -rf "$installDir/nemoRepoCore"
echo "Removed $installDir/nemoRepoCore"
else
echo "Nothing installed at $installDir/nemoRepoCore"
fi
rmdir "$installDir" 2>/dev/null || true
remaining="$(find "$extensionsDir" -maxdepth 1 -name 'nemoRepo*.py' -o -maxdepth 1 -name 'nemoFolderTools.py' 2>/dev/null || true)"
if [ -n "$remaining" ]; then
echo
echo "WARNING: these plugins still expect the core and will now fail to load:"
echo "$remaining"
echo "Uninstall them too, then restart Nemo with: nemo -q"
fi