From edf0234056159d2cce329bc391e791755403f0a4 Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 3 Apr 2024 02:11:07 +0300 Subject: [PATCH 01/56] implement TLS/socket client --- README.md | 52 +++++++++++- src/Client/Request.php | 178 ++++++++++++++++++++++++++++++++++++++++ src/Client/Response.php | 58 +++++++++++++ 3 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 src/Client/Request.php create mode 100644 src/Client/Response.php diff --git a/README.md b/README.md index 1a69944..7979afb 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,57 @@ PHP 8 Library for Gemini Protocol ## Usage ``` -composer require yggverse/gemini:dev-main +composer require yggverse/gemini +``` + +## Client + +PHP interface for Gemini protocol queries by TLS socket connection + +### Request + +``` +$request = new \Yggverse\Gemini\Client\Request( + 'gemini://betahowto.duckdns.org:1965/archive' +); +``` + +#### Request::setHost +#### Request::getHost +#### Request::setPort +#### Request::getPort +#### Request::setPath +#### Request::getPath +#### Request::setQuery +#### Request::getQuery +#### Request::getResponse + +Execute requested URL and return raw response + +``` +var_dump( + $request->getResponse() +); +``` + +### Response + +This class provides additional features for the raw response operations + +``` +$response = new \Yggverse\Gemini\Client\Response( + $request->getResponse() +); +``` + +#### Response::getCode +#### Response::getMeta +#### Response::getBody + +``` +var_dump( + $response->getBody() +); ``` ## DokuWiki diff --git a/src/Client/Request.php b/src/Client/Request.php new file mode 100644 index 0000000..400693b --- /dev/null +++ b/src/Client/Request.php @@ -0,0 +1,178 @@ +setHost( + $host + ); + } + + else + { + throw new Exception(); // @TODO + } + + if ($port = parse_url($url, PHP_URL_PORT)) + { + $this->setPort( + $port + ); + } + + else + { + $this->setPort( + 1965 + ); + } + + if ($path = parse_url($url, PHP_URL_PATH)) + { + $this->setPath( + $path + ); + } + + else + { + $this->setPath( + '' + ); + } + + if ($query = parse_url($url, PHP_URL_QUERY)) + { + $this->setQuery( + $query + ); + } + + else + { + $this->setQuery( + '' + ); + } + } + + public function setHost(string $value): void + { + $this->_host = $value; + } + + public function getHost(): string + { + return $this->_host; + } + + public function setPort(int $value): void + { + $this->_port = $value; + } + + public function getPort(): int + { + return $this->_port; + } + + public function setPath(string $value): void + { + $this->_path = $value; + } + + public function getPath(): string + { + return $this->_path; + } + + public function setQuery(string $value): void + { + $this->_query = $value; + } + + public function getQuery(): string + { + return $this->_query; + } + + public function getResponse( + int $timeout = 30, // socket timeout, useful for offline resources + ?int $limit = null, // content length, null for unlimited + int $chunk = 1024, // chunk size, it's better change it later to 1 @TODO + int &$length = 0, // current response length, do not change without special needs + ?int &$code = null, // error code for debug + ?string &$message = null, // error message for debug + string &$response = '' // response init, also returning by this method + ): ?string + { + $connection = stream_socket_client( + sprintf( + 'tls://%s:%d', + $this->_host, + $this->_port + ), + $code, + $message, + $timeout, + STREAM_CLIENT_CONNECT, + stream_context_create( + [ + 'ssl' => + [ + 'verify_peer' => false, + 'verify_peer_name' => false + ] + ] + ) + ); + + if (!is_resource($connection)) + { + return null; + } + + fwrite( + $connection, + sprintf( + "gemini://%s:%d%s%s\r\n", + $this->_host, + $this->_port, + $this->_path, + $this->_query + ) + ); + + while ($part = fgets($connection, $chunk)) + { + $length = $length + mb_strlen( + $part + ); + + if ($limit && $length > $limit) + { + break; + } + + $response .= $part; + } + + fclose( + $connection + ); + + return $response; + } +} \ No newline at end of file diff --git a/src/Client/Response.php b/src/Client/Response.php new file mode 100644 index 0000000..9d8c4bd --- /dev/null +++ b/src/Client/Response.php @@ -0,0 +1,58 @@ +\d{2})\s(?.*)\r\n(?.*)/su', + $data, + $match + ); + + if (isset($match['code'])) + { + $code = (int) $match['code']; + + if ($code >= 10 && $code <= 69) + { + $this->_code = $match['code']; + } + } + + if (isset($match['meta']) && mb_strlen($match['meta']) <= 1024) + { + $this->_meta = (string) $match['meta']; + } + + if (isset($match['body'])) + { + $this->_body = (string) $match['body']; + } + } + + public function getCode(): ?int + { + return $this->_code; + } + + public function getMeta(): ?string + { + return $this->_meta; + } + + public function getBody(): ?string + { + return $this->_body; + } +} \ No newline at end of file From 00dddea5543a9e397db0621917c6fca2c7d5f750 Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 3 Apr 2024 03:57:01 +0300 Subject: [PATCH 02/56] add gemtext features --- README.md | 25 +++++++++++++ src/Gemtext/Body.php | 86 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 src/Gemtext/Body.php diff --git a/README.md b/README.md index 7979afb..201dae7 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,31 @@ var_dump( ); ``` +## Gemtext + +Object-oriented API for Gemtext + +### Body + +Basic methods to work with `text/gemini` documents + +``` +$body = new \Yggverse\Gemini\Gemtext\Body( + $response->getBody() // gemtext body from client response or .gmi file +); +``` + +#### Body::getH1 +#### Body::getH2 +#### Body::getH3 +#### Body::getLinks + +``` +var_dump( + $body->getLinks() // returns array of clickable links +); +``` + ## DokuWiki Toolkit provides DokuWiki API for Gemini. diff --git a/src/Gemtext/Body.php b/src/Gemtext/Body.php new file mode 100644 index 0000000..2e85bd0 --- /dev/null +++ b/src/Gemtext/Body.php @@ -0,0 +1,86 @@ +_lines[] = $line; + } + } + + public function getH1(): array + { + $matches = []; + + foreach ($this->_lines as $line) + { + if (preg_match('/^#([^#]+)/', trim($line), $match)) + { + $matches[] = trim( + $match[1] + ); + } + } + + return $matches; + } + + public function getH2(): array + { + $matches = []; + + foreach ($this->_lines as $line) + { + if (preg_match('/^##([^#]+)/', trim($line), $match)) + { + $matches[] = trim( + $match[1] + ); + } + } + + return $matches; + } + + public function getH3(): array + { + $matches = []; + + foreach ($this->_lines as $line) + { + if (preg_match('/^###([^#]+)/', trim($line), $match)) + { + $matches[] = trim( + $match[1] + ); + } + } + + return $matches; + } + + public function getLinks(): array + { + $matches = []; + + foreach ($this->_lines as $i => $line) + { + if (preg_match('/^=>(.*)/', trim($line), $match)) + { + $matches[] = trim( + $match[1] + ); + } + } + + return $matches; + } +} \ No newline at end of file From be8b86102f443e0a1ae47d21d9c990ba48d1009e Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 3 Apr 2024 03:58:29 +0300 Subject: [PATCH 03/56] remove index variable not in use --- src/Gemtext/Body.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gemtext/Body.php b/src/Gemtext/Body.php index 2e85bd0..1a2a0b6 100644 --- a/src/Gemtext/Body.php +++ b/src/Gemtext/Body.php @@ -71,7 +71,7 @@ class Body { $matches = []; - foreach ($this->_lines as $i => $line) + foreach ($this->_lines as $line) { if (preg_match('/^=>(.*)/', trim($line), $match)) { From 7916351299060019b721d0ba881c00dbee2c8088 Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 3 Apr 2024 04:29:22 +0300 Subject: [PATCH 04/56] Implement Gemtext/Link class --- README.md | 44 ++++++++++++++++++++++++++++++- src/Gemtext/Link.php | 63 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/Gemtext/Link.php diff --git a/README.md b/README.md index 201dae7..4b41247 100644 --- a/README.md +++ b/README.md @@ -79,10 +79,52 @@ $body = new \Yggverse\Gemini\Gemtext\Body( ``` var_dump( - $body->getLinks() // returns array of clickable links + $body->getLinks() // returns array of inline links ); ``` +### Link + +Inline links parser. + +Allows to extract address, date with timestamp and alt text from link line given + +``` +foreach ($body->getLinks() as $line) +{ + $link = new \Yggverse\Gemini\Gemtext\Link( + $line + ); + + var_dump( + $link->getAddress() + ); + + var_dump( + $link->getAlt() + ); +} +``` + +#### Link::getAddress +#### Link::getDate + +This method also validates time format and returns the unix timestamp as linked argument + +``` +var_dump( + $link->getDate( + $timestamp // get unix time from this variable + ) +); + +var_dump( + $timestamp +); +``` + +#### Link::getAlt + ## DokuWiki Toolkit provides DokuWiki API for Gemini. diff --git a/src/Gemtext/Link.php b/src/Gemtext/Link.php new file mode 100644 index 0000000..f7ed077 --- /dev/null +++ b/src/Gemtext/Link.php @@ -0,0 +1,63 @@ +_line = $line; + } + + public function getAddress(): ?string + { + if (preg_match('/^([^\s]+)\s.*/', trim($this->_line), $match)) + { + return trim( + $match[1] + ); + } + + return null; + } + + public function getDate(?int &$timestamp = null): ?string + { + if (preg_match('/\s([\d]+-[\d+]+-[\d]+)\s/', trim($this->_line), $match)) + { + if ($result = strtotime($match[1])) + { + $timestamp = $result; + + return trim( + $match[1] + ); + } + } + + return null; + } + + public function getAlt(): ?string + { + if (preg_match('/\s[\d]+-[\d+]+-[\d]+\s(.*)$/', trim($this->_line), $match)) + { + return trim( + $match[1] + ); + } + + else if (preg_match('/\s(.*)$/', trim($this->_line), $match)) + { + return trim( + $match[1] + ); + } + + return null; + } +} \ No newline at end of file From ad6b35d6f78a7ba7f550f26d4088c04fec19db9d Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 3 Apr 2024 15:38:18 +0300 Subject: [PATCH 05/56] add response setters --- README.md | 3 +++ src/Client/Response.php | 27 ++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4b41247..18d00fb 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,11 @@ $response = new \Yggverse\Gemini\Client\Response( ); ``` +#### Response::setCode #### Response::getCode +#### Response::setMeta #### Response::getMeta +#### Response::setBody #### Response::getBody ``` diff --git a/src/Client/Response.php b/src/Client/Response.php index 9d8c4bd..a06cac2 100644 --- a/src/Client/Response.php +++ b/src/Client/Response.php @@ -26,31 +26,52 @@ class Response if ($code >= 10 && $code <= 69) { - $this->_code = $match['code']; + $this->setCode( + $code + ); } } if (isset($match['meta']) && mb_strlen($match['meta']) <= 1024) { - $this->_meta = (string) $match['meta']; + $this->setMeta( + (string) $match['meta'] + ); } if (isset($match['body'])) { - $this->_body = (string) $match['body']; + $this->setBody( + (string) (string) $match['body'] + ); } } + public function setCode(?int $value): void + { + $this->_code = $value; + } + public function getCode(): ?int { return $this->_code; } + public function setMeta(?string $value): void + { + $this->_meta = $value; + } + public function getMeta(): ?string { return $this->_meta; } + public function setBody(?string $value): void + { + $this->_body = $value; + } + public function getBody(): ?string { return $this->_body; From e3f4786f9ec45465f120060371ac05ec52054db9 Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 3 Apr 2024 15:56:23 +0300 Subject: [PATCH 06/56] reduce default chunk size --- src/Client/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client/Request.php b/src/Client/Request.php index 400693b..3309e62 100644 --- a/src/Client/Request.php +++ b/src/Client/Request.php @@ -111,7 +111,7 @@ class Request public function getResponse( int $timeout = 30, // socket timeout, useful for offline resources ?int $limit = null, // content length, null for unlimited - int $chunk = 1024, // chunk size, it's better change it later to 1 @TODO + int $chunk = 1, // chunk size in bytes int &$length = 0, // current response length, do not change without special needs ?int &$code = null, // error code for debug ?string &$message = null, // error message for debug From 008d191d3c61a0e00f5df8ffc744cfe6577b4759 Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 3 Apr 2024 16:30:08 +0300 Subject: [PATCH 07/56] add Body::findLinks method --- README.md | 10 ++++++++++ src/Gemtext/Body.php | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/README.md b/README.md index 18d00fb..699c976 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,16 @@ var_dump( ); ``` +#### Body::findLinks + +Find context links by protocol as argument, `gemini` by default + +``` +var_dump( + $body->findLinks('http') // returns array of http links found +); +``` + ### Link Inline links parser. diff --git a/src/Gemtext/Body.php b/src/Gemtext/Body.php index 1a2a0b6..a9d8209 100644 --- a/src/Gemtext/Body.php +++ b/src/Gemtext/Body.php @@ -83,4 +83,26 @@ class Body return $matches; } + + public function findLinks(string $protocol = 'gemini'): array + { + $matches = []; + + foreach ($this->_lines as $line) + { + if (preg_match('/' . $protocol . ':\/\/(.*)[\s\S\'"]*/', trim($line), $match)) + { + $matches[] = + sprintf( + '%s://%s', + $protocol, + trim( + $match[1] + ) + ); + } + } + + return $matches; + } } \ No newline at end of file From 15eee0e8146f0bdd2f738331a727c529b74ef829 Mon Sep 17 00:00:00 2001 From: yggverse Date: Fri, 5 Apr 2024 05:18:49 +0300 Subject: [PATCH 08/56] allow nullable $length attribute --- src/Client/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client/Request.php b/src/Client/Request.php index 3309e62..54b553c 100644 --- a/src/Client/Request.php +++ b/src/Client/Request.php @@ -112,7 +112,7 @@ class Request int $timeout = 30, // socket timeout, useful for offline resources ?int $limit = null, // content length, null for unlimited int $chunk = 1, // chunk size in bytes - int &$length = 0, // current response length, do not change without special needs + ?int &$length = 0, // initial response length, do not change without special needs ?int &$code = null, // error code for debug ?string &$message = null, // error message for debug string &$response = '' // response init, also returning by this method From 6c2a3f63679a9a2b424a60c6f47f05d79e9b0cbb Mon Sep 17 00:00:00 2001 From: yggverse Date: Fri, 5 Apr 2024 05:19:22 +0300 Subject: [PATCH 09/56] remove chunk settings --- src/Client/Request.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Client/Request.php b/src/Client/Request.php index 54b553c..cbe1cd0 100644 --- a/src/Client/Request.php +++ b/src/Client/Request.php @@ -111,7 +111,6 @@ class Request public function getResponse( int $timeout = 30, // socket timeout, useful for offline resources ?int $limit = null, // content length, null for unlimited - int $chunk = 1, // chunk size in bytes ?int &$length = 0, // initial response length, do not change without special needs ?int &$code = null, // error code for debug ?string &$message = null, // error message for debug @@ -155,7 +154,7 @@ class Request ) ); - while ($part = fgets($connection, $chunk)) + while ($part = fgets($connection, 1)) { $length = $length + mb_strlen( $part From d7d608363466b9f1c763097e35d64487ef7320ea Mon Sep 17 00:00:00 2001 From: yggverse Date: Fri, 5 Apr 2024 05:43:54 +0300 Subject: [PATCH 10/56] remove custom length value --- src/Client/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client/Request.php b/src/Client/Request.php index cbe1cd0..dc1d4c2 100644 --- a/src/Client/Request.php +++ b/src/Client/Request.php @@ -154,7 +154,7 @@ class Request ) ); - while ($part = fgets($connection, 1)) + while ($part = fgets($connection)) { $length = $length + mb_strlen( $part From c38cdc841f0a88346af92729345e276c2c673732 Mon Sep 17 00:00:00 2001 From: yggverse Date: Fri, 5 Apr 2024 05:49:46 +0300 Subject: [PATCH 11/56] allow nullable response init --- src/Client/Response.php | 57 ++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/src/Client/Response.php b/src/Client/Response.php index a06cac2..d5eacaf 100644 --- a/src/Client/Response.php +++ b/src/Client/Response.php @@ -10,40 +10,43 @@ class Response private ?string $_meta = null; private ?string $_body = null; - public function __construct(string $data) + public function __construct(?string $data = null) { - $match = []; - - preg_match( - '/(?\d{2})\s(?.*)\r\n(?.*)/su', - $data, - $match - ); - - if (isset($match['code'])) + if ($data) { - $code = (int) $match['code']; + $match = []; - if ($code >= 10 && $code <= 69) + preg_match( + '/(?\d{2})\s(?.*)\r\n(?.*)/su', + $data, + $match + ); + + if (isset($match['code'])) { - $this->setCode( - $code + $code = (int) $match['code']; + + if ($code >= 10 && $code <= 69) + { + $this->setCode( + $code + ); + } + } + + if (isset($match['meta']) && mb_strlen($match['meta']) <= 1024) + { + $this->setMeta( + (string) $match['meta'] ); } - } - if (isset($match['meta']) && mb_strlen($match['meta']) <= 1024) - { - $this->setMeta( - (string) $match['meta'] - ); - } - - if (isset($match['body'])) - { - $this->setBody( - (string) (string) $match['body'] - ); + if (isset($match['body'])) + { + $this->setBody( + (string) (string) $match['body'] + ); + } } } From 9ec48ba9449192d4cbd32469a96c094c6644cb47 Mon Sep 17 00:00:00 2001 From: yggverse Date: Fri, 5 Apr 2024 05:51:32 +0300 Subject: [PATCH 12/56] fix macros name --- src/Client/Response.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client/Response.php b/src/Client/Response.php index d5eacaf..5988423 100644 --- a/src/Client/Response.php +++ b/src/Client/Response.php @@ -17,7 +17,7 @@ class Response $match = []; preg_match( - '/(?\d{2})\s(?.*)\r\n(?.*)/su', + '/(?\d{2})\s(?.*)\r\n(?.*)/su', $data, $match ); From 75685f37379fae5067d405805bdcaf26b6d23387 Mon Sep 17 00:00:00 2001 From: yggverse Date: Fri, 5 Apr 2024 07:03:47 +0300 Subject: [PATCH 13/56] implement skipTags method --- README.md | 19 ++++++++++ src/Gemtext/Body.php | 85 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/README.md b/README.md index 699c976..123e565 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,25 @@ var_dump( ); ``` +#### Body::skipTags + +Strip gemini tags from Gemini document + +``` +var_dump( + $body->skipTags() // strip all tags +); + +var_dump( + $body->skipTags( + [ // 1- and 2- level headers only + "##", + "###" + ] + ) +); +``` + ### Link Inline links parser. diff --git a/src/Gemtext/Body.php b/src/Gemtext/Body.php index a9d8209..5e18fd0 100644 --- a/src/Gemtext/Body.php +++ b/src/Gemtext/Body.php @@ -105,4 +105,89 @@ class Body return $matches; } + + public function skipTags(array $tags = []): string + { + $lines = []; + + foreach ($this->_lines as $line) + { + $line = trim( + $line + ); + + if ($tags) + { + foreach ($tags as $tag) + { + if(!in_array($tag, ['#', '##', '###', '=>', '*', '```'])) + { + continue; + } + + switch (true) + { + case str_starts_with($line, '#'): + + $line = preg_replace( + sprintf( + '/^%s([^#]+)/ui', + $tag + ), + '$1', + $line + ); + + break; + + case str_starts_with($line, '*'): + + $line = preg_replace( + '/^\*(.*)/ui', + '$1', + $line + ); + + break; + + default: + + $line = preg_replace( + sprintf( + '/^%s(.*)/ui', + $tag + ), + '$1', + $line + ); + } + } + } + + else + { + $line = preg_replace( + [ + '/^#([^#]+)/ui', + '/^##([^#]+)/ui', + '/^###([^#]+)/ui', + '/^=>(.*)/ui', + '/^\*(.*)/ui', + '/^```(.*)/ui', + ], + '$1', + $line + ); + } + + $lines[] = trim( + $line + ); + } + + return implode( + PHP_EOL, + $lines + ); + } } \ No newline at end of file From 54ad13e9a51fadbcd32236f2f6f5c54a058eb620 Mon Sep 17 00:00:00 2001 From: yggverse Date: Sat, 6 Apr 2024 09:28:20 +0300 Subject: [PATCH 14/56] add stream context options support --- src/Client/Request.php | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/Client/Request.php b/src/Client/Request.php index dc1d4c2..c0e14e6 100644 --- a/src/Client/Request.php +++ b/src/Client/Request.php @@ -11,6 +11,15 @@ class Request private string $_path; private string $_query; + private array $_options = + [ + 'ssl' => + [ + 'verify_peer' => false, + 'verify_peer_name' => false + ] + ]; + public function __construct(string $url) { if ($host = parse_url($url, PHP_URL_HOST)) @@ -68,6 +77,16 @@ class Request } } + public function setOptions(array $value): void + { + $this->_options = $value; + } + + public function getOptions(): array + { + return $this->_options; + } + public function setHost(string $value): void { $this->_host = $value; @@ -128,13 +147,7 @@ class Request $timeout, STREAM_CLIENT_CONNECT, stream_context_create( - [ - 'ssl' => - [ - 'verify_peer' => false, - 'verify_peer_name' => false - ] - ] + $this->_options ) ); From 06238a4ce3ea61e41d0cd4f431c6b7507330b925 Mon Sep 17 00:00:00 2001 From: yggverse Date: Sun, 7 Apr 2024 20:53:35 +0300 Subject: [PATCH 15/56] add resolved request support --- README.md | 87 +++++++++++++++++++++++++++--------------- src/Client/Request.php | 23 ++++++++++- 2 files changed, 78 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 123e565..e96683c 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,39 @@ PHP interface for Gemini protocol queries by TLS socket connection ### Request -``` +``` php $request = new \Yggverse\Gemini\Client\Request( 'gemini://betahowto.duckdns.org:1965/archive' ); ``` +**Resolved request (SNI)** + +Optionally, provide resolved IP as the second argument + +``` php +$request = new \Yggverse\Gemini\Client\Request( + 'gemini://betahowto.duckdns.org:1965/archive' // target URL + '94.140.114.89' // resolved IP, skip to use system-wide resolver +); +``` + +Alternatively, use `setResolvedHost` method of `Request` object before `getResponse` + +#### Request::getResolvedHost + +``` php +$request->setResolvedHost( + '94.140.114.89' +) +``` + +* to resolve address with PHP, take a look on the [net-php](https://github.com/YGGverse/net-php) library! + +#### Request::setResolvedHost + +Get resolved host back + #### Request::setHost #### Request::getHost #### Request::setPort @@ -32,7 +59,7 @@ $request = new \Yggverse\Gemini\Client\Request( Execute requested URL and return raw response -``` +``` php var_dump( $request->getResponse() ); @@ -42,7 +69,7 @@ var_dump( This class provides additional features for the raw response operations -``` +``` php $response = new \Yggverse\Gemini\Client\Response( $request->getResponse() ); @@ -55,7 +82,7 @@ $response = new \Yggverse\Gemini\Client\Response( #### Response::setBody #### Response::getBody -``` +``` php var_dump( $response->getBody() ); @@ -69,7 +96,7 @@ Object-oriented API for Gemtext Basic methods to work with `text/gemini` documents -``` +``` php $body = new \Yggverse\Gemini\Gemtext\Body( $response->getBody() // gemtext body from client response or .gmi file ); @@ -80,7 +107,7 @@ $body = new \Yggverse\Gemini\Gemtext\Body( #### Body::getH3 #### Body::getLinks -``` +``` php var_dump( $body->getLinks() // returns array of inline links ); @@ -90,7 +117,7 @@ var_dump( Find context links by protocol as argument, `gemini` by default -``` +``` php var_dump( $body->findLinks('http') // returns array of http links found ); @@ -100,7 +127,7 @@ var_dump( Strip gemini tags from Gemini document -``` +``` php var_dump( $body->skipTags() // strip all tags ); @@ -121,7 +148,7 @@ Inline links parser. Allows to extract address, date with timestamp and alt text from link line given -``` +``` php foreach ($body->getLinks() as $line) { $link = new \Yggverse\Gemini\Gemtext\Link( @@ -143,7 +170,7 @@ foreach ($body->getLinks() as $line) This method also validates time format and returns the unix timestamp as linked argument -``` +``` php var_dump( $link->getDate( $timestamp // get unix time from this variable @@ -171,7 +198,7 @@ Allows to simple deploy new apps or make existing website mirror Read DokuWiki and convert to Gemini -``` +``` php $reader = new \Yggverse\Gemini\Dokuwiki\Reader( // optional regex rule set array ); @@ -184,7 +211,7 @@ $reader = new \Yggverse\Gemini\Dokuwiki\Reader( Get or change existing regex rule (or just skip by using build-in set) -``` +``` php echo $reader->setRule( '/subject/ui', 'replacement' @@ -196,7 +223,7 @@ echo $reader->setRule( #### Reader::getMacros #### Reader::setMacros -``` +``` php echo $reader->setMacros( '~my-macros-key~', '~my-macros-value~', @@ -211,7 +238,7 @@ As wiki has lot of inline links, to make converted document well-readable, this If you don't like this implementation, feel free to change it by `Reader::setRule` method! -``` +``` php echo $reader->toGemini( file_get_contents( '/host/data/pages/index.txt' @@ -223,7 +250,7 @@ echo $reader->toGemini( Get document title -``` +``` php $gemini = $reader->toGemini( file_get_contents( '/host/data/pages/index.txt' @@ -239,7 +266,7 @@ echo $reader->getH1( Get document links -``` +``` php $gemini = $reader->toGemini( file_get_contents( '/host/data/pages/index.txt' @@ -255,7 +282,7 @@ echo $reader->getLinks( Provides methods for simple and secure interaction with DokuWiki file storage -``` +``` php $filesystem = new \Yggverse\Gemini\Dokuwiki\Filesystem( '/host/data' // storage location ); @@ -265,7 +292,7 @@ $filesystem = new \Yggverse\Gemini\Dokuwiki\Filesystem( Return simple array of all files in storage -``` +``` php var_dump ( $filesystem->getList( 'hello:world' @@ -277,7 +304,7 @@ var_dump ( Return all files under the storage folder in tree format -``` +``` php var_dump ( $filesystem->getTree( 'hello:world' @@ -289,7 +316,7 @@ var_dump ( Return pages under the given data directory -``` +``` php var_dump ( $filesystem->getPagePathsByPath( // absolute path to target data directory (e.g. Filesystem::getDirectoryPathByUri) @@ -302,7 +329,7 @@ var_dump ( Return absolute path to stored page file -``` +``` php var_dump ( $filesystem->getPagePathByUri( 'hello:world' @@ -315,7 +342,7 @@ var_dump ( Return page URI in `dokuwiki:format` -``` +``` php var_dump ( $filesystem->getPageUriByPath( '/full/path/to/page.txt' @@ -327,7 +354,7 @@ var_dump ( Return absolute path to stored media file -``` +``` php var_dump ( $filesystem->getMediaPathByUri( 'hello:world' @@ -339,7 +366,7 @@ var_dump ( Return file MIME if path match storage item -``` +``` php var_dump ( $filesystem->getMimeByPath( '/full/path/to/page.txt' @@ -351,7 +378,7 @@ var_dump ( Return file content if path match storage item -``` +``` php var_dump ( $filesystem->getDataByPath( '/full/path/to/page.txt' @@ -363,7 +390,7 @@ var_dump ( Check path exist and match storage item -``` +``` php var_dump ( $filesystem->isPath( '/full/path/to/page.txt' @@ -375,7 +402,7 @@ var_dump ( Useful methods to minify controller codebase -``` +``` php $helper = new \Yggverse\Gemini\Dokuwiki\Helper( new \Yggverse\Gemini\Dokuwiki\Filesystem(), new \Yggverse\Gemini\Dokuwiki\Reader() @@ -386,7 +413,7 @@ $helper = new \Yggverse\Gemini\Dokuwiki\Helper( Return simple array of children section links in Gemini format -``` +``` php var_dump ( $helper->getChildrenSectionLinksByUri( 'hello:world' @@ -398,7 +425,7 @@ var_dump ( Return simple array of children page links in Gemini format -``` +``` php var_dump ( $helper->getChildrenPageLinksByUri( 'hello:world' @@ -410,7 +437,7 @@ var_dump ( Return page link (that contain document name) in Gemini format -``` +``` php var_dump ( $helper->getPageLinkByPath( $filesystem->getPagePathByUri( diff --git a/src/Client/Request.php b/src/Client/Request.php index c0e14e6..6b3583a 100644 --- a/src/Client/Request.php +++ b/src/Client/Request.php @@ -6,6 +6,8 @@ namespace Yggverse\Gemini\Client; class Request { + private ?string $_ip = null; + private string $_host; private int $_port; private string $_path; @@ -20,7 +22,7 @@ class Request ] ]; - public function __construct(string $url) + public function __construct(string $url, ?string $ip = null) { if ($host = parse_url($url, PHP_URL_HOST)) { @@ -75,6 +77,13 @@ class Request '' ); } + + if ($ip && false !== filter_var($ip, FILTER_VALIDATE_IP)) + { + $this->setResolvedHost( + $ip + ); + } } public function setOptions(array $value): void @@ -127,6 +136,16 @@ class Request return $this->_query; } + public function setResolvedHost(?string $value): void + { + $this->_ip = $value; + } + + public function getResolvedHost(): ?string + { + return $this->_ip; + } + public function getResponse( int $timeout = 30, // socket timeout, useful for offline resources ?int $limit = null, // content length, null for unlimited @@ -139,7 +158,7 @@ class Request $connection = stream_socket_client( sprintf( 'tls://%s:%d', - $this->_host, + $this->_ip ? $this->_ip : $this->_host, $this->_port ), $code, From ca0746f5367526437226011d61375477caee0d45 Mon Sep 17 00:00:00 2001 From: yggverse Date: Sun, 7 Apr 2024 20:55:17 +0300 Subject: [PATCH 16/56] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e96683c..6db8188 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ $request = new \Yggverse\Gemini\Client\Request( Alternatively, use `setResolvedHost` method of `Request` object before `getResponse` -#### Request::getResolvedHost +#### Request::setResolvedHost ``` php $request->setResolvedHost( @@ -43,7 +43,7 @@ $request->setResolvedHost( * to resolve address with PHP, take a look on the [net-php](https://github.com/YGGverse/net-php) library! -#### Request::setResolvedHost +#### Request::getResolvedHost Get resolved host back From c8257641ce55a129b0c3d6d51a08906106c2d1f7 Mon Sep 17 00:00:00 2001 From: yggverse Date: Sun, 7 Apr 2024 20:55:58 +0300 Subject: [PATCH 17/56] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6db8188..65e732f 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ $request->setResolvedHost( ) ``` -* to resolve address with PHP, take a look on the [net-php](https://github.com/YGGverse/net-php) library! +* to resolve network address with PHP, take a look on the [net-php](https://github.com/YGGverse/net-php) library! #### Request::getResolvedHost From b395f5f17a41fe4c83cf1f8cafe84b5fcdf891b7 Mon Sep 17 00:00:00 2001 From: yggverse Date: Sun, 7 Apr 2024 21:01:21 +0300 Subject: [PATCH 18/56] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 65e732f..0a7a881 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # gemini-php -PHP 8 Library for Gemini Protocol +PHP 8 Library for [Gemini Protocol](https://geminiprotocol.net) ## Usage From ae779833cbbf3e358eebe34a36250584a48c815a Mon Sep 17 00:00:00 2001 From: yggverse Date: Sun, 7 Apr 2024 21:04:03 +0300 Subject: [PATCH 19/56] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a7a881..abfae79 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ $request = new \Yggverse\Gemini\Client\Request( **Resolved request (SNI)** -Optionally, provide resolved IP as the second argument +For direct connection, provide resolved IP as the second argument ``` php $request = new \Yggverse\Gemini\Client\Request( From 8bdd2341eb8479a574f0c05a97e480d714475cc3 Mon Sep 17 00:00:00 2001 From: yggverse Date: Sun, 7 Apr 2024 21:04:33 +0300 Subject: [PATCH 20/56] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index abfae79..0ffcf48 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ $request = new \Yggverse\Gemini\Client\Request( **Resolved request (SNI)** -For direct connection, provide resolved IP as the second argument +For direct connection provide resolved IP as the second argument ``` php $request = new \Yggverse\Gemini\Client\Request( From 3a1d8aa83bd7cbede89cfc3fdefa8750fdcda58a Mon Sep 17 00:00:00 2001 From: yggverse Date: Sun, 7 Apr 2024 22:48:53 +0300 Subject: [PATCH 21/56] update readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0ffcf48..13d4019 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ PHP interface for Gemini protocol queries by TLS socket connection ``` php $request = new \Yggverse\Gemini\Client\Request( - 'gemini://betahowto.duckdns.org:1965/archive' + 'gemini://yggverse.cities.yesterweb.org:1965/index.gmi' ); ``` @@ -26,8 +26,8 @@ For direct connection provide resolved IP as the second argument ``` php $request = new \Yggverse\Gemini\Client\Request( - 'gemini://betahowto.duckdns.org:1965/archive' // target URL - '94.140.114.89' // resolved IP, skip to use system-wide resolver + 'gemini://yggverse.cities.yesterweb.org:1965/index.gmi' // target URL + '68.133.1.71' // resolved IP, skip to use system-wide resolver ); ``` @@ -37,7 +37,7 @@ Alternatively, use `setResolvedHost` method of `Request` object before `getRespo ``` php $request->setResolvedHost( - '94.140.114.89' + '68.133.1.71' ) ``` From 79e4de7434c56bf2605b6f642738af0b23e93a49 Mon Sep 17 00:00:00 2001 From: yggverse Date: Mon, 8 Apr 2024 03:32:14 +0300 Subject: [PATCH 22/56] update readme --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 13d4019..afbcf9c 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,27 @@ var_dump( ); ``` +#### Request::getOptions +#### Request::setOptions + +``` php +$request = new \Yggverse\Gemini\Client\Request( + 'gemini://yggverse.cities.yesterweb.org', + '68.133.1.71' // make direct request to the resolved host +); + +$request->setOptions( + [ + 'ssl' => + [ + 'peer_name' => 'yggverse.cities.yesterweb.org', // SNI + 'verify_peer' => false, + 'verify_peer_name' => false + ] + ] +); +``` + ### Response This class provides additional features for the raw response operations From 595e0e6adab0c1732ad7c5ed657f601941822a75 Mon Sep 17 00:00:00 2001 From: yggverse Date: Tue, 9 Apr 2024 10:34:38 +0300 Subject: [PATCH 23/56] add integrations --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index afbcf9c..220042d 100644 --- a/README.md +++ b/README.md @@ -466,4 +466,9 @@ var_dump ( ) ) ); -``` \ No newline at end of file +``` + +## Integrations + +* [Yo! Crawler for different networks](https://github.com/YGGverse/Yo) +* [Yoda - PHP-GTK browser for Gemini Protocol](https://github.com/YGGverse/Yoda) \ No newline at end of file From 1c54da73c384738db862e89fe393e026a633cd50 Mon Sep 17 00:00:00 2001 From: yggverse Date: Tue, 9 Apr 2024 10:35:46 +0300 Subject: [PATCH 24/56] =?UTF-8?q?add=20=CE=B2-Doku=20integration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 220042d..d4cba13 100644 --- a/README.md +++ b/README.md @@ -470,5 +470,6 @@ var_dump ( ## Integrations +* [β-Doku is DokuWiki Satellite for Gemini Protocol](https://github.com/YGGverse/bdoku) * [Yo! Crawler for different networks](https://github.com/YGGverse/Yo) * [Yoda - PHP-GTK browser for Gemini Protocol](https://github.com/YGGverse/Yoda) \ No newline at end of file From f5aebfe554af6a56b6e2bccceae7ffd19fc07d28 Mon Sep 17 00:00:00 2001 From: yggverse Date: Tue, 9 Apr 2024 10:37:56 +0300 Subject: [PATCH 25/56] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d4cba13..2dd961a 100644 --- a/README.md +++ b/README.md @@ -471,5 +471,5 @@ var_dump ( ## Integrations * [β-Doku is DokuWiki Satellite for Gemini Protocol](https://github.com/YGGverse/bdoku) -* [Yo! Crawler for different networks](https://github.com/YGGverse/Yo) +* [Yo! Crawler for different networks](https://github.com/YGGverse/Yo/tree/gemini) * [Yoda - PHP-GTK browser for Gemini Protocol](https://github.com/YGGverse/Yoda) \ No newline at end of file From 55b13c4031fdc16c66b89b3f1d4ee9ca704e22d6 Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 10 Apr 2024 14:43:27 +0300 Subject: [PATCH 26/56] fix getAddress regex condition --- src/Gemtext/Link.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gemtext/Link.php b/src/Gemtext/Link.php index f7ed077..a479257 100644 --- a/src/Gemtext/Link.php +++ b/src/Gemtext/Link.php @@ -15,7 +15,7 @@ class Link public function getAddress(): ?string { - if (preg_match('/^([^\s]+)\s.*/', trim($this->_line), $match)) + if (preg_match('/^=>\s*([^\s]+)/', trim($this->_line), $match)) { return trim( $match[1] From 526d8104068f931bea85b7ae1f2541ce90540571 Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 10 Apr 2024 17:25:15 +0300 Subject: [PATCH 27/56] return match line number as the array key in result --- src/Gemtext/Body.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Gemtext/Body.php b/src/Gemtext/Body.php index 5e18fd0..fabac2d 100644 --- a/src/Gemtext/Body.php +++ b/src/Gemtext/Body.php @@ -20,11 +20,11 @@ class Body { $matches = []; - foreach ($this->_lines as $line) + foreach ($this->_lines as $index => $line) { if (preg_match('/^#([^#]+)/', trim($line), $match)) { - $matches[] = trim( + $matches[$index] = trim( $match[1] ); } @@ -37,11 +37,11 @@ class Body { $matches = []; - foreach ($this->_lines as $line) + foreach ($this->_lines as $index => $line) { if (preg_match('/^##([^#]+)/', trim($line), $match)) { - $matches[] = trim( + $matches[$index] = trim( $match[1] ); } @@ -54,11 +54,11 @@ class Body { $matches = []; - foreach ($this->_lines as $line) + foreach ($this->_lines as $index => $line) { if (preg_match('/^###([^#]+)/', trim($line), $match)) { - $matches[] = trim( + $matches[$index] = trim( $match[1] ); } @@ -71,11 +71,11 @@ class Body { $matches = []; - foreach ($this->_lines as $line) + foreach ($this->_lines as $index => $line) { if (preg_match('/^=>(.*)/', trim($line), $match)) { - $matches[] = trim( + $matches[$index] = trim( $match[1] ); } @@ -88,11 +88,11 @@ class Body { $matches = []; - foreach ($this->_lines as $line) + foreach ($this->_lines as $index => $line) { if (preg_match('/' . $protocol . ':\/\/(.*)[\s\S\'"]*/', trim($line), $match)) { - $matches[] = + $matches[$index] = sprintf( '%s://%s', $protocol, From fc3b82a05205130e0972285d8ae772a1ac36e4d9 Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 10 Apr 2024 17:33:58 +0300 Subject: [PATCH 28/56] add the line methods --- README.md | 8 +++++--- src/Gemtext/Body.php | 14 ++++++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2dd961a..2082583 100644 --- a/README.md +++ b/README.md @@ -119,10 +119,12 @@ Basic methods to work with `text/gemini` documents ``` php $body = new \Yggverse\Gemini\Gemtext\Body( - $response->getBody() // gemtext body from client response or .gmi file + $response->getBody() // gemtext body from client response or .gmi file content ); ``` +#### Body::getLines +#### Body::getLine #### Body::getH1 #### Body::getH2 #### Body::getH3 @@ -130,7 +132,7 @@ $body = new \Yggverse\Gemini\Gemtext\Body( ``` php var_dump( - $body->getLinks() // returns array of inline links + $body->getLinks() // returns array of links (with line number in key) ); ``` @@ -140,7 +142,7 @@ Find context links by protocol as argument, `gemini` by default ``` php var_dump( - $body->findLinks('http') // returns array of http links found + $body->findLinks('http') // returns array of http links only (with line number in key) ); ``` diff --git a/src/Gemtext/Body.php b/src/Gemtext/Body.php index fabac2d..0ce16d0 100644 --- a/src/Gemtext/Body.php +++ b/src/Gemtext/Body.php @@ -10,12 +10,22 @@ class Body public function __construct(string $gemtext) { - foreach ((array) explode(PHP_EOL, $gemtext) as $line) + foreach ((array) explode(PHP_EOL, $gemtext) as $index => $line) { - $this->_lines[] = $line; + $this->_lines[$index] = $line; } } + public function getLine(int $index): ?int + { + return isset($this->_lines[$index]) ? $this->_lines[$index] : null; + } + + public function getLines(): array + { + return $this->_lines; + } + public function getH1(): array { $matches = []; From faac656ab1935ac6e1f064499217e95ab0f1c850 Mon Sep 17 00:00:00 2001 From: yggverse Date: Fri, 12 Apr 2024 08:01:43 +0300 Subject: [PATCH 29/56] add pango markup converter --- README.md | 22 +++++++++++++ src/Pango.php | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 src/Pango.php diff --git a/README.md b/README.md index 2082583..7be841b 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,28 @@ var_dump( #### Link::getAlt +## Pango + +Converter for GTK/Pango format + +### Pango::fromGemtext + +``` php +$pango = \Yggverse\Gemini\Pango::fromGemtext( + $gemtext +); +``` + +### Pango::fromBody + +``` php +$pango = \Yggverse\Gemini\Pango::fromBody( + new \Yggverse\Gemini\Gemtext\Body( + $gemtext + ) +); +``` + ## DokuWiki Toolkit provides DokuWiki API for Gemini. diff --git a/src/Pango.php b/src/Pango.php new file mode 100644 index 0000000..69670e5 --- /dev/null +++ b/src/Pango.php @@ -0,0 +1,86 @@ +getLines(); + + $escaped = []; + + /// Format H1 + foreach ($body->getH1() as $index => $h1) + { + $lines[$index] = sprintf( + '%s', + htmlentities( + $h1 + ) + ); + + $escaped[] = $index; + } + + /// Format H2 + foreach ($body->getH2() as $index => $h2) + { + $lines[$index] = sprintf( + '%s', + htmlentities( + $h2 + ) + ); + + $escaped[] = $index; + } + + /// Format H3 + foreach ($body->getH3() as $index => $h3) + { + $lines[$index] = sprintf( + '%s', + htmlentities( + $h3 + ) + ); + + $escaped[] = $index; + } + + /// Escape entities + foreach ($lines as $index => $line) + { + if (!in_array($index, $escaped)) + { + $lines[$index] = htmlentities( + $line + ); + } + } + + // @TODO links, code, escape entities + + return implode( + PHP_EOL, + $lines + ); + } +} \ No newline at end of file From 3b371988ff8f710d07c0ad1cdd87e84be5998683 Mon Sep 17 00:00:00 2001 From: yggverse Date: Fri, 12 Apr 2024 08:03:14 +0300 Subject: [PATCH 30/56] update headers level --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7be841b..65ec485 100644 --- a/README.md +++ b/README.md @@ -211,7 +211,7 @@ var_dump( Converter for GTK/Pango format -### Pango::fromGemtext +#### Pango::fromGemtext ``` php $pango = \Yggverse\Gemini\Pango::fromGemtext( @@ -219,7 +219,7 @@ $pango = \Yggverse\Gemini\Pango::fromGemtext( ); ``` -### Pango::fromBody +#### Pango::fromBody ``` php $pango = \Yggverse\Gemini\Pango::fromBody( From 284733886eb55881681f6b8ee1f55807f71107f7 Mon Sep 17 00:00:00 2001 From: yggverse Date: Fri, 12 Apr 2024 09:36:27 +0300 Subject: [PATCH 31/56] add escape method --- src/Pango.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Pango.php b/src/Pango.php index 69670e5..6c17092 100644 --- a/src/Pango.php +++ b/src/Pango.php @@ -31,7 +31,7 @@ class Pango { $lines[$index] = sprintf( '%s', - htmlentities( + self::escape( $h1 ) ); @@ -44,7 +44,7 @@ class Pango { $lines[$index] = sprintf( '%s', - htmlentities( + self::escape( $h2 ) ); @@ -57,7 +57,7 @@ class Pango { $lines[$index] = sprintf( '%s', - htmlentities( + self::escape( $h3 ) ); @@ -70,7 +70,7 @@ class Pango { if (!in_array($index, $escaped)) { - $lines[$index] = htmlentities( + $lines[$index] = self::escape( $line ); } @@ -83,4 +83,13 @@ class Pango $lines ); } + + public static function escape( + string $value + ): string + { + return htmlspecialchars( + $value + ); + } } \ No newline at end of file From 1b996e043ba6a34c2a2b9c54198406fab482fb3e Mon Sep 17 00:00:00 2001 From: yggverse Date: Fri, 12 Apr 2024 09:45:23 +0300 Subject: [PATCH 32/56] add getQuote method --- README.md | 1 + src/Gemtext/Body.php | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/README.md b/README.md index 65ec485..54f4190 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,7 @@ $body = new \Yggverse\Gemini\Gemtext\Body( #### Body::getH1 #### Body::getH2 #### Body::getH3 +#### Body::getQuote #### Body::getLinks ``` php diff --git a/src/Gemtext/Body.php b/src/Gemtext/Body.php index 0ce16d0..4c6f721 100644 --- a/src/Gemtext/Body.php +++ b/src/Gemtext/Body.php @@ -94,6 +94,23 @@ class Body return $matches; } + public function getQuote(): array + { + $matches = []; + + foreach ($this->_lines as $index => $line) + { + if (preg_match('/^>(.*)/', trim($line), $match)) + { + $matches[$index] = trim( + $match[1] + ); + } + } + + return $matches; + } + public function findLinks(string $protocol = 'gemini'): array { $matches = []; From 482106edf4df19110b6e6b2659b46d5501737637 Mon Sep 17 00:00:00 2001 From: yggverse Date: Fri, 12 Apr 2024 09:46:12 +0300 Subject: [PATCH 33/56] add quote support --- src/Pango.php | 52 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/Pango.php b/src/Pango.php index 6c17092..fc847b4 100644 --- a/src/Pango.php +++ b/src/Pango.php @@ -21,62 +21,76 @@ class Pango \Yggverse\Gemini\Gemtext\Body $body ): string { - // Format body $lines = $body->getLines(); $escaped = []; - /// Format H1 - foreach ($body->getH1() as $index => $h1) + // H1 + foreach ($body->getH1() as $index => $value) { $lines[$index] = sprintf( '%s', self::escape( - $h1 + $value ) ); $escaped[] = $index; } - /// Format H2 - foreach ($body->getH2() as $index => $h2) + // H2 + foreach ($body->getH2() as $index => $value) { $lines[$index] = sprintf( '%s', self::escape( - $h2 + $value ) ); $escaped[] = $index; } - /// Format H3 - foreach ($body->getH3() as $index => $h3) + // H3 + foreach ($body->getH3() as $index => $value) { $lines[$index] = sprintf( '%s', self::escape( - $h3 + $value ) ); $escaped[] = $index; } - /// Escape entities - foreach ($lines as $index => $line) + // H3 + foreach ($body->getH3() as $index => $value) { - if (!in_array($index, $escaped)) - { - $lines[$index] = self::escape( - $line - ); - } + $lines[$index] = sprintf( + '%s', + self::escape( + $value + ) + ); + + $escaped[] = $index; } - // @TODO links, code, escape entities + // Quote + foreach ($body->getQuote() as $index => $value) + { + $lines[$index] = sprintf( + '%s', + self::escape( + $value + ) + ); + + $escaped[] = $index; + } + + // @TODO links, code return implode( PHP_EOL, From 3265add74aa811312b523a6d0913a74fb93824ff Mon Sep 17 00:00:00 2001 From: yggverse Date: Fri, 12 Apr 2024 11:52:33 +0300 Subject: [PATCH 34/56] implement getCode method --- README.md | 1 + src/Gemtext/Body.php | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/README.md b/README.md index 54f4190..a03dfcb 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ $body = new \Yggverse\Gemini\Gemtext\Body( #### Body::getH2 #### Body::getH3 #### Body::getQuote +#### Body::getCode #### Body::getLinks ``` php diff --git a/src/Gemtext/Body.php b/src/Gemtext/Body.php index 4c6f721..3cfa3fb 100644 --- a/src/Gemtext/Body.php +++ b/src/Gemtext/Body.php @@ -111,6 +111,21 @@ class Body return $matches; } + public function getCode(): array + { + $matches = []; + + foreach ($this->_lines as $index => $line) + { + if (preg_match('/^```(.*)/', trim($line), $match)) + { + $matches[$index] = empty($match[1]) ? null : trim($match[1]); + } + } + + return $matches; + } + public function findLinks(string $protocol = 'gemini'): array { $matches = []; From 675cdf3ac46f1c64993c32ec46fbea8e1eaaca02 Mon Sep 17 00:00:00 2001 From: yggverse Date: Fri, 12 Apr 2024 14:20:19 +0300 Subject: [PATCH 35/56] implement gemtext code format --- README.md | 4 +- src/Pango.php | 147 +++++++++++++++++++++++++++++++++++--------------- 2 files changed, 105 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index a03dfcb..a38d071 100644 --- a/README.md +++ b/README.md @@ -221,10 +221,10 @@ $pango = \Yggverse\Gemini\Pango::fromGemtext( ); ``` -#### Pango::fromBody +#### Pango::fromGemtextBody ``` php -$pango = \Yggverse\Gemini\Pango::fromBody( +$pango = \Yggverse\Gemini\Pango::fromGemtextBody( new \Yggverse\Gemini\Gemtext\Body( $gemtext ) diff --git a/src/Pango.php b/src/Pango.php index fc847b4..a3855db 100644 --- a/src/Pango.php +++ b/src/Pango.php @@ -10,87 +10,146 @@ class Pango string $gemtext ): string { - return self::fromBody( + return self::fromGemtextBody( new \Yggverse\Gemini\Gemtext\Body( $gemtext ) ); } - public static function fromBody( + public static function fromGemtextBody( \Yggverse\Gemini\Gemtext\Body $body ): string { $lines = $body->getLines(); + $raw = []; + $escaped = []; + // Code + $code = $body->getCode(); + + if (count($code) % 2 == 0) // make sure tags has pairs + { + $i = 1; + + foreach ($code as $index => $capture) + { + // Replace code tags + if ($i % 2 == 0) + { + $lines[$index] = ''; + + // Skip code format inside the tags by raw registry + foreach (array_slice($lines, $offset, $index - $offset) as $start => $line) + { + $raw[$start + $offset] = $line; + } + } + + else + { + if ($capture) + { + $lines[$index] = sprintf( + '%s', + self::escape( + $capture + ) + ); + } + + else + { + $lines[$index] = ''; + } + + $offset = $index + 1; + } + + $escaped[] = $index; + + $i++; + } + } + // H1 foreach ($body->getH1() as $index => $value) { - $lines[$index] = sprintf( - '%s', - self::escape( - $value - ) - ); + if (!isset($raw[$index])) + { + $lines[$index] = sprintf( + '%s', + self::escape( + $value + ) + ); - $escaped[] = $index; + $escaped[] = $index; + } } // H2 foreach ($body->getH2() as $index => $value) { - $lines[$index] = sprintf( - '%s', - self::escape( - $value - ) - ); + if (!isset($raw[$index])) + { + $lines[$index] = sprintf( + '%s', + self::escape( + $value + ) + ); - $escaped[] = $index; + $escaped[] = $index; + } } // H3 foreach ($body->getH3() as $index => $value) { - $lines[$index] = sprintf( - '%s', - self::escape( - $value - ) - ); + if (!isset($raw[$index])) + { + $lines[$index] = sprintf( + '%s', + self::escape( + $value + ) + ); - $escaped[] = $index; - } - - // H3 - foreach ($body->getH3() as $index => $value) - { - $lines[$index] = sprintf( - '%s', - self::escape( - $value - ) - ); - - $escaped[] = $index; + $escaped[] = $index; + } } // Quote foreach ($body->getQuote() as $index => $value) { - $lines[$index] = sprintf( - '%s', - self::escape( - $value - ) - ); + if (!isset($raw[$index])) + { + $lines[$index] = sprintf( + '%s', + self::escape( + $value + ) + ); - $escaped[] = $index; + $escaped[] = $index; + } } - // @TODO links, code + // @TODO links + + // Escape special chars for non escaped lines + foreach ($body->getLines() as $index => $value) + { + if (!in_array($index, $escaped)) + { + $lines[$index] = self::escape( + $value + ); + } + } return implode( PHP_EOL, From cef15510ea63c4f66aebaa5b70faea930bb677d3 Mon Sep 17 00:00:00 2001 From: yggverse Date: Sun, 14 Apr 2024 07:48:46 +0300 Subject: [PATCH 36/56] filter link tag on line construct --- src/Gemtext/Link.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Gemtext/Link.php b/src/Gemtext/Link.php index a479257..6557391 100644 --- a/src/Gemtext/Link.php +++ b/src/Gemtext/Link.php @@ -10,12 +10,18 @@ class Link public function __construct(string $line) { - $this->_line = $line; + $this->_line = preg_replace( + '/^\s*=>(.*)/', + '$1', + trim( + $line + ) + ); } public function getAddress(): ?string { - if (preg_match('/^=>\s*([^\s]+)/', trim($this->_line), $match)) + if (preg_match('/^\s*([^\s]+)/', trim($this->_line), $match)) { return trim( $match[1] From 2cbdcbf25561d4c3aa538641549f31f6de602725 Mon Sep 17 00:00:00 2001 From: yggverse Date: Sun, 14 Apr 2024 07:50:59 +0300 Subject: [PATCH 37/56] add links support --- src/Pango.php | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Pango.php b/src/Pango.php index a3855db..97b7ad9 100644 --- a/src/Pango.php +++ b/src/Pango.php @@ -138,7 +138,39 @@ class Pango } } - // @TODO links + // Links + foreach ($body->getLinks() as $index => $line) + { + if (!isset($raw[$index])) + { + $link = new \Yggverse\Gemini\Gemtext\Link( + $line + ); + + if (!$address = $link->getAddress()) + { + continue; + } + + if (!$alt = $link->getAlt()) + { + if (!$alt = $link->getDate()) + { + $alt = $address; + } + } + + $lines[$index] = sprintf( + '%s', + $address, + self::escape( + $alt + ) + ); + + $escaped[] = $index; + } + } // Escape special chars for non escaped lines foreach ($body->getLines() as $index => $value) From b0f8e62ce53aada3277a2acd48c9e5901c9e91df Mon Sep 17 00:00:00 2001 From: yggverse Date: Tue, 16 Apr 2024 19:46:39 +0300 Subject: [PATCH 38/56] add title attribute to the links tag --- src/Pango.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Pango.php b/src/Pango.php index 97b7ad9..310ae66 100644 --- a/src/Pango.php +++ b/src/Pango.php @@ -161,8 +161,13 @@ class Pango } $lines[$index] = sprintf( - '%s', + '%s', $address, + self::escape( + urldecode( + $address + ) + ), self::escape( $alt ) From 3677e090c4ae674b0762172521f99f3b0ed8c503 Mon Sep 17 00:00:00 2001 From: yggverse Date: Tue, 16 Apr 2024 20:15:00 +0300 Subject: [PATCH 39/56] append title attribute on address does not match alt value --- src/Pango.php | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/Pango.php b/src/Pango.php index 310ae66..de46f7f 100644 --- a/src/Pango.php +++ b/src/Pango.php @@ -160,18 +160,32 @@ class Pango } } - $lines[$index] = sprintf( - '%s', - $address, - self::escape( - urldecode( - $address + if ($alt == $address) + { + $lines[$index] = sprintf( + '%s', + $address, + self::escape( + $alt ) - ), - self::escape( - $alt - ) - ); + ); + } + + else + { + $lines[$index] = sprintf( + '%s', + $address, + self::escape( + urldecode( + $address + ) + ), + self::escape( + $alt + ) + ); + } $escaped[] = $index; } From 3d86054f911d541463b9bb145094c5007808f6d1 Mon Sep 17 00:00:00 2001 From: yggverse Date: Tue, 16 Apr 2024 20:32:24 +0300 Subject: [PATCH 40/56] decode url in alt --- src/Pango.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Pango.php b/src/Pango.php index de46f7f..d6d82f4 100644 --- a/src/Pango.php +++ b/src/Pango.php @@ -156,7 +156,9 @@ class Pango { if (!$alt = $link->getDate()) { - $alt = $address; + $alt = urldecode( + $address + ); } } From f12ff026168acb23953a386e36bcd7809fe25d97 Mon Sep 17 00:00:00 2001 From: yggverse Date: Tue, 16 Apr 2024 20:34:27 +0300 Subject: [PATCH 41/56] escape all url entities --- src/Pango.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Pango.php b/src/Pango.php index d6d82f4..3d9e636 100644 --- a/src/Pango.php +++ b/src/Pango.php @@ -166,7 +166,9 @@ class Pango { $lines[$index] = sprintf( '%s', - $address, + self::escape( + $address + ), self::escape( $alt ) @@ -177,7 +179,9 @@ class Pango { $lines[$index] = sprintf( '%s', - $address, + self::escape( + $address + ), self::escape( urldecode( $address From e36d9fb2a1ef96d8a01628eb6c47ecb3be63488b Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 17 Apr 2024 09:44:36 +0300 Subject: [PATCH 42/56] use gtk version dependent pango class implementation --- README.md | 6 ++++-- src/{ => Gtk3}/Pango.php | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) rename src/{ => Gtk3}/Pango.php (99%) diff --git a/README.md b/README.md index a38d071..70d0f3d 100644 --- a/README.md +++ b/README.md @@ -209,9 +209,11 @@ var_dump( #### Link::getAlt -## Pango +## GTK3 -Converter for GTK/Pango format +### Pango + +Converter to GTK3-compatible Pango format #### Pango::fromGemtext diff --git a/src/Pango.php b/src/Gtk3/Pango.php similarity index 99% rename from src/Pango.php rename to src/Gtk3/Pango.php index 3d9e636..3a8099a 100644 --- a/src/Pango.php +++ b/src/Gtk3/Pango.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yggverse\Gemini; +namespace Yggverse\Gemini\Gtk3; class Pango { From 5b6e645a4b68b55f7e3773fe0c57afe70bbe3f9e Mon Sep 17 00:00:00 2001 From: yggverse Date: Mon, 24 Jun 2024 23:15:49 +0300 Subject: [PATCH 43/56] fix exception namespace --- src/Client/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client/Request.php b/src/Client/Request.php index 6b3583a..ac12a72 100644 --- a/src/Client/Request.php +++ b/src/Client/Request.php @@ -33,7 +33,7 @@ class Request else { - throw new Exception(); // @TODO + throw new \Exception(); // @TODO } if ($port = parse_url($url, PHP_URL_PORT)) From 0d5db685d1c8162f4e16a60d5626b73767095453 Mon Sep 17 00:00:00 2001 From: yggverse Date: Mon, 24 Jun 2024 23:16:35 +0300 Subject: [PATCH 44/56] add exception message --- src/Client/Request.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Client/Request.php b/src/Client/Request.php index ac12a72..560a1fd 100644 --- a/src/Client/Request.php +++ b/src/Client/Request.php @@ -33,7 +33,9 @@ class Request else { - throw new \Exception(); // @TODO + throw new \Exception( + _('Host required') + ); } if ($port = parse_url($url, PHP_URL_PORT)) From 9368310e74003d922449d13bdc4256432337ce55 Mon Sep 17 00:00:00 2001 From: yggverse Date: Mon, 24 Jun 2024 23:27:15 +0300 Subject: [PATCH 45/56] fix binary body detection in response --- src/Client/Response.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Client/Response.php b/src/Client/Response.php index 5988423..aabc7cf 100644 --- a/src/Client/Response.php +++ b/src/Client/Response.php @@ -17,7 +17,7 @@ class Response $match = []; preg_match( - '/(?\d{2})\s(?.*)\r\n(?.*)/su', + '/^(?\d{2})(?.*)$/m', $data, $match ); @@ -37,14 +37,16 @@ class Response if (isset($match['meta']) && mb_strlen($match['meta']) <= 1024) { $this->setMeta( - (string) $match['meta'] + trim( + (string) $match['meta'] + ) ); } - if (isset($match['body'])) + if ($body = substr($data, strpos($data, chr(10)) + 1)) { $this->setBody( - (string) (string) $match['body'] + (string) $body ); } } From 2ba31f03734d630d852a94acd5f5dffe9b6cfccd Mon Sep 17 00:00:00 2001 From: yggverse Date: Mon, 24 Jun 2024 23:32:55 +0300 Subject: [PATCH 46/56] add deprecation notice --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 70d0f3d..e5748dd 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,8 @@ var_dump( Object-oriented API for Gemtext +**Deprecated and will be removed in future releases. Take a look at [gemtext-php](https://github.com/YGGverse/gemtext-php) instead!** + ### Body Basic methods to work with `text/gemini` documents From 3ee5de206fa04eddf16a1025d4d43c73047686c9 Mon Sep 17 00:00:00 2001 From: yggverse Date: Mon, 24 Jun 2024 23:34:38 +0300 Subject: [PATCH 47/56] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e5748dd..6bfa299 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ var_dump( Object-oriented API for Gemtext -**Deprecated and will be removed in future releases. Take a look at [gemtext-php](https://github.com/YGGverse/gemtext-php) instead!** +**Deprecated and will be removed in future releases! Use [gemtext-php](https://github.com/YGGverse/gemtext-php) instead.** ### Body From 325ac1eb8ce1c6fbe85fc18382c76c756d7bd162 Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 26 Jun 2024 20:01:35 +0300 Subject: [PATCH 48/56] drop gemtext features (use gemtext-php library) --- README.md | 102 ------------------- src/Gemtext/Body.php | 235 ------------------------------------------- src/Gemtext/Link.php | 69 ------------- 3 files changed, 406 deletions(-) delete mode 100644 src/Gemtext/Body.php delete mode 100644 src/Gemtext/Link.php diff --git a/README.md b/README.md index 6bfa299..d80d855 100644 --- a/README.md +++ b/README.md @@ -109,108 +109,6 @@ var_dump( ); ``` -## Gemtext - -Object-oriented API for Gemtext - -**Deprecated and will be removed in future releases! Use [gemtext-php](https://github.com/YGGverse/gemtext-php) instead.** - -### Body - -Basic methods to work with `text/gemini` documents - -``` php -$body = new \Yggverse\Gemini\Gemtext\Body( - $response->getBody() // gemtext body from client response or .gmi file content -); -``` - -#### Body::getLines -#### Body::getLine -#### Body::getH1 -#### Body::getH2 -#### Body::getH3 -#### Body::getQuote -#### Body::getCode -#### Body::getLinks - -``` php -var_dump( - $body->getLinks() // returns array of links (with line number in key) -); -``` - -#### Body::findLinks - -Find context links by protocol as argument, `gemini` by default - -``` php -var_dump( - $body->findLinks('http') // returns array of http links only (with line number in key) -); -``` - -#### Body::skipTags - -Strip gemini tags from Gemini document - -``` php -var_dump( - $body->skipTags() // strip all tags -); - -var_dump( - $body->skipTags( - [ // 1- and 2- level headers only - "##", - "###" - ] - ) -); -``` - -### Link - -Inline links parser. - -Allows to extract address, date with timestamp and alt text from link line given - -``` php -foreach ($body->getLinks() as $line) -{ - $link = new \Yggverse\Gemini\Gemtext\Link( - $line - ); - - var_dump( - $link->getAddress() - ); - - var_dump( - $link->getAlt() - ); -} -``` - -#### Link::getAddress -#### Link::getDate - -This method also validates time format and returns the unix timestamp as linked argument - -``` php -var_dump( - $link->getDate( - $timestamp // get unix time from this variable - ) -); - -var_dump( - $timestamp -); -``` - -#### Link::getAlt - ## GTK3 ### Pango diff --git a/src/Gemtext/Body.php b/src/Gemtext/Body.php deleted file mode 100644 index 3cfa3fb..0000000 --- a/src/Gemtext/Body.php +++ /dev/null @@ -1,235 +0,0 @@ - $line) - { - $this->_lines[$index] = $line; - } - } - - public function getLine(int $index): ?int - { - return isset($this->_lines[$index]) ? $this->_lines[$index] : null; - } - - public function getLines(): array - { - return $this->_lines; - } - - public function getH1(): array - { - $matches = []; - - foreach ($this->_lines as $index => $line) - { - if (preg_match('/^#([^#]+)/', trim($line), $match)) - { - $matches[$index] = trim( - $match[1] - ); - } - } - - return $matches; - } - - public function getH2(): array - { - $matches = []; - - foreach ($this->_lines as $index => $line) - { - if (preg_match('/^##([^#]+)/', trim($line), $match)) - { - $matches[$index] = trim( - $match[1] - ); - } - } - - return $matches; - } - - public function getH3(): array - { - $matches = []; - - foreach ($this->_lines as $index => $line) - { - if (preg_match('/^###([^#]+)/', trim($line), $match)) - { - $matches[$index] = trim( - $match[1] - ); - } - } - - return $matches; - } - - public function getLinks(): array - { - $matches = []; - - foreach ($this->_lines as $index => $line) - { - if (preg_match('/^=>(.*)/', trim($line), $match)) - { - $matches[$index] = trim( - $match[1] - ); - } - } - - return $matches; - } - - public function getQuote(): array - { - $matches = []; - - foreach ($this->_lines as $index => $line) - { - if (preg_match('/^>(.*)/', trim($line), $match)) - { - $matches[$index] = trim( - $match[1] - ); - } - } - - return $matches; - } - - public function getCode(): array - { - $matches = []; - - foreach ($this->_lines as $index => $line) - { - if (preg_match('/^```(.*)/', trim($line), $match)) - { - $matches[$index] = empty($match[1]) ? null : trim($match[1]); - } - } - - return $matches; - } - - public function findLinks(string $protocol = 'gemini'): array - { - $matches = []; - - foreach ($this->_lines as $index => $line) - { - if (preg_match('/' . $protocol . ':\/\/(.*)[\s\S\'"]*/', trim($line), $match)) - { - $matches[$index] = - sprintf( - '%s://%s', - $protocol, - trim( - $match[1] - ) - ); - } - } - - return $matches; - } - - public function skipTags(array $tags = []): string - { - $lines = []; - - foreach ($this->_lines as $line) - { - $line = trim( - $line - ); - - if ($tags) - { - foreach ($tags as $tag) - { - if(!in_array($tag, ['#', '##', '###', '=>', '*', '```'])) - { - continue; - } - - switch (true) - { - case str_starts_with($line, '#'): - - $line = preg_replace( - sprintf( - '/^%s([^#]+)/ui', - $tag - ), - '$1', - $line - ); - - break; - - case str_starts_with($line, '*'): - - $line = preg_replace( - '/^\*(.*)/ui', - '$1', - $line - ); - - break; - - default: - - $line = preg_replace( - sprintf( - '/^%s(.*)/ui', - $tag - ), - '$1', - $line - ); - } - } - } - - else - { - $line = preg_replace( - [ - '/^#([^#]+)/ui', - '/^##([^#]+)/ui', - '/^###([^#]+)/ui', - '/^=>(.*)/ui', - '/^\*(.*)/ui', - '/^```(.*)/ui', - ], - '$1', - $line - ); - } - - $lines[] = trim( - $line - ); - } - - return implode( - PHP_EOL, - $lines - ); - } -} \ No newline at end of file diff --git a/src/Gemtext/Link.php b/src/Gemtext/Link.php deleted file mode 100644 index 6557391..0000000 --- a/src/Gemtext/Link.php +++ /dev/null @@ -1,69 +0,0 @@ -_line = preg_replace( - '/^\s*=>(.*)/', - '$1', - trim( - $line - ) - ); - } - - public function getAddress(): ?string - { - if (preg_match('/^\s*([^\s]+)/', trim($this->_line), $match)) - { - return trim( - $match[1] - ); - } - - return null; - } - - public function getDate(?int &$timestamp = null): ?string - { - if (preg_match('/\s([\d]+-[\d+]+-[\d]+)\s/', trim($this->_line), $match)) - { - if ($result = strtotime($match[1])) - { - $timestamp = $result; - - return trim( - $match[1] - ); - } - } - - return null; - } - - public function getAlt(): ?string - { - if (preg_match('/\s[\d]+-[\d+]+-[\d]+\s(.*)$/', trim($this->_line), $match)) - { - return trim( - $match[1] - ); - } - - else if (preg_match('/\s(.*)$/', trim($this->_line), $match)) - { - return trim( - $match[1] - ); - } - - return null; - } -} \ No newline at end of file From 639f384f29ccd46f3ba013f8f7306edcd8997095 Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 26 Jun 2024 20:02:29 +0300 Subject: [PATCH 49/56] drop Gtk3/Pango class from future release --- README.md | 24 ----- src/Gtk3/Pango.php | 225 --------------------------------------------- 2 files changed, 249 deletions(-) delete mode 100644 src/Gtk3/Pango.php diff --git a/README.md b/README.md index d80d855..5eaa349 100644 --- a/README.md +++ b/README.md @@ -109,30 +109,6 @@ var_dump( ); ``` -## GTK3 - -### Pango - -Converter to GTK3-compatible Pango format - -#### Pango::fromGemtext - -``` php -$pango = \Yggverse\Gemini\Pango::fromGemtext( - $gemtext -); -``` - -#### Pango::fromGemtextBody - -``` php -$pango = \Yggverse\Gemini\Pango::fromGemtextBody( - new \Yggverse\Gemini\Gemtext\Body( - $gemtext - ) -); -``` - ## DokuWiki Toolkit provides DokuWiki API for Gemini. diff --git a/src/Gtk3/Pango.php b/src/Gtk3/Pango.php deleted file mode 100644 index 3a8099a..0000000 --- a/src/Gtk3/Pango.php +++ /dev/null @@ -1,225 +0,0 @@ -getLines(); - - $raw = []; - - $escaped = []; - - // Code - $code = $body->getCode(); - - if (count($code) % 2 == 0) // make sure tags has pairs - { - $i = 1; - - foreach ($code as $index => $capture) - { - // Replace code tags - if ($i % 2 == 0) - { - $lines[$index] = ''; - - // Skip code format inside the tags by raw registry - foreach (array_slice($lines, $offset, $index - $offset) as $start => $line) - { - $raw[$start + $offset] = $line; - } - } - - else - { - if ($capture) - { - $lines[$index] = sprintf( - '%s', - self::escape( - $capture - ) - ); - } - - else - { - $lines[$index] = ''; - } - - $offset = $index + 1; - } - - $escaped[] = $index; - - $i++; - } - } - - // H1 - foreach ($body->getH1() as $index => $value) - { - if (!isset($raw[$index])) - { - $lines[$index] = sprintf( - '%s', - self::escape( - $value - ) - ); - - $escaped[] = $index; - } - } - - // H2 - foreach ($body->getH2() as $index => $value) - { - if (!isset($raw[$index])) - { - $lines[$index] = sprintf( - '%s', - self::escape( - $value - ) - ); - - $escaped[] = $index; - } - } - - // H3 - foreach ($body->getH3() as $index => $value) - { - if (!isset($raw[$index])) - { - $lines[$index] = sprintf( - '%s', - self::escape( - $value - ) - ); - - $escaped[] = $index; - } - } - - // Quote - foreach ($body->getQuote() as $index => $value) - { - if (!isset($raw[$index])) - { - $lines[$index] = sprintf( - '%s', - self::escape( - $value - ) - ); - - $escaped[] = $index; - } - } - - // Links - foreach ($body->getLinks() as $index => $line) - { - if (!isset($raw[$index])) - { - $link = new \Yggverse\Gemini\Gemtext\Link( - $line - ); - - if (!$address = $link->getAddress()) - { - continue; - } - - if (!$alt = $link->getAlt()) - { - if (!$alt = $link->getDate()) - { - $alt = urldecode( - $address - ); - } - } - - if ($alt == $address) - { - $lines[$index] = sprintf( - '%s', - self::escape( - $address - ), - self::escape( - $alt - ) - ); - } - - else - { - $lines[$index] = sprintf( - '%s', - self::escape( - $address - ), - self::escape( - urldecode( - $address - ) - ), - self::escape( - $alt - ) - ); - } - - $escaped[] = $index; - } - } - - // Escape special chars for non escaped lines - foreach ($body->getLines() as $index => $value) - { - if (!in_array($index, $escaped)) - { - $lines[$index] = self::escape( - $value - ); - } - } - - return implode( - PHP_EOL, - $lines - ); - } - - public static function escape( - string $value - ): string - { - return htmlspecialchars( - $value - ); - } -} \ No newline at end of file From 8a895adbdfed333fa27b6a9ef6d016c496bcf78a Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 26 Jun 2024 20:04:16 +0300 Subject: [PATCH 50/56] drop Dokuwiki features from future release --- README.md | 263 ----------------------- src/Dokuwiki/Filesystem.php | 277 ------------------------ src/Dokuwiki/Helper.php | 154 -------------- src/Dokuwiki/Reader.php | 412 ------------------------------------ 4 files changed, 1106 deletions(-) delete mode 100644 src/Dokuwiki/Filesystem.php delete mode 100644 src/Dokuwiki/Helper.php delete mode 100644 src/Dokuwiki/Reader.php diff --git a/README.md b/README.md index 5eaa349..0fe3a06 100644 --- a/README.md +++ b/README.md @@ -109,269 +109,6 @@ var_dump( ); ``` -## DokuWiki - -Toolkit provides DokuWiki API for Gemini. - -Allows to simple deploy new apps or make existing website mirror - -### Examples - -* [β-Doku](https://github.com/YGGverse/bdoku) - DokuWiki Satellite for Gemini Protocol - -### Reader - -Read DokuWiki and convert to Gemini - -``` php -$reader = new \Yggverse\Gemini\Dokuwiki\Reader( - // optional regex rule set array -); -``` - -#### Reader::getRules -#### Reader::setRules -#### Reader::getRule -#### Reader::setRule - -Get or change existing regex rule (or just skip by using build-in set) - -``` php -echo $reader->setRule( - '/subject/ui', - 'replacement' -); -``` - -#### Reader::getMacroses -#### Reader::setMacroses -#### Reader::getMacros -#### Reader::setMacros - -``` php -echo $reader->setMacros( - '~my-macros-key~', - '~my-macros-value~', -); -``` - -#### Reader::toGemini - -Convert DokuWiki text to Gemini markup - -As wiki has lot of inline links, to make converted document well-readable, this method does not replace links with new line `=>` macros, but uses inline context: `Name ( URL )`. This model useful with `Reader::getLinks` method, that for example appends all those related links to the document footer. - -If you don't like this implementation, feel free to change it by `Reader::setRule` method! - -``` php -echo $reader->toGemini( - file_get_contents( - '/host/data/pages/index.txt' - ) -); -``` - -#### Reader::getH1 - -Get document title - -``` php -$gemini = $reader->toGemini( - file_get_contents( - '/host/data/pages/index.txt' - ) -); - -echo $reader->getH1( - $gemini -); -``` - -#### Reader::getLinks - -Get document links - -``` php -$gemini = $reader->toGemini( - file_get_contents( - '/host/data/pages/index.txt' - ) -); - -echo $reader->getLinks( - $gemini -); -``` - -### Filesystem - -Provides methods for simple and secure interaction with DokuWiki file storage - -``` php -$filesystem = new \Yggverse\Gemini\Dokuwiki\Filesystem( - '/host/data' // storage location -); -``` - -#### Filesystem::getList - -Return simple array of all files in storage - -``` php -var_dump ( - $filesystem->getList( - 'hello:world' - ) -); -``` - -#### Filesystem::getTree - -Return all files under the storage folder in tree format - -``` php -var_dump ( - $filesystem->getTree( - 'hello:world' - ) -); -``` - -#### Filesystem::getPagePathsByPath - -Return pages under the given data directory - -``` php -var_dump ( - $filesystem->getPagePathsByPath( - // absolute path to target data directory (e.g. Filesystem::getDirectoryPathByUri) - ) -); -``` - -#### Filesystem::getDirectoryPathByUri -#### Filesystem::getPagePathByUri - -Return absolute path to stored page file - -``` php -var_dump ( - $filesystem->getPagePathByUri( - 'hello:world' - ) -); -``` - -#### Filesystem::getDirectoryUriByPath -#### Filesystem::getPageUriByPath - -Return page URI in `dokuwiki:format` - -``` php -var_dump ( - $filesystem->getPageUriByPath( - '/full/path/to/page.txt' - ) -); -``` - -#### Filesystem::getMediaPathByUri - -Return absolute path to stored media file - -``` php -var_dump ( - $filesystem->getMediaPathByUri( - 'hello:world' - ) -); -``` - -#### Filesystem::getMimeByPath - -Return file MIME if path match storage item - -``` php -var_dump ( - $filesystem->getMimeByPath( - '/full/path/to/page.txt' - ) -); -``` - -#### Filesystem::getDataByPath - -Return file content if path match storage item - -``` php -var_dump ( - $filesystem->getDataByPath( - '/full/path/to/page.txt' - ) -); -``` - -#### Filesystem::isPath - -Check path exist and match storage item - -``` php -var_dump ( - $filesystem->isPath( - '/full/path/to/page.txt' - ) -); -``` - -### Helper - -Useful methods to minify controller codebase - -``` php -$helper = new \Yggverse\Gemini\Dokuwiki\Helper( - new \Yggverse\Gemini\Dokuwiki\Filesystem(), - new \Yggverse\Gemini\Dokuwiki\Reader() -); -``` - -#### Helper::getChildrenSectionLinksByUri - -Return simple array of children section links in Gemini format - -``` php -var_dump ( - $helper->getChildrenSectionLinksByUri( - 'hello:world' - ) -); -``` - -#### Helper::getChildrenPageLinksByUri - -Return simple array of children page links in Gemini format - -``` php -var_dump ( - $helper->getChildrenPageLinksByUri( - 'hello:world' - ) -); -``` - -#### Helper::getPageLinkByPath - -Return page link (that contain document name) in Gemini format - -``` php -var_dump ( - $helper->getPageLinkByPath( - $filesystem->getPagePathByUri( - 'hello:world' - ) - ) -); -``` - ## Integrations * [β-Doku is DokuWiki Satellite for Gemini Protocol](https://github.com/YGGverse/bdoku) diff --git a/src/Dokuwiki/Filesystem.php b/src/Dokuwiki/Filesystem.php deleted file mode 100644 index 33b79c2..0000000 --- a/src/Dokuwiki/Filesystem.php +++ /dev/null @@ -1,277 +0,0 @@ -_path = rtrim( - $path, - '/' - ); - - $this->_index( - $this->_path - ); - } - - public function getTree(): array - { - return $this->_tree; - } - - public function getList(): array - { - return $this->_list; - } - - public function getPagePathsByPath(string $path): ?array - { - if (isset($this->_tree[$path])) - { - return $this->_tree[$path]; - } - - return null; - } - - public function getPagePathByUri(string $uri): ?string - { - $path = sprintf( - '%s/pages/%s.txt', - $this->_path, - str_replace( - ':', - '/', - mb_strtolower( - urldecode( - $uri - ) - ) - ) - ); - - if (!$this->isPath($path)) - { - return null; - } - - return $path; - } - - public function getPageUriByPath(string $path): ?string - { - if (!$this->isPath($path)) - { - return null; - } - - $path = str_replace( - sprintf( - '%s/pages/', - $this->_path - ), - '', - $path - ); - - $path = trim( - $path, - '/' - ); - - $path = str_replace( - [ - '/', - '.txt' - ], - [ - ':', - null - ], - $path - ); - - return $path; - } - - public function getDirectoryPathByUri(string $uri = ''): ?string - { - $path = rtrim( - sprintf( - '%s/pages/%s', - $this->_path, - str_replace( - ':', - '/', - mb_strtolower( - urldecode( - $uri - ) - ) - ) - ), - '/' - ); - - if (!isset($this->_tree[$path]) || !is_dir($path) || !is_readable($path)) - { - return null; - } - - return $path; - } - - public function getDirectoryUriByPath(string $path): ?string - { - if (!isset($this->_tree[$path]) || !is_dir($path) || !is_readable($path)) - { - return null; - } - - $path = str_replace( - sprintf( - '%s/pages', - $this->_path - ), - '', - $path - ); - - $path = trim( - $path, - '/' - ); - - $path = str_replace( - [ - '/' - ], - [ - ':' - ], - $path - ); - - return $path; - } - - public function getMediaPathByUri(string $uri): ?string - { - $path = sprintf( - '%s/media/%s', - $this->_path, - str_replace( - ':', - '/', - mb_strtolower( - urldecode( - $uri - ) - ) - ) - ); - - if (!$this->isPath($path)) - { - return null; - } - - return $path; - } - - public function getMimeByPath(?string $path): ?string - { - if ($this->isPath($path)) - { - if ($mime = mime_content_type($path)) - { - return $mime; - } - } - - return null; - } - - public function getDataByPath(?string $path): ?string - { - if ($this->isPath($path)) - { - if ($data = file_get_contents($path)) - { - return $data; - } - } - - return null; - } - - public function isPath(?string $path): bool - { - if (in_array($path, $this->_list) && is_file($path) && is_readable($path)) - { - return true; - } - - return false; - } - - private function _index( - string $path, - ?array $blacklist = ['sidebar.txt', '__template.txt'] - ): void - { - foreach ((array) scandir($path) as $file) - { - if (str_starts_with($file, '.')) - { - continue; - } - - if (is_link($file)) - { - continue; - } - - if (in_array($file, $blacklist)) - { - continue; - } - - $file = sprintf( - '%s/%s', - $path, - $file - ); - - switch (true) - { - case is_dir($file): - - if (!isset($this->_tree[$path])) - { - $this->_tree[$path] = []; - } - - $this->_index($file); - - break; - - case is_file($file): - - $this->_tree[$path][] = $file; - - $this->_list[] = $file; - - break; - } - } - } -} \ No newline at end of file diff --git a/src/Dokuwiki/Helper.php b/src/Dokuwiki/Helper.php deleted file mode 100644 index 232b10f..0000000 --- a/src/Dokuwiki/Helper.php +++ /dev/null @@ -1,154 +0,0 @@ -_filesystem = $filesystem; - $this->_reader = $reader; - } - - public function getChildrenSectionLinksByUri(?string $uri = ''): array - { - $sections = []; - - if ($directory = $this->_filesystem->getDirectoryPathByUri($uri)) - { - foreach ((array) $this->_filesystem->getTree() as $path => $files) - { - if (str_starts_with($path, $directory) && $path != $directory) - { - // Init link name - $h1 = null; - - // Init this directory URI - $thisUri = $this->_filesystem->getDirectoryUriByPath( - $path - ); - - // Skip sections deeper this level - if (substr_count($thisUri, ':') > ($uri ? substr_count($uri, ':') + 1 : 0)) - { - continue; - } - - // Get section names - $segments = []; - - foreach ((array) explode(':', $thisUri) as $segment) - { - $segments[] = $segment; - - // Find section index if exists - if ($file = $this->_filesystem->getPagePathByUri(implode(':', $segments) . ':' . $segment)) - { - $h1 = $this->_reader->getH1( - $this->_reader->toGemini( - $this->_filesystem->getDataByPath( - $file - ) - ) - ); - } - - // Find section page if exists - else if ($file = $this->_filesystem->getPagePathByUri(implode(':', $segments))) - { - $h1 = $this->_reader->getH1( - $this->_reader->toGemini( - $this->_filesystem->getDataByPath( - $file - ) - ) - ); - } - - // Reset title of undefined segment - else - { - $h1 = null; - } - } - - // Register section link - $sections[] = sprintf( - '=> /%s %s', - $thisUri, - $h1 - ); - } - } - } - - // Keep unique - $sections = array_unique( - $sections - ); - - // Sort asc - sort( - $sections - ); - - return $sections; - } - - public function getChildrenPageLinksByUri(?string $uri = ''): array - { - $pages = []; - - if ($directory = $this->_filesystem->getDirectoryPathByUri($uri)) - { - foreach ((array) $this->_filesystem->getPagePathsByPath($directory) as $file) - { - if ($link = $this->getPageLinkByPath($file)) - { - $pages[] = $link; - } - } - } - - // Keep unique - $pages = array_unique( - $pages - ); - - // Sort asc - sort( - $pages - ); - - return $pages; - } - - public function getPageLinkByPath(string $path): ?string - { - if (in_array($path, $this->_filesystem->getList()) && is_file($path) && is_readable($path)) - { - return sprintf( - '=> /%s %s', - $this->_filesystem->getPageUriByPath( - $path - ), - $this->_reader->getH1( - $this->_reader->toGemini( - $this->_filesystem->getDataByPath( - $path - ) - ) - ) - ); - } - - return null; - } -} \ No newline at end of file diff --git a/src/Dokuwiki/Reader.php b/src/Dokuwiki/Reader.php deleted file mode 100644 index 95accef..0000000 --- a/src/Dokuwiki/Reader.php +++ /dev/null @@ -1,412 +0,0 @@ - null, - '~IPv6:open~' => '[', - '~IPv6:close~' => ']', - '~LINE:break~' => PHP_EOL - ]; - - private array $_rule = - [ - // Headers - '/^([\s]*)#([^#]+)/' => '$1#$2' . PHP_EOL, - '/^([\s]*)##([^#]+)/' => '$1##$2' . PHP_EOL, - '/^([\s]*)###([^#]+)/' => '$1###$2' . PHP_EOL, - '/^([\s]*)####([^#]+)/' => '$1###$2' . PHP_EOL, - '/^([\s]*)#####([^#]+)/' => '$1###$2' . PHP_EOL, - '/^([\s]*)######([^#]+)/' => '$1###$2' . PHP_EOL, - - '/^[\s]*[=]{6}([^=]+)[=]{6}/' => '# $1' . PHP_EOL, - '/^[\s]*[=]{5}([^=]+)[=]{5}/' => '## $1' . PHP_EOL, - '/^[\s]*[=]{4}([^=]+)[=]{4}/' => '### $1' . PHP_EOL, - '/^[\s]*[=]{3}([^=]+)[=]{3}/' => '### $1' . PHP_EOL, - '/^[\s]*[=]{2}([^=]+)[=]{2}/' => '### $1' . PHP_EOL, - '/^[\s]*[=]{1}([^=]+)[=]{1}/' => '### $1' . PHP_EOL, - - // Tags - '/\*\*/' => '', - '/\'\'/' => '', - '/\%\%/' => '', - '/(? '', - - // Remove extra spaces - '/(\s)\s+/' => '$1', - - // Links - - /// Detect IPv6 (used as no idea how to resolve square quotes in rules below) - '/\[\[([^\[]+)\[([A-f:0-9]*)\]([^\]]+)\]\]/' => '$1~IPv6:open~$2~IPv6:close~$3', - - /// Remove extra chars - '/\[\[\s*\:?([^\|]+)\s*\|\s*([^\]]+)\s*\]\]/' => '[[$1|$2]]', - '/\[\[\s*\:?([^\]]+)\s*\]\]/' => '[[$1]]', - - '/\{\{\s*\:?([^\|]+)\s*\|\s*([^\}]+)\s*\}\}/' => '{{$1|$2}}', - '/\{\{\s*\:?([^\}]+)\s*\}\}/' => '{{$1}}', - - /// Wikipedia - '/\[\[wp([A-z]{2,})>([^\|]+)\|([^\]]+)\]\]/ui' => '$3 ( https://$1.wikipedia.org/wiki/$2 )', - '/\[\[wp>([^\|]+)\|([^\]]+)\]\]/i' => '$2 ( https://en.wikipedia.org/wiki/$1 )', - '/\[\[wp([A-z]{2,})>([^\]]+)\]\]/i' => '$2 ( https://$1.wikipedia.org/wiki/$2 )', - '/\[\[wp>([^\]]+)\]\]/i' => '$1 ( https://en.wikipedia.org/wiki/$1 )', - - /// Dokuwiki - '/\[\[doku>([^\|]+)\|([^\]]+)\]\]/i' => '$2( https://www.dokuwiki.org/$1 )', - '/\[\[doku>([^\]]+)\]\]/i' => '$1( https://www.dokuwiki.org/$1 )', - - /// Index - /// Useful with src/Dokuwiki/Helper.php - '/\{\{indexmenu>:([^\}]+)\}\}/i' => '', - '/\{\{indexmenu_n>[\d]+\}\}/i' => '', - - // Related - '/\[\[this>([^\|]+)\|([^\]]+)\]\]/i' => '$2', - - /// Relative - '/\[\[(?!https?:|this|doku|wp[A-z]{0,2})([^\|]+)\|([^\]]+)\]\]/i' => ' $2$3 ( ~URL:base~$1 )', - '/\[\[(?!https?:|this|doku|wp[A-z]{0,2})([^\]]+)\]\]/i' => ' $2 ( ~URL:base~$1 )', - - /// Absolute - '/\[\[(https?:)([^\|]+)\|([^\]]+)\]\]/i' => '$3 ( $1$2 )', - '/\[\[(https?:)([^\]]+)\]\]/i' => '$1$2', // @TODO - - /// Media - '/\{\{(?!https?:)([^\|]+)\|([^\}]+)\}\}/i' => PHP_EOL . '=> /$1$2' . PHP_EOL, - '/\{\{(?!https?:)([^\}]+)\}\}/i' => PHP_EOL . '=> /$1$2' . PHP_EOL, - - // List - '/^[\s]?-/' => '* ', - '/^[\s]+\*/' => '*', - - // Separators - '/[\\\]{2}/' => '~LINE:break~', - - // Plugins - '/~~DISCUSSION~~/' => '', // @TODO - '/~~INFO:syntaxplugins~~/' => '', // @TODO - - // Final corrections - '/[\n\r]+[.,;:]+/' => PHP_EOL - ]; - - public function __construct(?array $rules = null) - { - if ($rules) - { - $this->_rule = $rules; - } - } - - // Macros operations - public function getMacroses(): array - { - $this->_macros; - } - - public function setMacroses(array $macros) - { - $this->_macros = $macros; - } - - public function getMacros(string $key, string $value): ?string - { - $this->_macros[$key] = isset($this->_macros[$key]) ? $value : null; - } - - public function setMacros(string $key, ?string $value): void - { - if ($value) - { - $this->_macros[$key] = $value; - } - - else - { - unset( - $this->_macros[$key] - ); - } - } - - // Rule operations - public function getRules(): array - { - $this->_rule; - } - - public function setRules(array $rules) - { - $this->_rule = $rules; - } - - public function getRule(string $key, string $value): ?string - { - $this->_rule[$key] = isset($this->_rule[$key]) ? $value : null; - } - - public function setRule(string $key, ?string $value): void - { - if ($value) - { - $this->_rule[$key] = $value; - } - - else - { - unset( - $this->_rule[$key] - ); - } - } - - // Convert DokuWiki text to Gemini - public function toGemini(?string $data, ?array &$lines = []): ?string - { - if (empty($data)) - { - return null; - } - - $raw = false; - - $lines = []; - - foreach ((array) explode(PHP_EOL, $data) as $line) - { - // Skip any formatting in lines between code tag - if (!$raw && preg_match('/<(code|file)([^>]*)>/i', $line, $matches)) - { - // Prepend tag meta or filename as plain description - if (!empty($matches[0])) - { - $lines[] = preg_replace( - '/<(code|file)[\s-]*([^>]*)>/i', - '$2', - $matches[0] - ); - } - - $lines[] = '```'; - $lines[] = preg_replace( - '/<\/?(code|file)[^>]*>/i', - '', - $line - ); - - $raw = true; - - // Make sure inline tag closed - if (preg_match('/<\/(code|file)>/i', $line)) - { - $lines[] = '```'; - - $raw = false; - - continue; - } - - continue; - } - - if ($raw && preg_match('/<\/(code|file)>/i', $line)) - { - $lines[] = preg_replace( - '/<\/(code|file)>/i', - '', - $line - ); - - $lines[] = '```'; - - $raw = false; - - continue; - } - - if ($raw) - { - $lines[] = preg_replace( - '/^```/', - ' ```', - $line - ); - - continue; - } - - // Apply config - $lines[] = preg_replace( - array_keys( - $this->_rule - ), - array_values( - $this->_rule - ), - strip_tags( - $line - ) - ); - } - - // ASCII table - $table = false; - - $rows = []; - - $th = []; - - foreach ($lines as $index => $line) - { - // Strip line breaks - $line = str_replace( - '~LINE:break~', - ' ', - $line - ); - - // Header - if (!$table && preg_match_all('/\^([^\^]+)/', $line, $matches)) - { - if (!empty($matches[1])) - { - $table = true; - - $rows = []; - - $th = []; - - foreach ($matches[1] as $value) - { - $th[] = trim( - $value - ); - } - - unset( - $lines[$index] - ); - - continue; - } - } - - // Body - if ($table) - { - $table = false; - - if (preg_match(sprintf('/%s\|/', str_repeat('\|(.*)', count($th))), $line, $matches)) - { - if (count($matches) == count($th) + 1) - { - $table = true; - - $row = []; - foreach ($th as $offset => $column) - { - $row[$column] = trim( - $matches[$offset + 1] - ); - } - - $rows[] = $row; - - unset( - $lines[$index] - ); - } - } - - if (!$table && $rows) - { - $builder = new ArrayToTextTable( - $rows - ); - - $lines[$index] = '```' . PHP_EOL . $builder->render() . PHP_EOL . '```'; - } - } - } - - // Merge lines - return preg_replace( - '/[\n\r]{2,}/', - PHP_EOL . PHP_EOL, - str_replace( - array_keys( - $this->_macros - ), - array_values( - $this->_macros - ), - implode( - PHP_EOL, - $lines - ) - ) - ); - } - - public function getH1(?string $gemini, ?string $regex = '/^[\s]?#([^#]+)/'): ?string - { - foreach ((array) explode(PHP_EOL, (string) $gemini) as $line) - { - preg_match( - $regex, - $line, - $matches - ); - - if (!empty($matches[1])) - { - return trim( - $matches[1] - ); - - break; - } - } - - return null; - } - - public function getLinks(?string $gemini, ?string $regex = '/(https?|gemini):\/\/\S+/'): array - { - $links = []; - - if (empty($gemini)) - { - return $links; - } - - preg_match_all( - $regex, - $gemini, - $matches - ); - - if (!empty($matches[0])) - { - foreach ((array) $matches[0] as $link) - { - $links[] = trim( - $link - ); - } - } - - return array_unique( - $links - ); - } -} \ No newline at end of file From 5b3a714194d1a4e29c4e6af257f5484bbd42a1f0 Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 26 Jun 2024 20:05:42 +0300 Subject: [PATCH 51/56] update links --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0fe3a06..579424d 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,7 @@ var_dump( ## Integrations -* [β-Doku is DokuWiki Satellite for Gemini Protocol](https://github.com/YGGverse/bdoku) +* [gemini-dl - CLI Batch downloader for Gemini Protocol](https://github.com/YGGverse/gemini-dl) * [Yo! Crawler for different networks](https://github.com/YGGverse/Yo/tree/gemini) -* [Yoda - PHP-GTK browser for Gemini Protocol](https://github.com/YGGverse/Yoda) \ No newline at end of file +* [Yoda - PHP-GTK browser for Gemini Protocol](https://github.com/YGGverse/Yoda) +* [β-Doku is DokuWiki Satellite for Gemini Protocol](https://github.com/YGGverse/bdoku) \ No newline at end of file From d8c7b0668baa544bd096c36f1c9de9494723285e Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 26 Jun 2024 20:12:29 +0300 Subject: [PATCH 52/56] update readme --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 579424d..3d4f7e8 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ PHP 8 Library for [Gemini Protocol](https://geminiprotocol.net) +## Extras + +* [gemtext-php](https://github.com/YGGverse/gemtext-php) - Object-oriented PHP 8 library for Gemini / Gemtext operations + ## Usage ``` @@ -111,7 +115,7 @@ var_dump( ## Integrations -* [gemini-dl - CLI Batch downloader for Gemini Protocol](https://github.com/YGGverse/gemini-dl) -* [Yo! Crawler for different networks](https://github.com/YGGverse/Yo/tree/gemini) -* [Yoda - PHP-GTK browser for Gemini Protocol](https://github.com/YGGverse/Yoda) -* [β-Doku is DokuWiki Satellite for Gemini Protocol](https://github.com/YGGverse/bdoku) \ No newline at end of file +* [gemini-dl](https://github.com/YGGverse/gemini-dl) - CLI Batch downloader for Gemini Protocol +* [Yo!](https://github.com/YGGverse/Yo/tree/gemini) - Crawler for different networks +* [Yoda](https://github.com/YGGverse/Yoda) - PHP-GTK browser for Gemini Protocol +* [β-Doku](https://github.com/YGGverse/bdoku) - DokuWiki Satellite for Gemini Protocol \ No newline at end of file From f10537947d129e4c10248e8d52e34bf59d9a6858 Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 26 Jun 2024 20:15:08 +0300 Subject: [PATCH 53/56] remove extra dependency, update tags --- composer.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index efc7aa6..33c1ae1 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "yggverse/gemini", "description": "PHP 8 Library for Gemini Protocol", - "keywords": [ "yggverse", "gemini", "wiki", "dokuwiki", "markdown" ], + "keywords": [ "yggverse", "gemini", "gemini-protocol", "client", "request", "response" ], "homepage": "https://github.com/yggverse/gemini-php", "type": "library", "license": "MIT", @@ -10,7 +10,5 @@ "Yggverse\\Gemini\\": "src/" } }, - "require": { - "dekor/php-array-table": "^2.0" - } + "require": {} } From ee3d403364087ee0ad1ff880e11018e1797aafca Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 26 Jun 2024 20:42:50 +0300 Subject: [PATCH 54/56] update readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3d4f7e8..39de76d 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ PHP 8 Library for [Gemini Protocol](https://geminiprotocol.net) +_For optimization reasons, some experimental features like `Dokuwiki` and `GTK3/Pango` was dropped from `1.0.0` release, but available in [previous versions](https://github.com/YGGverse/gemini-php/releases/tag/0.10.1). `Gemtext` component re-implemented as separated library (see [Extras](#extras))_ + ## Extras * [gemtext-php](https://github.com/YGGverse/gemtext-php) - Object-oriented PHP 8 library for Gemini / Gemtext operations From acad5e14cc2f0a3b203f1b522b631b84ec8c8bf3 Mon Sep 17 00:00:00 2001 From: yggverse Date: Mon, 8 Jul 2024 04:36:04 +0300 Subject: [PATCH 55/56] fix query format --- src/Client/Request.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Client/Request.php b/src/Client/Request.php index 560a1fd..b0c37ce 100644 --- a/src/Client/Request.php +++ b/src/Client/Request.php @@ -184,7 +184,10 @@ class Request $this->_host, $this->_port, $this->_path, - $this->_query + $this->_query ? sprintf( + '?%s', + $this->_query + ) : null ) ); From 597a105be779076561302e35a4ea27235eeb07b3 Mon Sep 17 00:00:00 2001 From: yggverse Date: Sat, 3 Aug 2024 02:17:26 +0300 Subject: [PATCH 56/56] update default options --- src/Client/Request.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Client/Request.php b/src/Client/Request.php index b0c37ce..183ce56 100644 --- a/src/Client/Request.php +++ b/src/Client/Request.php @@ -17,8 +17,10 @@ class Request [ 'ssl' => [ - 'verify_peer' => false, - 'verify_peer_name' => false + 'allow_self_signed' => true, + 'disable_compression' => true, + 'verify_peer_name' => false, + 'verify_peer' => false ] ];