From 2a08ab21abc8b482f41c521b5f9b0df5b18a67eb Mon Sep 17 00:00:00 2001 From: magicquark <198001825+magicquark@users.noreply.github.com> Date: Mon, 19 Jan 2026 03:43:54 +0000 Subject: [PATCH] pyradio: add module --- .../misc/news/2026/01/2026-01-19_03-12-38.nix | 9 ++ modules/programs/pyradio.nix | 122 ++++++++++++++++++ .../programs/pyradio/basic-configuration.nix | 11 ++ .../programs/pyradio/custom-stations.nix | 28 ++++ tests/modules/programs/pyradio/default.nix | 5 + .../programs/pyradio/empty-settings.nix | 11 ++ .../modules/programs/pyradio/expected-config | 3 + .../programs/pyradio/expected-stations.csv | 4 + 8 files changed, 193 insertions(+) create mode 100644 modules/misc/news/2026/01/2026-01-19_03-12-38.nix create mode 100644 modules/programs/pyradio.nix create mode 100644 tests/modules/programs/pyradio/basic-configuration.nix create mode 100644 tests/modules/programs/pyradio/custom-stations.nix create mode 100644 tests/modules/programs/pyradio/default.nix create mode 100644 tests/modules/programs/pyradio/empty-settings.nix create mode 100644 tests/modules/programs/pyradio/expected-config create mode 100644 tests/modules/programs/pyradio/expected-stations.csv diff --git a/modules/misc/news/2026/01/2026-01-19_03-12-38.nix b/modules/misc/news/2026/01/2026-01-19_03-12-38.nix new file mode 100644 index 00000000..29f842a9 --- /dev/null +++ b/modules/misc/news/2026/01/2026-01-19_03-12-38.nix @@ -0,0 +1,9 @@ +{ + time = "2026-01-19T03:12:38+00:00"; + condition = true; + message = '' + A new module `programs.pyradio` is available. + + A curses based internet radio player. + ''; +} diff --git a/modules/programs/pyradio.nix b/modules/programs/pyradio.nix new file mode 100644 index 00000000..ab189c2c --- /dev/null +++ b/modules/programs/pyradio.nix @@ -0,0 +1,122 @@ +{ + config, + lib, + pkgs, + ... +}: +let + inherit (lib) + mkIf + mkEnableOption + mkPackageOption + mkOption + ; + + cfg = config.programs.pyradio; +in +{ + meta.maintainers = with lib.maintainers; [ magicquark ]; + + options.programs.pyradio = { + enable = mkEnableOption "pyradio"; + + package = mkPackageOption pkgs "pyradio" { nullable = true; }; + + settings = mkOption { + type = + with lib.types; + attrsOf (oneOf [ + str + int + bool + ]); + default = { }; + example = { + enable_clock = true; + enable_notifications = 0; + player = "mpv"; + theme = "dark_16_colors"; + time_format = 0; + use_transparency = true; + }; + description = '' + Options to add to the PyRadio {file}`config` file. + See + for available options. + ''; + }; + + stations = mkOption { + type = + with lib.types; + listOf (submodule { + options = { + name = mkOption { + type = str; + description = "Display name of the radio station."; + }; + + url = mkOption { + type = str; + description = "Stream URL of the radio station."; + }; + }; + }); + default = [ ]; + example = [ + { + name = "DEF CON Radio (SomaFM)"; + url = "https://somafm.com/defcon256.pls"; + } + { + name = "Intense Radio - We Love Dance!"; + url = "https://secure.live-streams.nl/flac.ogg"; + } + ]; + description = '' + Radio stations to add to the {file}`stations.csv` file. + If empty, {file}`stations.csv` defaults to the built-in playlist. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = mkIf (cfg.package != null) [ cfg.package ]; + + xdg.configFile."pyradio/config" = mkIf (cfg.settings != { }) { + text = + let + renderedSettings = lib.generators.toINIWithGlobalSection { } { + globalSection = cfg.settings; + }; + in + lib.removeSuffix "\n\n" '' + # Generated by Home Manager. + + ${renderedSettings} + ''; + }; + + xdg.configFile."pyradio/stations.csv" = mkIf (cfg.stations != [ ]) { + text = + let + escapeCSV = + s: + if lib.hasInfix "," s || lib.hasInfix "\"" s then + "\"${lib.replaceStrings [ "\"" ] [ "\"\"" ] s}\"" + else + s; + + stationsToCSV = + stations: + let + body = lib.concatMapStringsSep "\n" ( + station: "${escapeCSV station.name},${escapeCSV station.url}" + ) stations; + in + "${body}\n"; + in + stationsToCSV cfg.stations; + }; + }; +} diff --git a/tests/modules/programs/pyradio/basic-configuration.nix b/tests/modules/programs/pyradio/basic-configuration.nix new file mode 100644 index 00000000..509715c6 --- /dev/null +++ b/tests/modules/programs/pyradio/basic-configuration.nix @@ -0,0 +1,11 @@ +{ + programs.pyradio = { + enable = true; + settings.enable_clock = true; + }; + + nmt.script = '' + assertFileExists "home-files/.config/pyradio/config" + assertFileContent "home-files/.config/pyradio/config" ${./expected-config} + ''; +} diff --git a/tests/modules/programs/pyradio/custom-stations.nix b/tests/modules/programs/pyradio/custom-stations.nix new file mode 100644 index 00000000..13946ae3 --- /dev/null +++ b/tests/modules/programs/pyradio/custom-stations.nix @@ -0,0 +1,28 @@ +{ + programs.pyradio = { + enable = true; + stations = [ + { + name = "DEF CON Radio (SomaFM)"; + url = "https://somafm.com/defcon256.pls"; + } + # The stations below test the csv escaping functionality. + { + name = "DEF CON Radio, SomaFM"; + url = "https://somafm.com/defcon256.pls"; + } + { + name = "DEF CON Radio on \"SomaFM\""; + url = "https://somafm.com/defcon256.pls"; + } + { + name = ''DEF CON Radio on "SomaFM"''; + url = "https://somafm.com/defcon256.pls"; + } + ]; + }; + + nmt.script = '' + assertFileContent "home-files/.config/pyradio/stations.csv" ${./expected-stations.csv} + ''; +} diff --git a/tests/modules/programs/pyradio/default.nix b/tests/modules/programs/pyradio/default.nix new file mode 100644 index 00000000..a88b9aaa --- /dev/null +++ b/tests/modules/programs/pyradio/default.nix @@ -0,0 +1,5 @@ +{ + pyradio-basic-configuration = ./basic-configuration.nix; + pyradio-custom-stations = ./custom-stations.nix; + pyradio-empty-settings = ./empty-settings.nix; +} diff --git a/tests/modules/programs/pyradio/empty-settings.nix b/tests/modules/programs/pyradio/empty-settings.nix new file mode 100644 index 00000000..3cdec94f --- /dev/null +++ b/tests/modules/programs/pyradio/empty-settings.nix @@ -0,0 +1,11 @@ +{ + programs.pyradio = { + enable = true; + settings = { }; + }; + + nmt.script = '' + assertPathNotExists "home-files/.config/pyradio/config" + assertPathNotExists "home-files/.config/pyradio/stations.csv" + ''; +} diff --git a/tests/modules/programs/pyradio/expected-config b/tests/modules/programs/pyradio/expected-config new file mode 100644 index 00000000..3271f38e --- /dev/null +++ b/tests/modules/programs/pyradio/expected-config @@ -0,0 +1,3 @@ +# Generated by Home Manager. + +enable_clock=true diff --git a/tests/modules/programs/pyradio/expected-stations.csv b/tests/modules/programs/pyradio/expected-stations.csv new file mode 100644 index 00000000..3ce67881 --- /dev/null +++ b/tests/modules/programs/pyradio/expected-stations.csv @@ -0,0 +1,4 @@ +DEF CON Radio (SomaFM),https://somafm.com/defcon256.pls +"DEF CON Radio, SomaFM",https://somafm.com/defcon256.pls +"DEF CON Radio on ""SomaFM""",https://somafm.com/defcon256.pls +"DEF CON Radio on ""SomaFM""",https://somafm.com/defcon256.pls