lx-ls 0.10.1

The file lister with personality! 🌟
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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
//! Wrapper types for the values returned from `File`s.
//!
//! The methods of `File` that return information about the entry on the
//! filesystem -- size, modification date, block count, or Git status -- used
//! to just return these as formatted strings, but this became inflexible once
//! customisable output styles landed.
//!
//! Instead, they will return a wrapper type from this module, which tags the
//! type with what field it is while containing the actual raw value.
//!
//! The `output::details` module, among others, uses these types to render and
//! display the information as formatted strings.

// C-style `blkcnt_t` types don’t follow Rust’s rules!
#![allow(non_camel_case_types)]
#![allow(clippy::struct_excessive_bools)]

/// The type of a file’s block count.
pub type blkcnt_t = u64;

/// The type of a file’s group ID.
pub type gid_t = u32;

/// The type of a file’s inode.
pub type ino_t = u64;

/// The type of a file’s number of links.
pub type nlink_t = u64;

/// The type of a file’s user ID.
pub type uid_t = u32;

/// The file’s base type, which gets displayed in the very first column of the
/// details output.
///
/// This type is set entirely by the filesystem, rather than relying on a
/// file’s contents. So “link” is a type, but “image” is just a type of
/// regular file. (See the `filetype` module for those checks.)
///
/// Its ordering is used when sorting by type.
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
pub enum Type {
    Directory,
    File,
    Link,
    Pipe,
    Socket,
    CharDevice,
    BlockDevice,
    Special,
}

impl Type {
    pub fn is_regular_file(self) -> bool {
        matches!(self, Self::File)
    }
}

/// The file’s Unix permission bitfield, with one entry per bit.
#[derive(Copy, Clone)]
pub struct Permissions {
    pub user_read: bool,
    pub user_write: bool,
    pub user_execute: bool,

    pub group_read: bool,
    pub group_write: bool,
    pub group_execute: bool,

    pub other_read: bool,
    pub other_write: bool,
    pub other_execute: bool,

    pub sticky: bool,
    pub setgid: bool,
    pub setuid: bool,
}

/// The file's `FileAttributes` field, available only on Windows.
#[cfg(windows)]
#[derive(Copy, Clone)]
pub struct Attributes {
    pub archive: bool,
    pub directory: bool,
    pub readonly: bool,
    pub hidden: bool,
    pub system: bool,
    pub reparse_point: bool,
}

/// The three pieces of information that are displayed as a single column in
/// the details view. These values are fused together to make the output a
/// little more compressed.
#[derive(Copy, Clone)]
pub struct PermissionsPlus {
    pub file_type: Type,
    #[cfg(unix)]
    pub permissions: Permissions,
    #[cfg(windows)]
    pub attributes: Attributes,
    pub xattrs: bool,
}

/// The permissions encoded as octal values
#[derive(Copy, Clone)]
pub struct OctalPermissions {
    pub permissions: Permissions,
}

/// A file’s number of hard links on the filesystem.
///
/// Under Unix, a file can exist on the filesystem only once but appear in
/// multiple directories. However, it’s rare (but occasionally useful!) for a
/// regular file to have a link count greater than 1, so we highlight the
/// block count specifically for this case.
#[derive(Copy, Clone)]
pub struct Links {
    /// The actual link count.
    pub count: nlink_t,

    /// Whether this file is a regular file with more than one hard link.
    pub multiple: bool,
}

/// A file’s inode. Every directory entry on a Unix filesystem has an inode,
/// including directories and links, so this is applicable to everything lx
/// can deal with.
#[derive(Copy, Clone)]
pub struct Inode(pub ino_t);

/// The number of blocks that a file takes up on the filesystem, if any.
#[derive(Copy, Clone)]
pub enum Blocks {
    /// This file has the given number of blocks.
    Some(blkcnt_t),

    /// This file isn’t of a type that can take up blocks.
    None,
}

/// The ID of the user that owns a file. This will only ever be a number;
/// looking up the username is done in the `display` module.
#[derive(Copy, Clone)]
pub struct User(pub uid_t);

/// The ID of the group that a file belongs to.
#[derive(Copy, Clone)]
pub struct Group(pub gid_t);

/// A file’s size, in bytes. This is usually formatted by the `unit_prefix`
/// crate into something human-readable.
#[derive(Copy, Clone)]
pub enum Size {
    /// This file has a defined size.
    Some(u64),

    /// This file has no size, or has a size but we aren’t interested in it.
    ///
    /// Under Unix, directory entries that aren’t regular files will still
    /// have a file size. For example, a directory will just contain a list of
    /// its files as its “contents” and will be specially flagged as being a
    /// directory, rather than a file. However, seeing the “file size” of this
    /// data is rarely useful — I can’t think of a time when I’ve seen it and
    /// learnt something. So we discard it and just output “-” instead.
    ///
    /// See this answer for more: <https://unix.stackexchange.com/a/68266>
    None,

    /// This file is a block or character device, so instead of a size, print
    /// out the file’s major and minor device IDs.
    ///
    /// This is what ls does as well. Without it, the devices will just have
    /// file sizes of zero.
    DeviceIDs(DeviceIDs),
}

/// The major and minor device IDs that gets displayed for device files.
///
/// You can see what these device numbers mean:
/// - <http://www.lanana.org/docs/device-list/>
/// - <http://www.lanana.org/docs/device-list/devices-2.6+.txt>
#[derive(Copy, Clone)]
pub struct DeviceIDs {
    pub major: u8,
    pub minor: u8,
}

/// A file’s status in a VCS repository.  Backend-agnostic: used for both
/// git and jj.
#[derive(PartialEq, Eq, Copy, Clone)]
pub enum VcsStatus {
    /// This file hasn’t changed since the last commit.
    NotModified,

    /// This file is new (added, not previously tracked).
    New,

    /// A file that’s been modified since the last commit.
    Modified,

    /// A deleted file. This can’t ever be shown, but it’s here anyway!
    Deleted,

    /// A file that the VCS has tracked a rename for.
    Renamed,

    /// A file that’s had its type (such as the file permissions) changed.
    TypeChange,

    /// A file that’s ignored (matches a VCS ignore pattern).
    Ignored,

    /// A file that’s updated but unmerged (conflicted).
    Conflicted,

    /// A file that’s been copied (jj tracks copies).
    Copied,

    /// A file that exists on disk but is not tracked by the VCS.
    Untracked,
}

/// A file’s complete VCS status.  For git this has separate staged and
/// unstaged status; for jj (which has no staging area) both fields hold
/// the same value.
#[derive(Copy, Clone)]
pub struct VcsFileStatus {
    pub staged: VcsStatus,
    pub unstaged: VcsStatus,
}

impl Default for VcsFileStatus {
    fn default() -> Self {
        Self {
            staged: VcsStatus::NotModified,
            unstaged: VcsStatus::NotModified,
        }
    }
}

// Convenience aliases so existing `f::Git` / `f::GitStatus` references
// continue to compile during the migration.
pub type Git = VcsFileStatus;
pub type GitStatus = VcsStatus;

/// BSD/macOS file flags from `st_flags` (see `chflags(1)`).
///
/// On platforms without `st_flags`, the flags value is always 0.
#[derive(Debug, Copy, Clone)]
pub struct FileFlags(pub u32);

impl FileFlags {
    /// Well-known BSD file flag bits.
    // User flags (UF_*)
    pub const UF_NODUMP: u32 = 0x0000_0001;
    pub const UF_IMMUTABLE: u32 = 0x0000_0002;
    pub const UF_APPEND: u32 = 0x0000_0004;
    pub const UF_OPAQUE: u32 = 0x0000_0008;
    pub const UF_NOUNLINK: u32 = 0x0000_0010; // FreeBSD
    pub const UF_COMPRESSED: u32 = 0x0000_0020; // macOS/FreeBSD
    pub const UF_TRACKED: u32 = 0x0000_0040; // FreeBSD
    pub const UF_SYSTEM: u32 = 0x0000_0080; // FreeBSD: Windows system bit
    pub const UF_SPARSE: u32 = 0x0000_0100; // FreeBSD
    pub const UF_OFFLINE: u32 = 0x0000_0200; // FreeBSD
    pub const UF_REPARSE: u32 = 0x0000_0400; // FreeBSD: Windows reparse point
    pub const UF_ARCHIVE: u32 = 0x0000_0800; // FreeBSD: user archive
    pub const UF_READONLY: u32 = 0x0000_1000; // FreeBSD: Windows readonly
    pub const UF_HIDDEN: u32 = 0x0000_8000; // macOS/FreeBSD
    // System flags (SF_*)
    pub const SF_ARCHIVED: u32 = 0x0001_0000;
    pub const SF_IMMUTABLE: u32 = 0x0002_0000;
    pub const SF_APPEND: u32 = 0x0004_0000;
    pub const SF_NOUNLINK: u32 = 0x0010_0000; // FreeBSD
    pub const SF_SNAPSHOT: u32 = 0x0020_0000; // FreeBSD

    // Linux file attributes (FS_IOC_GETFLAGS) — different bit layout.
    pub const FS_SECRM: u32 = 0x0000_0001;
    pub const FS_UNRM: u32 = 0x0000_0002;
    pub const FS_COMPR: u32 = 0x0000_0004;
    pub const FS_SYNC: u32 = 0x0000_0008;
    pub const FS_IMMUTABLE: u32 = 0x0000_0010;
    pub const FS_APPEND: u32 = 0x0000_0020;
    pub const FS_NODUMP: u32 = 0x0000_0040;
    pub const FS_NOATIME: u32 = 0x0000_0080;
    pub const FS_ENCRYPT: u32 = 0x0000_0800;
    pub const FS_JOURNAL: u32 = 0x0000_4000;
    pub const FS_NOTAIL: u32 = 0x0000_8000;
    pub const FS_DIRSYNC: u32 = 0x0001_0000;
    pub const FS_TOPDIR: u32 = 0x0002_0000;
    pub const FS_EXTENT: u32 = 0x0008_0000;
    pub const FS_VERITY: u32 = 0x0010_0000;
    pub const FS_NOCOW: u32 = 0x0080_0000;
    pub const FS_DAX: u32 = 0x0200_0000;
    pub const FS_PROJINHERIT: u32 = 0x2000_0000;
    pub const FS_CASEFOLD: u32 = 0x4000_0000;

    /// Return a short string representation of the flags.
    /// Returns "-" if no flags are set.
    ///
    /// On BSD/macOS, names match `chflags(1)` / `ls -lO` output.
    /// On Linux, names match `chattr(1)` / `lsattr` conventions.
    pub fn to_short_string(self) -> String {
        if self.0 == 0 {
            return "-".to_string();
        }

        let mut parts = Vec::new();

        #[cfg(any(target_os = "macos", target_os = "freebsd"))]
        {
            // BSD user flags — names match chflags(1).
            if self.0 & Self::UF_NODUMP != 0 {
                parts.push("nodump");
            }
            if self.0 & Self::UF_IMMUTABLE != 0 {
                parts.push("uchg");
            }
            if self.0 & Self::UF_APPEND != 0 {
                parts.push("uappnd");
            }
            if self.0 & Self::UF_OPAQUE != 0 {
                parts.push("opaque");
            }
            if self.0 & Self::UF_NOUNLINK != 0 {
                parts.push("uunlnk");
            }
            if self.0 & Self::UF_COMPRESSED != 0 {
                parts.push("compressed");
            }
            if self.0 & Self::UF_TRACKED != 0 {
                parts.push("tracked");
            }
            if self.0 & Self::UF_SYSTEM != 0 {
                parts.push("system");
            }
            if self.0 & Self::UF_SPARSE != 0 {
                parts.push("sparse");
            }
            if self.0 & Self::UF_OFFLINE != 0 {
                parts.push("offline");
            }
            if self.0 & Self::UF_REPARSE != 0 {
                parts.push("reparse");
            }
            if self.0 & Self::UF_ARCHIVE != 0 {
                parts.push("uarch");
            }
            if self.0 & Self::UF_READONLY != 0 {
                parts.push("rdonly");
            }
            if self.0 & Self::UF_HIDDEN != 0 {
                parts.push("hidden");
            }
            // BSD system flags
            if self.0 & Self::SF_ARCHIVED != 0 {
                parts.push("arch");
            }
            if self.0 & Self::SF_IMMUTABLE != 0 {
                parts.push("schg");
            }
            if self.0 & Self::SF_APPEND != 0 {
                parts.push("sappnd");
            }
            if self.0 & Self::SF_NOUNLINK != 0 {
                parts.push("sunlnk");
            }
            if self.0 & Self::SF_SNAPSHOT != 0 {
                parts.push("snapshot");
            }
        }

        #[cfg(target_os = "linux")]
        {
            // Linux attributes — names match chattr(1) conventions.
            if self.0 & Self::FS_SECRM != 0 {
                parts.push("secrm");
            }
            if self.0 & Self::FS_UNRM != 0 {
                parts.push("unrm");
            }
            if self.0 & Self::FS_COMPR != 0 {
                parts.push("compr");
            }
            if self.0 & Self::FS_SYNC != 0 {
                parts.push("sync");
            }
            if self.0 & Self::FS_IMMUTABLE != 0 {
                parts.push("immutable");
            }
            if self.0 & Self::FS_APPEND != 0 {
                parts.push("append");
            }
            if self.0 & Self::FS_NODUMP != 0 {
                parts.push("nodump");
            }
            if self.0 & Self::FS_NOATIME != 0 {
                parts.push("noatime");
            }
            if self.0 & Self::FS_ENCRYPT != 0 {
                parts.push("encrypt");
            }
            if self.0 & Self::FS_JOURNAL != 0 {
                parts.push("journal");
            }
            if self.0 & Self::FS_NOTAIL != 0 {
                parts.push("notail");
            }
            if self.0 & Self::FS_DIRSYNC != 0 {
                parts.push("dirsync");
            }
            if self.0 & Self::FS_TOPDIR != 0 {
                parts.push("topdir");
            }
            if self.0 & Self::FS_EXTENT != 0 {
                parts.push("extent");
            }
            if self.0 & Self::FS_VERITY != 0 {
                parts.push("verity");
            }
            if self.0 & Self::FS_NOCOW != 0 {
                parts.push("nocow");
            }
            if self.0 & Self::FS_DAX != 0 {
                parts.push("dax");
            }
            if self.0 & Self::FS_PROJINHERIT != 0 {
                parts.push("projinherit");
            }
            if self.0 & Self::FS_CASEFOLD != 0 {
                parts.push("casefold");
            }
        }

        if parts.is_empty() {
            // Unknown flags — show hex.
            format!("0x{:x}", self.0)
        } else {
            parts.join(",")
        }
    }
}

/// Per-directory VCS repository status, for the `--vcs-repos` column.
#[derive(Debug, Clone)]
pub enum VcsRepoStatus {
    /// This directory is not a VCS repository root.
    None,
    /// This directory is a VCS repository root.
    Repo {
        /// Which backend: "git" or "jj".
        backend: &'static str,
        /// Whether the working copy is clean (no uncommitted changes).
        clean: bool,
        /// The current branch or bookmark name (if detectable).
        branch: Option<String>,
    },
}