aboutsummaryrefslogtreecommitdiffstats
path: root/sl0ppij/zellij-utils/src/branding.rs
blob: cc90ecf6a16275d862438f76cee0e1d354a0a96a (plain) (blame)
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
//! sl0p fork: the branding seam.
//!
//! sl0ppij is a fork of zellij that must be installable and runnable *beside* a
//! stock zellij: different binary name, different config dir, different socket
//! dir, different cache. Rather than renaming things all over the tree (which
//! would turn every future rebase onto upstream into a conflict festival), every
//! user-visible name is defined exactly once, here, and referenced from the
//! handful of places that used to hold a literal.
//!
//! Rebase rule: if a conflict lands in this file, the fix is always "keep our
//! constant, take their surrounding code".
//!
//! What is deliberately NOT renamed:
//!   - crate names (`zellij-utils`, `zellij-tile`, ...). They are internal, and
//!     renaming them would break the plugin API and every intra-workspace dep
//!     for zero user-visible gain. The *binary* is renamed via `[[bin]]` in the
//!     root Cargo.toml instead.
//!   - the `$ZELLIJ*` environment variables. They stay, because the wider
//!     ecosystem (shell prompts, "am I in a multiplexer" checks, our own
//!     pi-ext) looks for them. We additionally export `$SL0PPIJ*` equivalents,
//!     which take precedence when both are set.

/// Program name. Drives the binary name, config dir, socket dir prefix and log
/// file name.
pub const APP: &str = "sl0ppij";

/// The upstream project we are forked from. Only used for compatibility
/// fallbacks, never for anything we create.
pub const UPSTREAM_APP: &str = "zellij";

pub const ABOUT: &str = "A terminal workspace with batteries included (sl0p fork)";

// --- filesystem ------------------------------------------------------------

/// `~/.config/sl0ppij` -- checked before the XDG dir, as upstream does.
pub const HOME_CONFIG_LOCATION: &str = ".config/sl0ppij";

/// System-wide config dir.
pub const SYSTEM_CONFIG_DIR: &str = "/etc/sl0ppij";
pub const SYSTEM_CONFIG_DIR_WINDOWS: &str = "C:\\ProgramData\\sl0ppij";
pub const SYSTEM_DATA_DIR_PREFIX_WINDOWS: &str = "C:\\ProgramData\\sl0ppij";

/// Appended to the data dir prefix, e.g. `/usr` + `share/sl0ppij`.
pub const SYSTEM_DATA_SUBDIR: &str = "share/sl0ppij";

/// Arguments to `directories::ProjectDirs::from`. On Linux only the last one
/// matters (`~/.config/sl0ppij`, `~/.cache/sl0ppij`); the qualifier and
/// organisation shape the macOS/Windows paths.
pub const PROJ_QUALIFIER: &str = "foo";
pub const PROJ_ORGANIZATION: &str = "sl0p";
pub const PROJ_APPLICATION: &str = "sl0ppij";

/// `$TMPDIR/sl0ppij-<uid>`
pub const TMP_DIR_PREFIX: &str = "sl0ppij";
pub const LOG_DIR_NAME: &str = "sl0ppij-log";
pub const LOG_FILE_NAME: &str = "sl0ppij.log";
pub const OLD_LOG_FILE_NAME: &str = "sl0ppij.log.old";

// --- plugin url scheme -----------------------------------------------------

/// Scheme for builtin plugins: `sl0ppij:tab-bar`. `zellij:tab-bar` is still
/// accepted when parsing so upstream layouts and configs keep working.
pub const PLUGIN_SCHEME: &str = "sl0ppij";
pub const PLUGIN_SCHEME_COMPAT: &str = "zellij";

// --- environment variables -------------------------------------------------
//
// Each pair is (ours, upstream's). Both are exported when we set them; ours
// wins when both are present on read.

pub const ENV_MARKER: &str = "SL0PPIJ";
pub const ENV_MARKER_COMPAT: &str = "ZELLIJ";

pub const ENV_SESSION_NAME: &str = "SL0PPIJ_SESSION_NAME";
pub const ENV_SESSION_NAME_COMPAT: &str = "ZELLIJ_SESSION_NAME";

pub const ENV_SOCKET_DIR: &str = "SL0PPIJ_SOCKET_DIR";
pub const ENV_SOCKET_DIR_COMPAT: &str = "ZELLIJ_SOCKET_DIR";

pub const ENV_PANE_ID: &str = "SL0PPIJ_PANE_ID";
pub const ENV_PANE_ID_COMPAT: &str = "ZELLIJ_PANE_ID";

pub const ENV_CONFIG_FILE: &str = "SL0PPIJ_CONFIG_FILE";
pub const ENV_CONFIG_FILE_COMPAT: &str = "ZELLIJ_CONFIG_FILE";

pub const ENV_CONFIG_DIR: &str = "SL0PPIJ_CONFIG_DIR";
pub const ENV_CONFIG_DIR_COMPAT: &str = "ZELLIJ_CONFIG_DIR";

pub const ENV_LAYOUT_DIR: &str = "SL0PPIJ_LAYOUT_DIR";
pub const ENV_LAYOUT_DIR_COMPAT: &str = "ZELLIJ_LAYOUT_DIR";

/// Every (ours, upstream) pair, for bulk operations.
pub const ENV_PAIRS: &[(&str, &str)] = &[
    (ENV_MARKER, ENV_MARKER_COMPAT),
    (ENV_SESSION_NAME, ENV_SESSION_NAME_COMPAT),
    (ENV_SOCKET_DIR, ENV_SOCKET_DIR_COMPAT),
    (ENV_PANE_ID, ENV_PANE_ID_COMPAT),
    (ENV_CONFIG_FILE, ENV_CONFIG_FILE_COMPAT),
    (ENV_CONFIG_DIR, ENV_CONFIG_DIR_COMPAT),
    (ENV_LAYOUT_DIR, ENV_LAYOUT_DIR_COMPAT),
];

/// Read our variable, falling back to the upstream-named one.
pub fn env_var(primary: &str, compat: &str) -> Result<String, std::env::VarError> {
    match std::env::var(primary) {
        Ok(value) => Ok(value),
        Err(_) => std::env::var(compat),
    }
}

/// Set both spellings, so children see whichever they look for.
pub fn set_env_var(primary: &str, compat: &str, value: &str) {
    std::env::set_var(primary, value);
    std::env::set_var(compat, value);
}

/// Normalise the environment we inherited, once, at startup.
///
/// For every pair, if only the upstream spelling is set, copy it to ours. After
/// this runs, code that only reads the `SL0PPIJ*` name still honours a user who
/// exported `ZELLIJ_CONFIG_DIR` -- which matters most for the clap-derived
/// arguments, since clap can only be told about one variable name.
pub fn import_compat_env() {
    for (primary, compat) in ENV_PAIRS {
        if std::env::var_os(primary).is_none() {
            if let Some(value) = std::env::var_os(compat) {
                std::env::set_var(primary, value);
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// The whole point of the fork rename: nothing we create on disk may land in
    /// a path a stock zellij would also use.
    #[test]
    fn no_shipped_path_collides_with_upstream() {
        for path in [
            HOME_CONFIG_LOCATION,
            SYSTEM_CONFIG_DIR,
            SYSTEM_CONFIG_DIR_WINDOWS,
            SYSTEM_DATA_DIR_PREFIX_WINDOWS,
            SYSTEM_DATA_SUBDIR,
            PROJ_APPLICATION,
            TMP_DIR_PREFIX,
            LOG_DIR_NAME,
            LOG_FILE_NAME,
            OLD_LOG_FILE_NAME,
            APP,
        ] {
            assert!(
                !path.to_lowercase().contains(UPSTREAM_APP),
                "{:?} still contains {:?} -- it would collide with a stock zellij",
                path,
                UPSTREAM_APP
            );
        }
    }

    /// Ours must win when both are set, and the upstream name must still work
    /// on its own.
    #[test]
    fn env_var_precedence() {
        let primary = "SL0PPIJ_TEST_PRECEDENCE";
        let compat = "ZELLIJ_TEST_PRECEDENCE";

        std::env::remove_var(primary);
        std::env::set_var(compat, "upstream");
        assert_eq!(env_var(primary, compat).unwrap(), "upstream");

        std::env::set_var(primary, "ours");
        assert_eq!(env_var(primary, compat).unwrap(), "ours");

        std::env::remove_var(primary);
        std::env::remove_var(compat);
        assert!(env_var(primary, compat).is_err());
    }

    #[test]
    fn set_env_var_sets_both_spellings() {
        let primary = "SL0PPIJ_TEST_BOTH";
        let compat = "ZELLIJ_TEST_BOTH";
        set_env_var(primary, compat, "v");
        assert_eq!(std::env::var(primary).unwrap(), "v");
        assert_eq!(std::env::var(compat).unwrap(), "v");
        std::env::remove_var(primary);
        std::env::remove_var(compat);
    }

    /// Every pair must be (SL0PPIJ*, ZELLIJ*) and differ only in the prefix.
    #[test]
    fn env_pairs_are_consistent() {
        for (primary, compat) in ENV_PAIRS {
            assert!(primary.starts_with(ENV_MARKER), "{primary} misprefixed");
            assert!(compat.starts_with(ENV_MARKER_COMPAT), "{compat} misprefixed");
            assert_eq!(
                primary.strip_prefix(ENV_MARKER),
                compat.strip_prefix(ENV_MARKER_COMPAT),
                "{primary} and {compat} are not the same variable"
            );
        }
    }
}