Parse and validate AT Protocol Lexicons with DTO generation for Laravel
at dev 5.5 kB view raw
1<?php 2 3namespace SocialDept\AtpSchema\Console; 4 5use Illuminate\Console\Command; 6use SocialDept\AtpSchema\Parser\SchemaLoader; 7 8class ListCommand extends Command 9{ 10 /** 11 * The name and signature of the console command. 12 */ 13 protected $signature = 'schema:list 14 {--filter= : Filter schemas by pattern (supports wildcards)} 15 {--type= : Filter by schema type (record, query, procedure, subscription)}'; 16 17 /** 18 * The console command description. 19 */ 20 protected $description = 'List all available ATProto Lexicon schemas'; 21 22 /** 23 * Execute the console command. 24 */ 25 public function handle(): int 26 { 27 $filter = $this->option('filter'); 28 $type = $this->option('type'); 29 30 try { 31 $sources = config('schema.sources', []); 32 $loader = new SchemaLoader($sources); 33 34 $schemas = $this->discoverSchemas($sources); 35 36 if ($filter) { 37 $schemas = $this->filterSchemas($schemas, $filter); 38 } 39 40 if ($type) { 41 $schemas = $this->filterByType($schemas, $type, $loader); 42 } 43 44 if (empty($schemas)) { 45 $this->info('No schemas found'); 46 47 return self::SUCCESS; 48 } 49 50 $this->info('Found '.count($schemas).' schema(s):'); 51 $this->newLine(); 52 53 $tableData = []; 54 55 foreach ($schemas as $nsid) { 56 try { 57 $document = $loader->load($nsid); 58 59 $schemaType = 'unknown'; 60 if ($document->isRecord()) { 61 $schemaType = 'record'; 62 } elseif ($document->isQuery()) { 63 $schemaType = 'query'; 64 } elseif ($document->isProcedure()) { 65 $schemaType = 'procedure'; 66 } elseif ($document->isSubscription()) { 67 $schemaType = 'subscription'; 68 } 69 70 $tableData[] = [ 71 $nsid, 72 $schemaType, 73 $document->description ?? '-', 74 ]; 75 } catch (\Exception $e) { 76 $tableData[] = [ 77 $nsid, 78 'error', 79 $e->getMessage(), 80 ]; 81 } 82 } 83 84 $this->table(['NSID', 'Type', 'Description'], $tableData); 85 86 return self::SUCCESS; 87 } catch (\Exception $e) { 88 $this->error('Failed to list schemas: '.$e->getMessage()); 89 90 if ($this->output->isVerbose()) { 91 $this->error($e->getTraceAsString()); 92 } 93 94 return self::FAILURE; 95 } 96 } 97 98 /** 99 * Discover all schema files in sources. 100 * 101 * @param array<string> $sources 102 * @return array<string> 103 */ 104 protected function discoverSchemas(array $sources): array 105 { 106 $schemas = []; 107 108 foreach ($sources as $source) { 109 if (! is_dir($source)) { 110 continue; 111 } 112 113 $schemas = array_merge($schemas, $this->scanDirectory($source)); 114 } 115 116 return array_unique($schemas); 117 } 118 119 /** 120 * Scan directory for schema files. 121 * 122 * @return array<string> 123 */ 124 protected function scanDirectory(string $directory, string $prefix = ''): array 125 { 126 $schemas = []; 127 $items = scandir($directory); 128 129 foreach ($items as $item) { 130 if ($item === '.' || $item === '..') { 131 continue; 132 } 133 134 $path = $directory.'/'.$item; 135 136 if (is_dir($path)) { 137 $newPrefix = $prefix ? $prefix.'.'.$item : $item; 138 $schemas = array_merge($schemas, $this->scanDirectory($path, $newPrefix)); 139 } elseif (pathinfo($item, PATHINFO_EXTENSION) === 'json' || pathinfo($item, PATHINFO_EXTENSION) === 'php') { 140 $name = pathinfo($item, PATHINFO_FILENAME); 141 $nsid = $prefix ? $prefix.'.'.$name : $name; 142 $schemas[] = $nsid; 143 } 144 } 145 146 return $schemas; 147 } 148 149 /** 150 * Filter schemas by pattern. 151 * 152 * @param array<string> $schemas 153 * @return array<string> 154 */ 155 protected function filterSchemas(array $schemas, string $pattern): array 156 { 157 $pattern = str_replace('*', '.*', preg_quote($pattern, '/')); 158 159 return array_filter($schemas, function ($nsid) use ($pattern) { 160 return preg_match("/^{$pattern}$/", $nsid); 161 }); 162 } 163 164 /** 165 * Filter schemas by type. 166 * 167 * @param array<string> $schemas 168 * @return array<string> 169 */ 170 protected function filterByType(array $schemas, string $type, SchemaLoader $loader): array 171 { 172 return array_filter($schemas, function ($nsid) use ($type, $loader) { 173 try { 174 $document = $loader->load($nsid); 175 176 return match ($type) { 177 'record' => $document->isRecord(), 178 'query' => $document->isQuery(), 179 'procedure' => $document->isProcedure(), 180 'subscription' => $document->isSubscription(), 181 default => false, 182 }; 183 } catch (\Exception) { 184 return false; 185 } 186 }); 187 } 188}