*/ public function parameters(): array { return [ 'type' => 'object', 'properties' => [ 'directory' => [ 'type' => 'string', 'description' => '要列出的目录路径,默认为当前目录。', 'default' => '.', ], 'include_hidden' => [ 'type' => 'boolean', 'description' => '是否包含以 . 开头的隐藏文件/目录(默认包含,兼容旧行为)。', 'default' => true, ], 'filter' => [ 'type' => 'string', 'description' => '过滤类型:all/files/directories。', 'enum' => ['all', 'files', 'directories'], 'default' => 'all', ], 'match' => [ 'type' => 'string', 'description' => '可选的通配符匹配(fnmatch),例如:*.php。', ], 'sort' => [ 'type' => 'string', 'description' => '排序:name_asc/name_desc/mtime_asc/mtime_desc。', 'enum' => ['name_asc', 'name_desc', 'mtime_asc', 'mtime_desc'], 'default' => 'name_asc', ], 'details' => [ 'type' => 'boolean', 'description' => '是否返回条目详情(name/type/size/modified_at)。', 'default' => false, ], 'limit' => [ 'type' => 'integer', 'description' => '最多返回条目数量(1-1000),默认 200。', 'minimum' => 1, 'maximum' => 1000, 'default' => 200, ], ], 'required' => [], ]; } /** * @param array $arguments * @return array */ public function execute(array $arguments): array { $directory = $this->stringOrDefault($arguments, 'directory', '.'); if (! is_dir($directory) || ! is_readable($directory)) { throw new InvalidArgumentException('目录不存在或不可读:'.$directory); } $includeHidden = $this->boolOrDefault($arguments, 'include_hidden', true); $details = $this->boolOrDefault($arguments, 'details', false); $filter = $this->enumOrDefault($arguments, 'filter', ['all', 'files', 'directories'], 'all'); $sort = $this->enumOrDefault($arguments, 'sort', ['name_asc', 'name_desc', 'mtime_asc', 'mtime_desc'], 'name_asc'); $match = $this->nullableString($arguments, 'match'); $limit = $this->intOrDefault($arguments, 'limit', 200); $limit = max(1, min(1000, $limit)); $rawEntries = $this->collectEntries($directory, $includeHidden, $filter, $match); $rawEntries = $this->sortEntries($rawEntries, $sort); $rawEntries = array_slice($rawEntries, 0, $limit); $entries = $details ? array_map(fn (array $entry) => $entry, $rawEntries) : array_map(fn (array $entry) => $entry['name'], $rawEntries); return [ 'directory' => $directory, 'entries' => array_values($entries), ]; } /** * @return array */ private function collectEntries(string $directory, bool $includeHidden, string $filter, ?string $match): array { $entries = []; try { $iterator = new FilesystemIterator($directory, FilesystemIterator::SKIP_DOTS); } catch (\Throwable $exception) { throw new InvalidArgumentException('无法读取目录:'.$directory, 0, $exception); } foreach ($iterator as $fileInfo) { if (! $fileInfo instanceof SplFileInfo) { continue; } $name = $fileInfo->getFilename(); if (! $includeHidden && str_starts_with($name, '.')) { continue; } if ($filter === 'files' && ! $fileInfo->isFile()) { continue; } if ($filter === 'directories' && ! $fileInfo->isDir()) { continue; } if (is_string($match) && $match !== '' && ! fnmatch($match, $name)) { continue; } $entries[] = [ 'name' => $name, 'type' => $this->entryType($fileInfo), 'size' => $fileInfo->isFile() ? $fileInfo->getSize() : null, 'modified_at' => $fileInfo->getMTime(), ]; } return $entries; } /** * @param array $entries * @return array */ private function sortEntries(array $entries, string $sort): array { usort($entries, function (array $left, array $right) use ($sort) { if ($sort === 'mtime_asc' || $sort === 'mtime_desc') { $compare = $left['modified_at'] <=> $right['modified_at']; } else { $compare = strnatcasecmp($left['name'], $right['name']); } if ($compare === 0) { $compare = strnatcasecmp($left['name'], $right['name']); } return ($sort === 'name_desc' || $sort === 'mtime_desc') ? -$compare : $compare; }); return $entries; } private function entryType(SplFileInfo $fileInfo): string { if ($fileInfo->isLink()) { return 'link'; } if ($fileInfo->isDir()) { return 'directory'; } if ($fileInfo->isFile()) { return 'file'; } return 'other'; } /** * @param array $arguments */ private function stringOrDefault(array $arguments, string $key, string $default): string { $value = $arguments[$key] ?? null; if ($value === null) { return $default; } if (! is_string($value) || trim($value) === '') { throw new InvalidArgumentException($key.' 必须是非空字符串'); } return $value; } /** * @param array $arguments * @param array $allowed */ private function enumOrDefault(array $arguments, string $key, array $allowed, string $default): string { $value = $arguments[$key] ?? null; if (! is_string($value)) { return $default; } return in_array($value, $allowed, true) ? $value : $default; } /** * @param array $arguments */ private function boolOrDefault(array $arguments, string $key, bool $default): bool { $value = $arguments[$key] ?? null; return is_bool($value) ? $value : $default; } /** * @param array $arguments */ private function intOrDefault(array $arguments, string $key, int $default): int { $value = $arguments[$key] ?? null; return is_int($value) ? $value : $default; } /** * @param array $arguments */ private function nullableString(array $arguments, string $key): ?string { $value = $arguments[$key] ?? null; return is_string($value) ? $value : null; } }