ApiDetails.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. <?php
  2. namespace Knuckles\Scribe\Extracting;
  3. use Knuckles\Scribe\Tools\ConsoleOutputUtils as c;
  4. use Knuckles\Scribe\Tools\DocumentationConfig;
  5. use Knuckles\Scribe\Tools\PathConfig;
  6. use Knuckles\Scribe\Tools\Utils as u;
  7. /**
  8. * Handles extracting other API details — intro, auth
  9. */
  10. class ApiDetails
  11. {
  12. private DocumentationConfig $config;
  13. private string $baseUrl;
  14. private bool $preserveUserChanges;
  15. private string $markdownOutputPath;
  16. private string $fileHashesTrackingFile;
  17. private array $lastKnownFileContentHashes = [];
  18. public function __construct(
  19. PathConfig $paths,
  20. DocumentationConfig $config = null,
  21. bool $preserveUserChanges = true
  22. ) {
  23. $this->markdownOutputPath = $paths->intermediateOutputPath(); //.scribe by default
  24. // If no config is injected, pull from global. Makes testing easier.
  25. $this->config = $config ?: new DocumentationConfig(config($paths->configName));
  26. $this->baseUrl = $this->config->get('base_url') ?? config('app.url');
  27. $this->preserveUserChanges = $preserveUserChanges;
  28. $this->fileHashesTrackingFile = $this->markdownOutputPath . '/.filehashes';
  29. $this->lastKnownFileContentHashes = [];
  30. }
  31. public function writeMarkdownFiles(): void
  32. {
  33. c::info('Extracting intro and auth Markdown files to: ' . $this->markdownOutputPath);
  34. if (!is_dir($this->markdownOutputPath)) {
  35. mkdir($this->markdownOutputPath, 0777, true);
  36. }
  37. $this->fetchFileHashesFromTrackingFile();
  38. $this->writeIntroMarkdownFile();
  39. $this->writeAuthMarkdownFile();
  40. $this->writeContentsTrackingFile();
  41. c::success('Extracted intro and auth Markdown files to: ' . $this->markdownOutputPath);
  42. }
  43. public function writeIntroMarkdownFile(): void
  44. {
  45. $introMarkdownFile = $this->markdownOutputPath . '/intro.md';
  46. if ($this->hasFileBeenModified($introMarkdownFile)) {
  47. if ($this->preserveUserChanges) {
  48. c::warn("Skipping modified file $introMarkdownFile");
  49. return;
  50. }
  51. c::warn("Discarding manual changes for file $introMarkdownFile because you specified --force");
  52. }
  53. $introMarkdown = view('scribe::markdown.intro')
  54. ->with('description', $this->config->get('description', ''))
  55. ->with('introText', $this->config->get('intro_text', ''))
  56. ->with('baseUrl', $this->baseUrl)->render();
  57. $this->writeMarkdownFileAndRecordHash($introMarkdownFile, $introMarkdown);
  58. }
  59. public function writeAuthMarkdownFile(): void
  60. {
  61. $authMarkdownFile = $this->markdownOutputPath . '/auth.md';
  62. if ($this->hasFileBeenModified($authMarkdownFile)) {
  63. if ($this->preserveUserChanges) {
  64. c::warn("Skipping modified file $authMarkdownFile");
  65. return;
  66. }
  67. c::warn("Discarding manual changes for file $authMarkdownFile because you specified --force");
  68. }
  69. $isAuthed = $this->config->get('auth.enabled', false);
  70. $authDescription = '';
  71. $extraInfo = '';
  72. if ($isAuthed) {
  73. $strategy = $this->config->get('auth.in');
  74. $parameterName = $this->config->get('auth.name');
  75. $authDescription = u::trans("scribe::auth.instruction.$strategy", [
  76. 'parameterName' => $parameterName,
  77. 'placeholder' => $this->config->get('auth.placeholder') ?: 'your-token']
  78. );
  79. $authDescription .= "\n\n".u::trans("scribe::auth.details");
  80. $extraInfo = $this->config->get('auth.extra_info', '');
  81. }
  82. $authMarkdown = view('scribe::markdown.auth', [
  83. 'isAuthed' => $isAuthed,
  84. 'authDescription' => $authDescription,
  85. 'extraAuthInfo' => $extraInfo,
  86. ])->render();
  87. $this->writeMarkdownFileAndRecordHash($authMarkdownFile, $authMarkdown);
  88. }
  89. /**
  90. */
  91. protected function writeMarkdownFileAndRecordHash(string $filePath, string $markdown): void
  92. {
  93. file_put_contents($filePath, $markdown);
  94. $this->lastKnownFileContentHashes[$filePath] = hash_file('md5', $filePath);
  95. }
  96. protected function writeContentsTrackingFile(): void
  97. {
  98. $content = "# GENERATED. YOU SHOULDN'T MODIFY OR DELETE THIS FILE.\n";
  99. $content .= "# Scribe uses this file to know when you change something manually in your docs.\n";
  100. $content .= collect($this->lastKnownFileContentHashes)
  101. ->map(fn($hash, $filePath) => "$filePath=$hash")->implode("\n");
  102. file_put_contents($this->fileHashesTrackingFile, $content);
  103. }
  104. protected function hasFileBeenModified(string $filePath): bool
  105. {
  106. if (!file_exists($filePath)) {
  107. return false;
  108. }
  109. $oldFileHash = $this->lastKnownFileContentHashes[$filePath] ?? null;
  110. if ($oldFileHash) {
  111. $currentFileHash = hash_file('md5', $filePath);
  112. // No danger of a timing attack, so no need for hash_equals() comparison
  113. $wasFileModifiedManually = $currentFileHash != $oldFileHash;
  114. return $wasFileModifiedManually;
  115. }
  116. return false;
  117. }
  118. protected function fetchFileHashesFromTrackingFile()
  119. {
  120. if (file_exists($this->fileHashesTrackingFile)) {
  121. $lastKnownFileHashes = explode("\n", trim(file_get_contents($this->fileHashesTrackingFile)));
  122. // First two lines are comments
  123. array_shift($lastKnownFileHashes);
  124. array_shift($lastKnownFileHashes);
  125. $this->lastKnownFileContentHashes = collect($lastKnownFileHashes)
  126. ->mapWithKeys(function ($line) {
  127. [$filePath, $hash] = explode("=", $line);
  128. return [$filePath => $hash];
  129. })->toArray();
  130. }
  131. }
  132. }