workflow/publish: update to php-8.1
[mdref/mdref] / mdref / File.php
1 <?php
2
3 namespace mdref;
4
5 use function feof;
6 use function fgets;
7 use function fopen;
8 use function fseek;
9 use function strncmp;
10 use function substr;
11 use const SEEK_SET;
12
13 /**
14 * A ref entry file
15 */
16 class File {
17 /**
18 * @var resource
19 */
20 private $fd;
21
22 /**
23 * Open the file
24 *
25 * @param string $path
26 * @throws Exception
27 */
28 public function __construct(string $path) {
29 if (!$this->fd = fopen($path, "rb")) {
30 throw Exception::fromLastError();
31 }
32 }
33
34 /**
35 * Read the title of the refentry
36 *
37 * @return string
38 * @throws Exception
39 */
40 public function readTitle() : string {
41 if ($this->rewind(1)) {
42 return fgets($this->fd);
43 }
44 throw Exception::fromLastError();
45 }
46
47 /**
48 * Read the description (first line) of the refentry
49 *
50 * @return string
51 * @throws Exception
52 */
53 public function readDescription() : string {
54 if (!$this->rewind()) {
55 throw Exception::fromLastError();
56 }
57 if (false !== fgets($this->fd)
58 && (false !== fgets($this->fd))) {
59 return fgets($this->fd);
60 }
61 return "";
62 }
63
64 /**
65 * Read the full description (first section) of the refentry
66 *
67 * @return string
68 * @throws Exception
69 */
70 public function readFullDescription() : string {
71 $desc = $this->readDescription();
72 while (false !== ($line = fgets($this->fd))) {
73 if ($line[0] === "#") {
74 break;
75 } else {
76 $desc .= $line;
77 }
78 }
79 return $desc;
80 }
81
82 /**
83 * Read the first subsection of a global refentry
84 * @return string
85 */
86 public function readIntro() : string {
87 $intro = "";
88 if ($this->rewind()) {
89 $header = false;
90
91 while (!feof($this->fd)) {
92 if (false === ($line = fgets($this->fd))) {
93 break;
94 }
95 /* search first header and read until next header*/
96 if ($this->isHeading($line)) {
97 if ($header) {
98 break;
99 } else {
100 $header = true;
101 continue;
102 }
103 }
104 if ($header) {
105 $intro .= $line;
106 }
107 }
108 }
109 return $intro;
110 }
111
112 /**
113 * Read section of $title
114 *
115 * @param $title
116 * @return string
117 */
118 public function readSection(string $title) : string {
119 $section = "";
120 if ($this->rewind()) {
121 while (!feof($this->fd)) {
122 if (false === ($line = fgets($this->fd))) {
123 break;
124 }
125 /* search for heading with $title and read until next heading */
126 if ($this->isHeading($line, $title)) {
127 do {
128 if (false === $line = fgets($this->fd)) {
129 break;
130 }
131 if ($this->isHeading($line)) {
132 break;
133 }
134 $section .= $line;
135 } while (true);
136 }
137 }
138 }
139 return $section;
140 }
141
142 /**
143 * @param int $offset
144 * @return bool
145 */
146 private function rewind(int $offset = 0) : bool {
147 return 0 === fseek($this->fd, $offset, SEEK_SET);
148 }
149
150 /**
151 * @param string $line
152 * @param string $title
153 * @return bool
154 */
155 private function isHeading(string $line, ?string $title = null) : bool {
156 if ("## " !== substr($line, 0, 3)) {
157 return false;
158 }
159 if (isset($title)) {
160 return !strncmp(substr($line, 3), $title, strlen($title));
161 }
162 return true;
163 }
164 }