@@ -48,6 +48,9 @@ final class CodegenFile {
4848 private ?ICodegenFormatter $formatter ;
4949 private ?string $fileNamespace ;
5050 private Map < string , ?string > $useNamespaces = Map {};
51+ private ?string $shebang ;
52+ private ?string $pseudoMainHeader ;
53+ private ?string $pseudoMainFooter ;
5154
5255 public function __construct (
5356 private IHackCodegenConfig $config ,
@@ -218,7 +221,6 @@ public function getFormatter(): ?ICodegenFormatter {
218221 return $this -> formatter ;
219222 }
220223
221-
222224 private function getFileTypeDeclaration (): string {
223225 switch ($this -> fileType ) {
224226 case CodegenFileType :: PHP :
@@ -232,9 +234,63 @@ private function getFileTypeDeclaration(): string {
232234 }
233235 }
234236
237+ /**
238+ * Useful when creating scripts.
239+ *
240+ * You probably want:
241+ * setShebangLine('#!/usr/bin/env hhvm')
242+ */
243+ public function setShebangLine (string $shebang ): this {
244+ invariant (
245+ ! strpbrk ($shebang , " \n " ),
246+ " Expected single line" ,
247+ );
248+ invariant (
249+ Str :: startsWith($shebang , ' #!' ),
250+ ' Shebang lines start with #!' ,
251+ );
252+ $this -> shebang = $shebang ;
253+ return $this ;
254+ }
255+
256+ /**
257+ * Use to execute code before declarations.
258+ *
259+ * Useful for scripts; eg:
260+ * setPseudoMainHeader('require_once("vendor/autoload.php");');
261+ */
262+ public function setPseudoMainHeader (string $code ): this {
263+ $this -> pseudoMainHeader = $code ;
264+ return $this ;
265+ }
266+
267+ /**
268+ * Use to execute code after declarations.
269+ *
270+ * Useful for scripts; eg:
271+ * setPseudoMainFooter((new MyScript())->main($argv));
272+ */
273+ public function setPseudoMainFooter (string $code ): this {
274+ $this -> pseudoMainFooter = $code ;
275+ return $this ;
276+ }
277+
278+ private function assertNotHackStrictForExecutable (): void {
279+ invariant (
280+ $this -> fileType !== CodegenFileType :: HACK_STRICT ,
281+ " Hack Strict can't be used for executables" ,
282+ );
283+ }
284+
235285 public function render (): string {
236286 $builder = new HackBuilder ($this -> config );
237287
288+ $shebang = $this -> shebang ;
289+ if ($shebang !== null ) {
290+ $this -> assertNotHackStrictForExecutable();
291+ $builder -> addLine($shebang );
292+ }
293+
238294 $builder -> addLine($this -> getFileTypeDeclaration());
239295 $header = $this -> config -> getFileHeader();
240296 if ($header ) {
@@ -303,10 +359,20 @@ private function getContent(): string {
303359 ' namespace %s;' ,
304360 $this -> fileNamespace ,
305361 );
362+
306363 foreach ($this -> useNamespaces as $ns => $as ) {
307364 $builder -> addLine($as === null ? " use $ns ;" : " use $ns as $as ;" );
308365 }
309366
367+ $header = $this -> pseudoMainHeader ;
368+ if ($header !== null ) {
369+ $this -> assertNotHackStrictForExecutable();
370+ $builder
371+ -> ensureNewLine()
372+ -> add($header )
373+ -> ensureNewLine();
374+ }
375+
310376 foreach ($this -> beforeTypes as $type ) {
311377 $builder -> ensureNewLine()-> newLine();
312378 $builder -> add($type -> render());
@@ -329,6 +395,15 @@ private function getContent(): string {
329395 $builder -> ensureNewLine()-> newLine();
330396 $builder -> add($type -> render());
331397 }
398+
399+ $footer = $this -> pseudoMainFooter ;
400+ if ($footer !== null ) {
401+ $this -> assertNotHackStrictForExecutable();
402+ $builder
403+ -> ensureEmptyLine()
404+ -> add($footer )
405+ -> ensureNewLine();
406+ }
332407 return $builder -> getCode();
333408 }
334409
0 commit comments