-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit d350b92
Showing
8 changed files
with
276 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
ColumnLimit: 0 | ||
PointerAlignment: Left |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
version: 2 | ||
updates: | ||
- package-ecosystem: github-actions | ||
directory: "/" | ||
schedule: | ||
interval: "weekly" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
name: ci | ||
|
||
on: [pull_request, push] | ||
|
||
jobs: | ||
check: | ||
runs-on: macos-15 | ||
steps: | ||
- uses: actions/checkout@v4 | ||
|
||
- name: build | ||
run: make CC=$(brew --prefix llvm@18)/bin/clang | ||
|
||
- name: run | ||
run: | | ||
touch x | ||
bin/can x | ||
if [[ -f x ]]; then | ||
exit 1 | ||
fi | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# General. | ||
.DS_Store | ||
bin/ | ||
|
||
# Prerequisites | ||
*.d | ||
|
||
# Object files | ||
*.o | ||
*.ko | ||
*.obj | ||
*.elf | ||
|
||
# Linker output | ||
*.ilk | ||
*.map | ||
*.exp | ||
|
||
# Precompiled Headers | ||
*.gch | ||
*.pch | ||
|
||
# Libraries | ||
*.lib | ||
*.a | ||
*.la | ||
*.lo | ||
|
||
# Shared objects (inc. Windows DLLs) | ||
*.dll | ||
*.so | ||
*.so.* | ||
*.dylib | ||
|
||
# Executables | ||
*.exe | ||
*.out | ||
*.app | ||
*.i*86 | ||
*.x86_64 | ||
*.hex | ||
|
||
# Debug files | ||
*.dSYM/ | ||
*.su | ||
*.idb | ||
*.pdb | ||
|
||
# Kernel Module Compile Results | ||
*.mod* | ||
*.cmd | ||
.tmp_versions/ | ||
modules.order | ||
Module.symvers | ||
Mkfile.old | ||
dkms.conf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
ISC License | ||
|
||
Copyright (c) 2023 Tshaka Lekholoane | ||
|
||
Permission to use, copy, modify, and/or distribute this software for any | ||
purpose with or without fee is hereby granted, provided that the above | ||
copyright notice and this permission notice appear in all copies. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH | ||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | ||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | ||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM | ||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR | ||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | ||
PERFORMANCE OF THIS SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
COMMIT = $(shell git rev-parse --short HEAD) | ||
DATE = $(shell date -u +"%Y.%m.%d") | ||
BUILD = $(shell printf "%s (%s)" "$(DATE)" "$(COMMIT)" ) | ||
CC = cc | ||
CFLAGS = -DCAN_BUILD="\"$(BUILD)\"" -O3 -Wall -Wextra -Wno-c++98-compat \ | ||
-Wno-cast-function-type-strict -Wno-declaration-after-statement \ | ||
-Wno-format-nonliteral -Wno-incompatible-pointer-types-discards-qualifiers \ | ||
-Wno-poison-system-directories -Wno-vla -framework Foundation -march=native \ | ||
-pedantic -std=c23 | ||
|
||
bin/can: src/main.o | ||
mkdir -p bin | ||
$(CC) $(CFLAGS) src/main.o -o bin/can | ||
|
||
src/main.o: | ||
|
||
.PHONY: clean | ||
clean: | ||
-rm bin/can src/main.o |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# `can` | ||
|
||
![Continuous Integration](https://github.com/tshakalekholoane/can/actions/workflows/ci.yaml/badge.svg) | ||
|
||
`can` is a macOS command-line utility that provides an alternative to the `rm` command. Instead of permanently deleting files and directories, `can` moves them to the user's Trash, allowing for easy recovery if needed. | ||
|
||
## Usage | ||
|
||
``` | ||
usage: can [-h | -V] [--] file ... | ||
``` | ||
|
||
## Installation | ||
|
||
### Source | ||
|
||
The application can be built from source by cloning the repository and running the following commands which require working versions of [Make](https://www.gnu.org/software/make/) and a C compiler with C23 support. | ||
|
||
> [!WARNING] | ||
> GCC 14 incorrectly uses `__STDC_VERSION__ == 202000` for C23 [^1], which will produce an error even when the `-std=c23` flag is set. | ||
```shell | ||
git clone https://github.com/tshakalekholoane/can && cd can | ||
make | ||
``` | ||
|
||
[^1]: See https://stackoverflow.com/a/78582932. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
#ifndef __APPLE__ | ||
#error "This program is intended to only run on macOS." | ||
#endif | ||
|
||
#if __STDC_VERSION__ < 202000 | ||
#error "This code requires C23 or later." | ||
#endif | ||
|
||
#include <errno.h> | ||
#include <getopt.h> | ||
#include <objc/message.h> | ||
#include <pwd.h> | ||
#include <stdio.h> | ||
#include <string.h> | ||
#include <unistd.h> | ||
|
||
#ifndef CAN_BUILD | ||
#define CAN_BUILD "TIP" | ||
#endif | ||
|
||
#define shift(n) \ | ||
do { \ | ||
argc -= (n); \ | ||
argv += (n); \ | ||
} while (false) | ||
|
||
#define unlikely(c) __builtin_expect(!!(c), false) | ||
|
||
static const char* usage = "usage: can [-h | -V] [--] file ..."; | ||
|
||
// Objective-C messaging primitives require that functions be cast to an | ||
// appropriate function pointer type before being called [1]. | ||
// | ||
// [1]: https://tshaka.dev/a/7hq9i9 | ||
|
||
static id file_manager_default_manager(void) { | ||
auto file_manager = objc_getClass("NSFileManager"); | ||
typedef id (*send_type)(Class, SEL); | ||
auto func = (send_type)objc_msgSend; | ||
return func(file_manager, sel_registerName("defaultManager")); | ||
} | ||
|
||
static id file_manager_string_with_file_system_representation(id self, const char string[static 1]) { | ||
typedef id (*send_type)(id, SEL, const char*, unsigned long); | ||
auto func = (send_type)objc_msgSend; | ||
return func(self, sel_registerName("stringWithFileSystemRepresentation:length:"), string, strlen(string)); | ||
} | ||
|
||
static bool file_manager_trash_item_at_url(id self, id url, id* err) { | ||
typedef bool (*send_type)(id, SEL, id, id, id*); | ||
auto func = (send_type)objc_msgSend; | ||
return func(self, sel_registerName("trashItemAtURL:resultingItemURL:error:"), url, nullptr, err); | ||
} | ||
|
||
static id error_localized_description(id self) { | ||
typedef id (*send_type)(id, SEL); | ||
auto func = (send_type)objc_msgSend; | ||
return func(self, sel_registerName("localizedDescription")); | ||
} | ||
|
||
static const char* string_utf8_string(id self) { | ||
typedef const char* (*send_type)(id, SEL); | ||
auto func = (send_type)objc_msgSend; | ||
return func(self, sel_registerName("UTF8String")); | ||
} | ||
|
||
static id url_file_url_with_path(id string) { | ||
auto url = objc_getClass("NSURL"); | ||
typedef id (*send_type)(Class, SEL, id); | ||
auto func = (send_type)objc_msgSend; | ||
return func(url, sel_registerName("fileURLWithPath:"), string); | ||
} | ||
|
||
int main(int argc, char* argv[argc + 1]) { | ||
int opt; | ||
while ((opt = getopt(argc, argv, "hV")) != -1) { | ||
switch (opt) { | ||
case 'h': | ||
printf("%s\n", usage); | ||
return EXIT_SUCCESS; | ||
case 'V': | ||
printf("can %s\n", CAN_BUILD); | ||
return EXIT_SUCCESS; | ||
default: | ||
fprintf(stderr, "%s\n", usage); | ||
return EXIT_FAILURE; | ||
} | ||
} | ||
if (unlikely(argc == 1)) { | ||
fprintf(stderr, "%s\n", usage); | ||
return EXIT_FAILURE; | ||
} | ||
shift(1); | ||
|
||
for (ssize_t i = 0; i < (ssize_t)argc; i++) { | ||
auto name = argv[i]; | ||
if (unlikely(strcmp(name, ".") == 0 || strcmp(name, "..") == 0 || strcmp(name, "/") == 0)) { | ||
fprintf(stderr, "\"/\", \".\", and \"..\" may not be removed.\n"); | ||
return EXIT_FAILURE; | ||
} | ||
} | ||
|
||
// Avoid using the root user's trash when invoked with sudo. | ||
auto superuser = getenv("SUDO_USER"); | ||
if (unlikely(superuser != nullptr)) { | ||
auto entry = getpwnam(superuser); | ||
if (unlikely(!entry || seteuid(entry->pw_uid))) { | ||
fprintf(stderr, "%s\n", strerror(errno)); | ||
return EXIT_FAILURE; | ||
} | ||
} | ||
|
||
// Ignore flag separator. | ||
if (unlikely(strcmp(argv[0], "--") == 0)) | ||
shift(1); | ||
|
||
auto exit_code = EXIT_SUCCESS; | ||
auto file_manager = file_manager_default_manager(); | ||
for (ssize_t i = 0; i < (ssize_t)argc; i++) { | ||
auto path = file_manager_string_with_file_system_representation(file_manager, argv[i]); | ||
auto url = url_file_url_with_path(path); | ||
id err; | ||
if (unlikely(!file_manager_trash_item_at_url(file_manager, url, &err))) { | ||
auto description = error_localized_description(err); | ||
fprintf(stderr, "%s\n", string_utf8_string(description)); | ||
exit_code = EXIT_FAILURE; | ||
} | ||
} | ||
return exit_code; | ||
} |