├── .gitignore ├── README.md ├── build.d └── src ├── luna ├── array.d ├── package.d ├── process.d └── string.d ├── main.d └── readline.d /.gitignore: -------------------------------------------------------------------------------- 1 | shed 2 | *.o 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shed 2 | 3 | > [!WARNING] 4 | > This is just a test project to try out -betterC feature of D programming language and it's not meant to be a serious production ready shell. 5 | 6 | Shell in D. 7 | 8 | ## Quick Start 9 | 10 | System dependencies: 11 | - [dmd](https://dlang.org/) 12 | - [readline](https://www.gnu.org/software/readline/) 13 | 14 | ```console 15 | $ rdmd build.d 16 | $ ./shed 17 | ``` 18 | -------------------------------------------------------------------------------- /build.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rdmd 2 | import std.process; 3 | import std.stdio; 4 | 5 | bool cmd(scope const(char[])[] args) { 6 | writeln("CMD: ", args); 7 | return wait(spawnProcess(args)) == 0; 8 | } 9 | 10 | int main() { 11 | if (!cmd([ 12 | "dmd", 13 | "-betterC", 14 | "-of=shed", 15 | "-I=./src/", 16 | "-i", 17 | "./src/main.d", 18 | "-L=-l:libreadline.a", 19 | "-L=-l:libhistory.a", 20 | "-L=-l:libncurses.a", 21 | ])) return 1; 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /src/luna/array.d: -------------------------------------------------------------------------------- 1 | // Dynamic Arrays in D 2 | import core.stdc.stdlib; 3 | 4 | struct Array(T, ulong INIT_CAPACITY = 256) { 5 | T* ptr; 6 | ulong length; 7 | ulong capacity; 8 | 9 | void append(T item) { 10 | if (length >= capacity) { 11 | if (capacity == 0) capacity = INIT_CAPACITY; 12 | else capacity *= 2; 13 | ptr = cast(T*)realloc(ptr, T.sizeof*capacity); 14 | } 15 | ptr[length++] = item; 16 | } 17 | 18 | void append(Range)(Range items) { 19 | foreach(item; items) append(item); 20 | } 21 | 22 | T opIndex(size_t i) { 23 | assert(i < length); 24 | return ptr[i]; 25 | } 26 | 27 | T opIndexAssign(T value, size_t i) { 28 | assert(i < length); 29 | ptr[i] = value; 30 | return value; 31 | } 32 | 33 | void opIndexOpAssign(string op)(T value, size_t i) { 34 | assert(i < length); 35 | mixin("ptr[i] "~op~"= value;"); 36 | } 37 | 38 | int opApply(scope int delegate(size_t i, ref T) dg) { 39 | for (size_t i = 0; i < length; ++i) { 40 | int result = dg(i, ptr[i]); 41 | if (result) return result; 42 | } 43 | return 0; 44 | } 45 | 46 | int opApply(scope int delegate(ref T) dg) { 47 | for (size_t i = 0; i < length; ++i) { 48 | int result = dg(ptr[i]); 49 | if (result) return result; 50 | } 51 | return 0; 52 | } 53 | 54 | T[] slice() { 55 | return ptr[0..length]; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/luna/package.d: -------------------------------------------------------------------------------- 1 | // D library that is closer to Earth. 2 | // 3 | // This is meant to be a library that you can use when -betterC is enabled. It is currently resides 4 | // directly in the source code of https://github.com/tsoding/shed until it become generally useful 5 | // (if ever). 6 | module luna; 7 | -------------------------------------------------------------------------------- /src/luna/process.d: -------------------------------------------------------------------------------- 1 | // Run external processes 2 | import array; 3 | import string; 4 | 5 | import core.stdc.stdio; 6 | import core.stdc.stdlib; 7 | import core.stdc.string; 8 | import core.stdc.errno; 9 | import core.sys.posix.unistd; 10 | import core.sys.posix.sys.wait; 11 | 12 | // TODO: support for Windows 13 | // Steal some code from https://github.com/tsoding/nob.h if needed 14 | 15 | int __WTERMSIG(int status) { return status & 0x7F; } 16 | bool WIFEXITED(int status) { return __WTERMSIG(status) == 0; } 17 | int WEXITSTATUS(int status) { return (status & 0xFF00) >> 8; } 18 | bool WIFSIGNALED(int status) { return (cast(byte)((status & 0x7F) + 1) >> 1) > 0; } 19 | int WTERMSIG(int status) { return status & 0x7F; } 20 | 21 | bool waitPid(pid_t proc) { 22 | if (proc < 0) return false; 23 | for (;;) { 24 | int wstatus = 0; 25 | if (waitpid(proc, &wstatus, 0) < 0) { 26 | fprintf(stderr, "could not wait on command (pid %d): %s\n", proc, strerror(errno)); 27 | return false; 28 | } 29 | 30 | if (WIFEXITED(wstatus)) { 31 | int exit_status = WEXITSTATUS(wstatus); 32 | if (exit_status != 0) { 33 | fprintf(stderr, "command exited with exit code %d\n", exit_status); 34 | return false; 35 | } 36 | 37 | break; 38 | } 39 | 40 | if (WIFSIGNALED(wstatus)) { 41 | fprintf(stderr, "command process was terminated by signal %d\n", WTERMSIG(wstatus)); 42 | return false; 43 | } 44 | } 45 | return true; 46 | } 47 | 48 | pid_t cmdRunAsync(inout(char)[][] args) { 49 | pid_t cpid = fork(); 50 | 51 | if (cpid < 0) { 52 | fprintf(stderr, "ERROR: Could not fork child process: %s\n", strerror(errno)); 53 | return -1; 54 | } 55 | 56 | if (cpid == 0) { 57 | // NOTE: This leaks a bit of memory in the child process. 58 | // But do we actually care? It's a one off leak anyway... 59 | Array!(char*) argsNull; 60 | foreach (ref arg; args) argsNull.append(dupAsCstr(arg)); 61 | argsNull.append(null); 62 | 63 | if (execvp(argsNull[0], argsNull.ptr) < 0) { 64 | fprintf(stderr, "ERROR: Could not exec child process: %s\n", strerror(errno)); 65 | exit(1); 66 | } 67 | assert(false, "cmdRunAsync: unreachable"); 68 | } 69 | 70 | return cpid; 71 | } 72 | 73 | bool cmdRunSync(inout(char)[][] args) 74 | { 75 | return waitPid(cmdRunAsync(args)); 76 | } 77 | -------------------------------------------------------------------------------- /src/luna/string.d: -------------------------------------------------------------------------------- 1 | // String operations 2 | import core.stdc.ctype; 3 | import core.stdc.stdlib; 4 | import core.stdc.string; 5 | 6 | Char[] trimLeft(Char)(Char[] s) { 7 | while (s.length > 0 && isspace(s[0])) { 8 | s = s[1..$]; 9 | } 10 | return s; 11 | } 12 | 13 | Char[] trimRight(Char)(Char[] s) { 14 | while (s.length > 0 && isspace(s[$-1])) { 15 | s = s[0..$-1]; 16 | } 17 | return s; 18 | } 19 | 20 | Char[] trim(Char)(Char[] s) { 21 | return trimRight(trimLeft(s)); 22 | } 23 | 24 | Char[] chopByDelim(Char)(ref Char[] s, Char delim) { 25 | size_t i = 0; 26 | while (i < s.length && s[i] != delim) i++; 27 | Char[] result; 28 | if (i >= s.length) { 29 | result = s; 30 | s = s[0..0]; 31 | } else { 32 | result = s[0..i]; 33 | s = s[i..$]; 34 | } 35 | return result; 36 | } 37 | 38 | char* dupAsCstr(Char)(Char[] s) { 39 | char* cstr = cast(char*)malloc((s.length + 1)*char.sizeof); 40 | assert(cstr != null); 41 | memcpy(cstr, s.ptr, s.length); 42 | cstr[s.length] = '\0'; 43 | return cstr; 44 | } 45 | -------------------------------------------------------------------------------- /src/main.d: -------------------------------------------------------------------------------- 1 | import core.stdc.string; 2 | import core.stdc.stdlib; 3 | import luna.array; 4 | import luna.string; 5 | import luna.process; 6 | import readline; 7 | 8 | void parseCommand(char[] s, ref Array!(char[]) args) { 9 | // TODO: string double quotes: echo "Hello, World" 10 | while (true) { 11 | s = trimLeft(s); 12 | if (s.length == 0) break; 13 | auto arg = chopByDelim(s, ' '); 14 | args.append(arg); 15 | } 16 | } 17 | 18 | const string HISTORY_NAME = ".shed_history"; 19 | Array!char historyPath; 20 | 21 | extern(C) void cleanup() 22 | { 23 | write_history(historyPath.ptr); 24 | } 25 | 26 | extern(C) int main(int argc, char **argv) { 27 | Array!(char[]) args; 28 | 29 | char *home = getenv("HOME"); 30 | assert(home != null); 31 | 32 | historyPath.append(home[0..strlen(home)]); 33 | historyPath.append("/"); 34 | historyPath.append(HISTORY_NAME); 35 | historyPath.append(0); 36 | 37 | read_history(historyPath.ptr); 38 | atexit(&cleanup); 39 | 40 | while (true) { 41 | const string prompt = "⛺ "; 42 | auto line = readline.readline(prompt.ptr); 43 | if (line == null) break; // Ctrl-D 44 | scope(exit) free(line); 45 | 46 | auto n = strlen(line); 47 | auto input = line[0..n]; 48 | 49 | args.length = 0; 50 | parseCommand(input, args); 51 | if (args.length == 0) continue; // empty command 52 | 53 | switch (args[0]) { 54 | case "exit": 55 | // TODO: support exit code argument 56 | exit(0); 57 | default: 58 | cmdRunSync(args.slice()); 59 | } 60 | 61 | add_history(line); 62 | } 63 | return 0; 64 | } 65 | -------------------------------------------------------------------------------- /src/readline.d: -------------------------------------------------------------------------------- 1 | extern(C) char *readline(const(char) *prompt); 2 | extern(C) void add_history(const(char) *line); 3 | extern(C) int read_history (const(char) *filename); 4 | extern(C) int write_history (const(char) *filename); 5 | --------------------------------------------------------------------------------