1: <?php declare(strict_types = 1);
2:
3: namespace ApiGen\Renderer;
4:
5: use ApiGen\Index\NamespaceIndex;
6: use ApiGen\Info\AliasInfo;
7: use ApiGen\Info\ClassLikeInfo;
8: use ApiGen\Info\ConstantInfo;
9: use ApiGen\Info\EnumCaseInfo;
10: use ApiGen\Info\FunctionInfo;
11: use ApiGen\Info\MemberInfo;
12: use ApiGen\Info\MethodInfo;
13: use ApiGen\Info\ParameterInfo;
14: use ApiGen\Info\PropertyInfo;
15:
16: use function assert;
17: use function get_debug_type;
18: use function sprintf;
19: use function str_starts_with;
20: use function strlen;
21: use function strrpos;
22: use function strtr;
23: use function substr;
24:
25: use const DIRECTORY_SEPARATOR;
26:
27:
28: class UrlGenerator
29: {
30: public function __construct(
31: protected string $baseDir,
32: protected string $baseUrl,
33: ) {
34: }
35:
36:
37: public function getRelativePath(string $path): string
38: {
39: if (str_starts_with($path, $this->baseDir)) {
40: return substr($path, strlen($this->baseDir) + 1);
41:
42: } else {
43: throw new \LogicException("{$path} does not start with {$this->baseDir}");
44: }
45: }
46:
47:
48: public function getAssetUrl(string $name): string
49: {
50: return $this->baseUrl . $this->getAssetPath($name);
51: }
52:
53:
54: public function getAssetPath(string $name): string
55: {
56: return "assets/$name";
57: }
58:
59:
60: public function getIndexUrl(): string
61: {
62: return $this->baseUrl . $this->getIndexPath();
63: }
64:
65:
66: public function getIndexPath(): string
67: {
68: return 'index.html';
69: }
70:
71:
72: public function getTreeUrl(): string
73: {
74: return $this->baseUrl . $this->getTreePath();
75: }
76:
77:
78: public function getTreePath(): string
79: {
80: return 'tree.html';
81: }
82:
83:
84: public function getSitemapPath(): string
85: {
86: return 'sitemap.xml';
87: }
88:
89:
90: public function getSitemapUrl(): string
91: {
92: return $this->baseUrl . $this->getSitemapPath();
93: }
94:
95:
96: public function getNamespaceUrl(NamespaceIndex $namespace): string
97: {
98: return $this->baseUrl . $this->getNamespacePath($namespace);
99: }
100:
101:
102: public function getNamespacePath(NamespaceIndex $namespace): string
103: {
104: return 'namespace-' . strtr($namespace->name->full ?: 'none', '\\', '.') . '.html';
105: }
106:
107:
108: public function getClassLikeUrl(ClassLikeInfo $classLike): string
109: {
110: return $this->baseUrl . $this->getClassLikePath($classLike);
111: }
112:
113:
114: public function getClassLikePath(ClassLikeInfo $classLike): string
115: {
116: return strtr($classLike->name->full, '\\', '.') . '.html';
117: }
118:
119:
120: public function getClassLikeSourceUrl(ClassLikeInfo $classLike): string
121: {
122: assert($classLike->file !== null);
123: return $this->getSourceUrl($classLike->file, $classLike->startLine, null); // intentionally not passing endLine
124: }
125:
126:
127: public function getMemberUrl(ClassLikeInfo $classLike, MemberInfo $member): string
128: {
129: return $this->getClassLikeUrl($classLike) . '#' . $this->getMemberAnchor($member);
130: }
131:
132:
133: public function getMemberAnchor(MemberInfo $member): string
134: {
135: if ($member instanceof ConstantInfo || $member instanceof EnumCaseInfo) {
136: return $member->name;
137:
138: } elseif ($member instanceof PropertyInfo) {
139: return '$' . $member->name;
140:
141: } elseif ($member instanceof MethodInfo) {
142: return '_' . $member->name;
143:
144: } else {
145: throw new \LogicException(sprintf('Unexpected member type %s', get_debug_type($member)));
146: }
147: }
148:
149:
150: public function getMemberSourceUrl(ClassLikeInfo $classLike, MemberInfo $member): string
151: {
152: assert($classLike->file !== null);
153: return $this->getSourceUrl($classLike->file, $member->startLine, $member->endLine);
154: }
155:
156:
157: public function getAliasUrl(ClassLikeInfo $classLike, AliasInfo $alias): string
158: {
159: return $this->getClassLikeUrl($classLike) . '#' . $this->getAliasAnchor($alias);
160: }
161:
162:
163: public function getAliasAnchor(AliasInfo $alias): string
164: {
165: return '~' . $alias->name;
166: }
167:
168:
169: public function getAliasSourceUrl(ClassLikeInfo $classLike, AliasInfo $alias): string
170: {
171: assert($classLike->file !== null);
172: return $this->getSourceUrl($classLike->file, $alias->startLine, $alias->endLine);
173: }
174:
175:
176: public function getFunctionUrl(FunctionInfo $function): string
177: {
178: return $this->baseUrl . $this->getFunctionPath($function);
179: }
180:
181:
182: public function getFunctionPath(FunctionInfo $function): string
183: {
184: return 'function-' . strtr($function->name->full, '\\', '.') . '.html';
185: }
186:
187:
188: public function getFunctionSourceUrl(FunctionInfo $function): string
189: {
190: assert($function->file !== null);
191: return $this->getSourceUrl($function->file, $function->startLine, $function->endLine);
192: }
193:
194:
195: public function getParameterAnchor(ParameterInfo $parameter): string
196: {
197: return '$' . $parameter->name;
198: }
199:
200:
201: public function getSourceUrl(string $path, ?int $startLine, ?int $endLine): string
202: {
203: if ($startLine === null) {
204: $fragment = '';
205:
206: } elseif ($endLine === null || $endLine === $startLine) {
207: $fragment = "#$startLine";
208:
209: } else {
210: $fragment = "#$startLine-$endLine";
211: }
212:
213: return $this->baseUrl . $this->getSourcePath($path) . $fragment;
214: }
215:
216:
217: public function getSourcePath(string $path): string
218: {
219: $relativePath = $this->getRelativePath($path);
220: $relativePathWithoutExtension = substr($relativePath, 0, strrpos($relativePath, '.') ?: null);
221: return 'source-' . strtr($relativePathWithoutExtension, DIRECTORY_SEPARATOR, '.') . '.html';
222: }
223: }
224: