|
| 1 | +--- |
| 2 | +date: '2026-04-29T14:00:00+02:00' |
| 3 | +draft: false |
| 4 | +title: 'File and Resource Handling' |
| 5 | +weight: 12 |
| 6 | +--- |
| 7 | + |
| 8 | +When an `@Option`, `@Argument`, or `@Arguments` field is typed as `Resource`, `File`, or `Path`, Aesh automatically provides file path completion, string-to-file conversion, and `<file>` usage hints in help text -- with no extra configuration needed. |
| 9 | + |
| 10 | +## Quick Comparison |
| 11 | + |
| 12 | +```java |
| 13 | +// Plain string — no completion, no path resolution |
| 14 | +@Option(description = "Output path") |
| 15 | +private String output; |
| 16 | + |
| 17 | +// Resource — automatic file completion + path resolution + I/O API |
| 18 | +@Option(description = "Output path") |
| 19 | +private Resource output; |
| 20 | + |
| 21 | +// File — automatic file completion + conversion |
| 22 | +@Option(description = "Output path") |
| 23 | +private File output; |
| 24 | + |
| 25 | +// Path — automatic file completion + conversion |
| 26 | +@Option(description = "Output path") |
| 27 | +private Path output; |
| 28 | +``` |
| 29 | + |
| 30 | +All three file-typed variants (`Resource`, `File`, `Path`) gain: |
| 31 | + |
| 32 | +| Feature | String | Resource / File / Path | |
| 33 | +|---|---|---| |
| 34 | +| Tab completion | None | File paths from filesystem | |
| 35 | +| Type conversion | None | Automatic string-to-type | |
| 36 | +| Help text | `<output>` | `<file>` | |
| 37 | +| Shell redirect completion | No | Yes (after `>`, `>>`, `<`) | |
| 38 | + |
| 39 | +## Resource Interface |
| 40 | + |
| 41 | +`Resource` is Aesh's filesystem abstraction. It provides a richer API than `File` or `Path` with built-in glob expansion, filtering, and I/O streams: |
| 42 | + |
| 43 | +```java |
| 44 | +@CommandDefinition(name = "cat", description = "Display file contents") |
| 45 | +public class CatCommand implements Command<CommandInvocation> { |
| 46 | + |
| 47 | + @Argument(description = "File to display", required = true) |
| 48 | + private Resource file; |
| 49 | + |
| 50 | + @Override |
| 51 | + public CommandResult execute(CommandInvocation invocation) throws CommandException { |
| 52 | + if (!file.exists()) { |
| 53 | + invocation.println("File not found: " + file.getName()); |
| 54 | + return CommandResult.FAILURE; |
| 55 | + } |
| 56 | + try (BufferedReader reader = new BufferedReader( |
| 57 | + new InputStreamReader(file.read()))) { |
| 58 | + String line; |
| 59 | + while ((line = reader.readLine()) != null) { |
| 60 | + invocation.println(line); |
| 61 | + } |
| 62 | + } catch (IOException e) { |
| 63 | + throw new CommandException("Failed to read file", e); |
| 64 | + } |
| 65 | + return CommandResult.SUCCESS; |
| 66 | + } |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +Tab completion works immediately: |
| 71 | + |
| 72 | +``` |
| 73 | +myshell$ cat sr<TAB> |
| 74 | +src/ |
| 75 | +myshell$ cat src/main<TAB> |
| 76 | +src/main/java/ src/main/resources/ |
| 77 | +``` |
| 78 | + |
| 79 | +### API Reference |
| 80 | + |
| 81 | +**Path and name:** |
| 82 | + |
| 83 | +| Method | Returns | Description | |
| 84 | +|---|---|---| |
| 85 | +| `getName()` | `String` | Filename only (no directory) | |
| 86 | +| `getAbsolutePath()` | `String` | Full absolute path | |
| 87 | +| `getParent()` | `Resource` | Parent directory | |
| 88 | +| `newInstance(String)` | `Resource` | Factory -- creates a new Resource from a path | |
| 89 | + |
| 90 | +**Type checks:** |
| 91 | + |
| 92 | +| Method | Returns | Description | |
| 93 | +|---|---|---| |
| 94 | +| `exists()` | `boolean` | Does the file/directory exist? | |
| 95 | +| `isLeaf()` | `boolean` | Is it a file (not a directory)? | |
| 96 | +| `isDirectory()` | `boolean` | Is it a directory? | |
| 97 | +| `isSymbolicLink()` | `boolean` | Is it a symbolic link? | |
| 98 | +| `readSymbolicLink()` | `Resource` | Resolves symlink target | |
| 99 | + |
| 100 | +**Directory listing:** |
| 101 | + |
| 102 | +| Method | Returns | Description | |
| 103 | +|---|---|---| |
| 104 | +| `list()` | `List<Resource>` | Lists directory contents | |
| 105 | +| `list(ResourceFilter)` | `List<Resource>` | Lists with filtering | |
| 106 | +| `listRoots()` | `List<Resource>` | Filesystem roots | |
| 107 | +| `resolve(Resource cwd)` | `List<Resource>` | Resolves path with glob expansion (`~`, `*`, `?`) | |
| 108 | + |
| 109 | +**I/O:** |
| 110 | + |
| 111 | +| Method | Returns | Description | |
| 112 | +|---|---|---| |
| 113 | +| `read()` | `InputStream` | Opens file for reading | |
| 114 | +| `write(boolean append)` | `OutputStream` | Opens file for writing (append or overwrite) | |
| 115 | + |
| 116 | +**File operations:** |
| 117 | + |
| 118 | +| Method | Returns | Description | |
| 119 | +|---|---|---| |
| 120 | +| `mkdirs()` | `boolean` | Creates directory and parents | |
| 121 | +| `delete()` | `boolean` | Deletes file or empty directory | |
| 122 | +| `move(Resource)` | `void` | Moves/renames file | |
| 123 | +| `copy(Resource)` | `Resource` | Copies file or directory | |
| 124 | + |
| 125 | +**Metadata:** |
| 126 | + |
| 127 | +| Method | Returns | Description | |
| 128 | +|---|---|---| |
| 129 | +| `lastModified()` | `long` | Last modified time (ms) | |
| 130 | +| `setLastModified(long)` | `boolean` | Sets last modified time | |
| 131 | +| `lastAccessed()` | `long` | Last accessed time (ms) | |
| 132 | + |
| 133 | +## FileResource |
| 134 | + |
| 135 | +`FileResource` is the default `Resource` implementation backed by `java.io.File`. When a command field is typed as `Resource`, the input string is automatically converted to a `FileResource` resolved relative to the current working directory. |
| 136 | + |
| 137 | +```java |
| 138 | +@Option(description = "Config file") |
| 139 | +private Resource config; |
| 140 | + |
| 141 | +// In execute(): |
| 142 | +if (config.isLeaf() && config.exists()) { |
| 143 | + // read the file |
| 144 | + InputStream in = config.read(); |
| 145 | +} |
| 146 | + |
| 147 | +// Access the underlying File if needed: |
| 148 | +File javaFile = ((FileResource) config).getFile(); |
| 149 | +``` |
| 150 | + |
| 151 | +You can also construct `FileResource` directly: |
| 152 | + |
| 153 | +```java |
| 154 | +Resource home = new FileResource(System.getProperty("user.home")); |
| 155 | +Resource config = new FileResource("/etc/myapp/config.yml"); |
| 156 | +Resource relative = new FileResource("data/output.csv"); |
| 157 | +``` |
| 158 | + |
| 159 | +## Resource Filters |
| 160 | + |
| 161 | +Resource filters control which files appear in directory listings and tab completion. Four built-in filters are available: |
| 162 | + |
| 163 | +| Filter | Accepts | |
| 164 | +|---|---| |
| 165 | +| `AllResourceFilter` | Everything (default) | |
| 166 | +| `DirectoryResourceFilter` | Directories only | |
| 167 | +| `LeafResourceFilter` | Files only (not directories) | |
| 168 | +| `NoDotNamesFilter` | Files not starting with `.` | |
| 169 | + |
| 170 | +### Filtering Completion Candidates |
| 171 | + |
| 172 | +To restrict tab completion to directories only, provide a custom completer using `FileOptionCompleter` with a filter: |
| 173 | + |
| 174 | +```java |
| 175 | +@CommandDefinition(name = "cd", description = "Change directory") |
| 176 | +public class CdCommand implements Command<CommandInvocation> { |
| 177 | + |
| 178 | + @Argument(description = "Target directory", |
| 179 | + completer = DirectoryCompleter.class) |
| 180 | + private Resource target; |
| 181 | + |
| 182 | + @Override |
| 183 | + public CommandResult execute(CommandInvocation invocation) { |
| 184 | + if (target != null && target.isDirectory()) { |
| 185 | + invocation.getAeshContext().setCurrentWorkingDirectory(target); |
| 186 | + } |
| 187 | + return CommandResult.SUCCESS; |
| 188 | + } |
| 189 | +} |
| 190 | + |
| 191 | +public class DirectoryCompleter extends FileOptionCompleter { |
| 192 | + public DirectoryCompleter() { |
| 193 | + super(new DirectoryResourceFilter()); |
| 194 | + } |
| 195 | +} |
| 196 | +``` |
| 197 | + |
| 198 | +Now tab completion only suggests directories: |
| 199 | + |
| 200 | +``` |
| 201 | +myshell$ cd sr<TAB> |
| 202 | +src/ |
| 203 | +myshell$ cd src/<TAB> |
| 204 | +src/main/ src/test/ |
| 205 | +``` |
| 206 | + |
| 207 | +### Filtering Directory Listings |
| 208 | + |
| 209 | +Filters work with `Resource.list()` too: |
| 210 | + |
| 211 | +```java |
| 212 | +// List only non-hidden files |
| 213 | +List<Resource> visible = directory.list(new NoDotNamesFilter()); |
| 214 | + |
| 215 | +// List only subdirectories |
| 216 | +List<Resource> dirs = directory.list(new DirectoryResourceFilter()); |
| 217 | +``` |
| 218 | + |
| 219 | +### Custom Filters |
| 220 | + |
| 221 | +Implement `ResourceFilter` for custom logic: |
| 222 | + |
| 223 | +```java |
| 224 | +public class JavaFileFilter implements ResourceFilter { |
| 225 | + @Override |
| 226 | + public boolean accept(Resource resource) { |
| 227 | + return resource.isDirectory() || resource.getName().endsWith(".java"); |
| 228 | + } |
| 229 | +} |
| 230 | +``` |
| 231 | + |
| 232 | +## Glob Expansion |
| 233 | + |
| 234 | +`Resource.resolve()` supports shell-style globs: |
| 235 | + |
| 236 | +```java |
| 237 | +Resource cwd = invocation.getAeshContext().getCurrentWorkingDirectory(); |
| 238 | +Resource pattern = new FileResource("src/**/*.java"); |
| 239 | +List<Resource> matches = pattern.resolve(cwd); |
| 240 | +``` |
| 241 | + |
| 242 | +The `~` character is expanded to the user's home directory: |
| 243 | + |
| 244 | +```java |
| 245 | +Resource home = new FileResource("~/documents"); |
| 246 | +List<Resource> resolved = home.resolve(cwd); |
| 247 | +``` |
| 248 | + |
| 249 | +## Resource vs File vs Path |
| 250 | + |
| 251 | +| | `Resource` | `File` | `Path` | |
| 252 | +|---|---|---|---| |
| 253 | +| **Tab completion** | Automatic | Automatic | Automatic | |
| 254 | +| **API richness** | Full (listing, glob, I/O, copy/move) | Java standard | Java NIO | |
| 255 | +| **Glob expansion** | Built-in via `resolve()` | Manual | Via `PathMatcher` | |
| 256 | +| **Filtering** | `ResourceFilter` integration | Manual | Manual | |
| 257 | +| **Abstraction** | Pluggable (file, pipeline, remote) | Local filesystem only | Local filesystem only | |
| 258 | +| **Recommendation** | Use for commands that work with files | Use when you need `java.io.File` APIs | Use when you need `java.nio.file` APIs | |
| 259 | + |
| 260 | +Use `Resource` when your command needs filesystem operations and you want the richest API. Use `File` or `Path` if you need compatibility with existing Java APIs that expect those types. |
| 261 | + |
| 262 | +## See Also |
| 263 | + |
| 264 | +- [Options](../options) -- Defining command options |
| 265 | +- [Arguments](../arguments) -- Positional arguments |
| 266 | +- [Completers](../completers) -- Custom tab completion |
| 267 | +- [Converters](../converters) -- Built-in and custom type conversion |
0 commit comments