From 20618a63c94232fbd59d2142a923c1e2d2f5d61b Mon Sep 17 00:00:00 2001 From: gomzyakov Date: Sun, 15 Oct 2023 20:10:09 +0500 Subject: [PATCH 1/8] Add main.html & move code to class --- generate.php | 110 +---------------- src/Generator.php | 107 ++++++++++++++++- src/Templates/Example/main.html | 201 ++++++++++++++++++++++++++++++++ 3 files changed, 312 insertions(+), 106 deletions(-) create mode 100644 src/Templates/Example/main.html diff --git a/generate.php b/generate.php index cc4affe..88c2ff9 100644 --- a/generate.php +++ b/generate.php @@ -1,110 +1,10 @@ run() +require_once('./vendor/autoload.php'); +use GoodFirstIssue\Generator; -if (!is_dir('lang')) { - mkdir('lang'); -} - - -// Read the JSON file -$json = file_get_contents('repositories.json'); - -// Decode the JSON file -$json_data = json_decode($json, true); - -// Display data -print_r($json_data); - -// TODO Рандомизируем массив с репозиториями - -$indexContent = "

Hello!

"; - -$repositoriesByLanguage = []; - -// Проходимся по всем репозиториям -foreach ($json_data as $line) { - echo "Line : https://api.github.com/repos/" . $line . "\n"; - // Записываем информацию о репозитории в файл data/repositories.json - // Это необходимо для главной страницы - - $opts = [ - 'http' => [ - 'method' => 'GET', - 'header' => [ - 'User-Agent: PHP' - ] - ] - ]; - - $context = stream_context_create($opts); - $repositoryJson = file_get_contents('https://api.github.com/repos/' . $line, false, $context); - $repository = json_decode($repositoryJson, true); - - $repositoryData = [ - 'html_url' => $repository['html_url'], // Ex: "https://github.com/octocat/Hello-World" - 'full_name' => $repository['full_name'], // Ex: "octocat/Hello-World" - 'description' => $repository['description'], // Ex: "This your first repo!" - 'language' => $repository['language'], // Ex: null, - 'stargazers_count' => $repository['stargazers_count'], // Ex: 80, - 'open_issues_count' => $repository['open_issues_count'], // Ex: 0, - 'open_issues' => $repository['open_issues'], // Ex: 0, - 'updated_at' => $repository['updated_at'], // Ex: "2011-01-26T19:14:43Z", - ]; - - print_r($repositoryData); - - // Конетент для главной страницы - $indexContent .= '

' . $repositoryData['full_name'] . '

'; - $indexContent .= '

' . $repositoryData['description'] . '

'; - - $repositoriesByLanguage[$repositoryData['language']][] = $repositoryData['full_name']; - - // Записываем ищуйки в общий файл -} - - -file_put_contents('index.html', $indexContent); - - -foreach ($repositoriesByLanguage as $lang => $repositories) { - if (strlen($lang) < 1) { - $lang = 'other'; - } - - print_r('Language: ' . $lang); - - $langFile = 'lang/' . $lang . '.html'; - if (file_exists($langFile)) { - $status = unlink($langFile) ? 'The file ' . $langFile . ' has been deleted' . "\n" : 'Error deleting ' . $langFile . "\n"; - echo $status; - } - - - // TODO Пишем шапку файла - file_put_contents($langFile, '

Lang: ' . $lang . '

' . "\n"); - - foreach ($repositories as $repository) { - print_r('Repository: ' . $repository."\n"); - - $issuesJson = file_get_contents('https://api.github.com/repos/' . $repository . '/issues?state=open&sort=updated&labels=good%20first%20issue', false, $context); - $issues = json_decode($issuesJson, true); - - foreach ($issues as $issue) { - print_r('Issue #' . $issue['number'] . ' ' . $issue['title'] . "\n"); - - $str = '

' . $issue['title'] . '

'; - $str .= '

' . $issue['html_url'] . '

'; - - file_put_contents($langFile, $str, FILE_APPEND); - } - - } - - -} - +$generator = new Generator(__DIR__); +$generator->run(); diff --git a/src/Generator.php b/src/Generator.php index d88ba22..858d53c 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -4,6 +4,111 @@ namespace GoodFirstIssue; -class Generator +readonly class Generator { + public function __construct( + private string $rootPath + ) { + } + + public function run(): void + { + if (! is_dir($this->rootPath . '/lang')) { + mkdir($this->rootPath . '/lang'); + } + + // Read the JSON file + $json = file_get_contents('repositories.json'); + + // Decode the JSON file + $json_data = json_decode($json, true); + + // Display data + print_r($json_data); + + // TODO Рандомизируем массив с репозиториями + + $indexContent = '

Hello!

'; + + $repositoriesByLanguage = []; + + // Проходимся по всем репозиториям + foreach ($json_data as $line) { + echo 'Line : https://api.github.com/repos/' . $line . "\n"; + // Записываем информацию о репозитории в файл data/repositories.json + // Это необходимо для главной страницы + + $opts = [ + 'http' => [ + 'method' => 'GET', + 'header' => [ + 'User-Agent: PHP', + ], + ], + ]; + + $context = stream_context_create($opts); + $repositoryJson = file_get_contents('https://api.github.com/repos/' . $line, false, $context); + $repository = json_decode($repositoryJson, true); + + $repositoryData = [ + 'html_url' => $repository['html_url'], // Ex: "https://github.com/octocat/Hello-World" + 'full_name' => $repository['full_name'], // Ex: "octocat/Hello-World" + 'description' => $repository['description'], // Ex: "This your first repo!" + 'language' => $repository['language'], // Ex: null, + 'stargazers_count' => $repository['stargazers_count'], // Ex: 80, + 'open_issues_count' => $repository['open_issues_count'], // Ex: 0, + 'open_issues' => $repository['open_issues'], // Ex: 0, + 'updated_at' => $repository['updated_at'], // Ex: "2011-01-26T19:14:43Z", + ]; + + print_r($repositoryData); + + // Конетент для главной страницы + $indexContent .= '

' . $repositoryData['full_name'] . '

'; + $indexContent .= '

' . $repositoryData['description'] . '

'; + + $repositoriesByLanguage[$repositoryData['language']][] = $repositoryData['full_name']; + + // Записываем ищуйки в общий файл + } + + + file_put_contents('index.html', $indexContent); + + + foreach ($repositoriesByLanguage as $lang => $repositories) { + if (strlen($lang) < 1) { + $lang = 'other'; + } + + print_r('Language: ' . $lang); + + $langFile = 'lang/' . $lang . '.html'; + if (file_exists($langFile)) { + $status = unlink($langFile) ? 'The file ' . $langFile . ' has been deleted' . "\n" : 'Error deleting ' . $langFile . "\n"; + echo $status; + } + + + // TODO Пишем шапку файла + file_put_contents($langFile, '

Lang: ' . $lang . '

' . "\n"); + + foreach ($repositories as $repository) { + print_r('Repository: ' . $repository . "\n"); + + $issuesJson = file_get_contents('https://api.github.com/repos/' . $repository . '/issues?state=open&sort=updated&labels=good%20first%20issue', false, $context); + $issues = json_decode($issuesJson, true); + + foreach ($issues as $issue) { + print_r('Issue #' . $issue['number'] . ' ' . $issue['title'] . "\n"); + + $str = '

' . $issue['title'] . '

'; + $str .= '

' . $issue['html_url'] . '

'; + + file_put_contents($langFile, $str, FILE_APPEND); + } + } + } + } } diff --git a/src/Templates/Example/main.html b/src/Templates/Example/main.html new file mode 100644 index 0000000..a11c157 --- /dev/null +++ b/src/Templates/Example/main.html @@ -0,0 +1,201 @@ + + + + + + good first issue + + + + + + +
+
+
+ +
+
+ + gomzyakov/good-first-issue + +

+ + This is a wider card with supporting text below as a natural lead-in to + additional content. This content is a little bit longer. + +

+
+ + lang: C# + +   + stars: 4.17K +   + + last activity: 10 months ago + +
+
+ +
+ +
+
+ + gomzyakov/good-first-issue + +

+ + This is a wider card with supporting text below as a natural lead-in to + additional content. This content is a little bit longer. + +

+
+ + lang: C# + +   + stars: 4.17K +   + + last activity: 10 months ago + +
+
+ +
+ +
+
+ + gomzyakov/good-first-issue + +

+ + This is a wider card with supporting text below as a natural lead-in to + additional content. This content is a little bit longer. + +

+
+ + lang: C# + +   + stars: 4.17K +   + + last activity: 10 months ago + +
+
+ +
+ + +
+
+
About
+ +

Good First Issue curates easy pickings from popular open-source projects, and helps you make your first + contribution to open-source.

+ + + + +

+ Made with ♥ by + + Alexander Gomzyakov + +

+
+
+
+ + + + + \ No newline at end of file From e9a5375ddb50e3d8ddf92720610c00a162df22ae Mon Sep 17 00:00:00 2001 From: gomzyakov Date: Sun, 15 Oct 2023 20:28:49 +0500 Subject: [PATCH 2/8] Add chunks of main.html --- src/Generator.php | 1 + src/Templates/{Example => Examples}/main.html | 3 -- src/Templates/main.html | 53 +++++++++++++++++++ src/Templates/main_card.html | 32 +++++++++++ src/Templates/main_card_li.html | 6 +++ 5 files changed, 92 insertions(+), 3 deletions(-) rename src/Templates/{Example => Examples}/main.html (99%) create mode 100644 src/Templates/main.html create mode 100644 src/Templates/main_card.html create mode 100644 src/Templates/main_card_li.html diff --git a/src/Generator.php b/src/Generator.php index 858d53c..22c844f 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -73,6 +73,7 @@ public function run(): void // Записываем ищуйки в общий файл } + // TODO сортируем репозитории по updated at file_put_contents('index.html', $indexContent); diff --git a/src/Templates/Example/main.html b/src/Templates/Examples/main.html similarity index 99% rename from src/Templates/Example/main.html rename to src/Templates/Examples/main.html index a11c157..d211986 100644 --- a/src/Templates/Example/main.html +++ b/src/Templates/Examples/main.html @@ -168,7 +168,6 @@ -
About
@@ -182,7 +181,6 @@
About
-

Made with ♥ by @@ -193,7 +191,6 @@

About
- diff --git a/src/Templates/main.html b/src/Templates/main.html new file mode 100644 index 0000000..8a63260 --- /dev/null +++ b/src/Templates/main.html @@ -0,0 +1,53 @@ + + + + + + good first issue + + + + + + +
+
+
+ + %CARDS% + +
+
+
About
+ +

Good First Issue curates easy pickings from popular open-source projects, and helps you make your first + contribution to open-source.

+ + + +

+ Made with ♥ by + + Alexander Gomzyakov + +

+
+
+
+ + + + \ No newline at end of file diff --git a/src/Templates/main_card.html b/src/Templates/main_card.html new file mode 100644 index 0000000..0a5476e --- /dev/null +++ b/src/Templates/main_card.html @@ -0,0 +1,32 @@ +
+
+ + gomzyakov/good-first-issue + +

+ + This is a wider card with supporting text below as a natural lead-in to + additional content. This content is a little bit longer. + +

+
+ + lang: C# + +   + stars: 4.17K +   + + last activity: 10 months ago + +
+
+ +
diff --git a/src/Templates/main_card_li.html b/src/Templates/main_card_li.html new file mode 100644 index 0000000..7bbd7d2 --- /dev/null +++ b/src/Templates/main_card_li.html @@ -0,0 +1,6 @@ +
  • + + Set `level: 2` in phpstan.neon.dist + + upd: 17 feb 2023 +
  • From 59ee582d6b1e3d8aa74cf9a25c3a9eafcceeb271 Mon Sep 17 00:00:00 2001 From: gomzyakov Date: Tue, 17 Oct 2023 21:57:10 +0500 Subject: [PATCH 3/8] Split code to classes --- src/DTO/Issue.php | 15 ++ src/DTO/Repository.php | 43 ++++++ src/Generator.php | 188 +++++++++++++------------ src/Renderer.php | 160 ++++++++++++++++++++++ src/Templates/Examples/issues.html | 211 +++++++++++++++++++++++++++++ src/Templates/Examples/main.html | 2 +- src/Templates/main_card.html | 7 +- src/Templates/main_card_li.html | 6 +- 8 files changed, 540 insertions(+), 92 deletions(-) create mode 100644 src/DTO/Issue.php create mode 100644 src/DTO/Repository.php create mode 100644 src/Renderer.php create mode 100644 src/Templates/Examples/issues.html diff --git a/src/DTO/Issue.php b/src/DTO/Issue.php new file mode 100644 index 0000000..326de43 --- /dev/null +++ b/src/DTO/Issue.php @@ -0,0 +1,15 @@ + + */ + private array $issues = []; + + public function __construct( + public readonly string $html_url, // Ex: "https://github.com/octocat/Hello-World" + public readonly string $full_name,// Ex: "octocat/Hello-World" + public readonly string $description, // Ex: "This your first repo!" + public readonly string $language, // Ex: null, + public readonly int $stargazers_count, // Ex: 80, + public readonly int $open_issues_count, // Ex: 0, + public readonly int $open_issues, // Ex: 0, + public readonly string $updated_at // Ex: "2011-01-26T19:14:43Z", + ) { + } + + /** + * @return array + */ + public function getIssues(): array + { + return $this->issues; + } + + /** + * @param array $issues + * + * @return void + */ + public function setIssues(array $issues): void + { + $this->issues = $issues; + } +} diff --git a/src/Generator.php b/src/Generator.php index 22c844f..47a20a3 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -4,112 +4,132 @@ namespace GoodFirstIssue; +use GoodFirstIssue\DTO\Issue; +use GoodFirstIssue\DTO\Repository; + readonly class Generator { public function __construct( - private string $rootPath + private string $root_path ) { } - public function run(): void + /** + * TODO To Client. + * + * @param string $repository_name + * + * @return Repository + */ + public function requestRepositoryInfo(string $repository_name): Repository { - if (! is_dir($this->rootPath . '/lang')) { - mkdir($this->rootPath . '/lang'); - } - - // Read the JSON file - $json = file_get_contents('repositories.json'); - - // Decode the JSON file - $json_data = json_decode($json, true); - - // Display data - print_r($json_data); - - // TODO Рандомизируем массив с репозиториями - - $indexContent = '

    Hello!

    '; - - $repositoriesByLanguage = []; - - // Проходимся по всем репозиториям - foreach ($json_data as $line) { - echo 'Line : https://api.github.com/repos/' . $line . "\n"; - // Записываем информацию о репозитории в файл data/repositories.json - // Это необходимо для главной страницы - - $opts = [ - 'http' => [ - 'method' => 'GET', - 'header' => [ - 'User-Agent: PHP', - ], - ], - ]; - - $context = stream_context_create($opts); - $repositoryJson = file_get_contents('https://api.github.com/repos/' . $line, false, $context); - $repository = json_decode($repositoryJson, true); - - $repositoryData = [ - 'html_url' => $repository['html_url'], // Ex: "https://github.com/octocat/Hello-World" - 'full_name' => $repository['full_name'], // Ex: "octocat/Hello-World" - 'description' => $repository['description'], // Ex: "This your first repo!" - 'language' => $repository['language'], // Ex: null, - 'stargazers_count' => $repository['stargazers_count'], // Ex: 80, - 'open_issues_count' => $repository['open_issues_count'], // Ex: 0, - 'open_issues' => $repository['open_issues'], // Ex: 0, - 'updated_at' => $repository['updated_at'], // Ex: "2011-01-26T19:14:43Z", - ]; - - print_r($repositoryData); + $opts = [ + 'http' => [ + 'method' => 'GET', + 'header' => ['User-Agent: PHP'], + ], + ]; + + $context = stream_context_create($opts); + $repositoryJson = file_get_contents('https://api.github.com/repos/' . $repository_name, false, $context); + $repository = json_decode($repositoryJson, true); + + return new Repository( + $repository['html_url'], + $repository['full_name'], + $repository['description'], + $repository['language'], + $repository['stargazers_count'], + $repository['open_issues_count'], + $repository['open_issues'], + $repository['updated_at'], + ); + } - // Конетент для главной страницы - $indexContent .= '

    ' . $repositoryData['full_name'] . '

    '; - $indexContent .= '

    ' . $repositoryData['description'] . '

    '; + /** + * TODO To Client. + * + * @param string $repository_name + * + * @return array + */ + public function requestIssues(string $repository_name): array + { + $opts = [ + 'http' => [ + 'method' => 'GET', + 'header' => ['User-Agent: PHP'], + ], + ]; + + $context = stream_context_create($opts); + + $issuesJson = file_get_contents('https://api.github.com/repos/' . $repository_name . '/issues?state=open&sort=updated&labels=good%20first%20issue', false, $context); + $issues_data = json_decode($issuesJson, true); + + foreach ($issues_data as $data) { + print_r('Issue #' . $data['number'] . ' ' . $data['title'] . "\n"); + + $issues[] = new Issue( + $data['html_url'], + $data['title'], + $data['number'] + ); + } - $repositoriesByLanguage[$repositoryData['language']][] = $repositoryData['full_name']; + return $issues ?? []; + } - // Записываем ищуйки в общий файл + /** + * Get information about all repositories + * TODO To Client. + * + * @param array $repository_names + * + * @return array + */ + public function requestRepositoriesData(array $repository_names): array + { + foreach ($repository_names as $line) { + $repository = $this->requestRepositoryInfo($line); + $repositories[] = $repository; } - // TODO сортируем репозитории по updated at - - file_put_contents('index.html', $indexContent); + return $repositories ?? []; + } + public function run(): void + { + if (! is_dir($this->root_path . '/lang')) { + mkdir($this->root_path . '/lang'); + } - foreach ($repositoriesByLanguage as $lang => $repositories) { - if (strlen($lang) < 1) { - $lang = 'other'; - } + // Get repository names from `repositories.json` file + $json = file_get_contents('repositories.json'); + $repository_names = json_decode($json, true); - print_r('Language: ' . $lang); + print_r($repository_names); - $langFile = 'lang/' . $lang . '.html'; - if (file_exists($langFile)) { - $status = unlink($langFile) ? 'The file ' . $langFile . ' has been deleted' . "\n" : 'Error deleting ' . $langFile . "\n"; - echo $status; - } + $repositories = $this->requestRepositoriesData($repository_names); + // TODO сортируем репозитории по updated at - // TODO Пишем шапку файла - file_put_contents($langFile, '

    Lang: ' . $lang . '

    ' . "\n"); + foreach ($repositories as $repository) { + print_r('Get issues for repository: ' . $repository->full_name . "\n"); - foreach ($repositories as $repository) { - print_r('Repository: ' . $repository . "\n"); + $issues = $this->requestIssues($repository->full_name); + $repository->setIssues($issues); + } - $issuesJson = file_get_contents('https://api.github.com/repos/' . $repository . '/issues?state=open&sort=updated&labels=good%20first%20issue', false, $context); - $issues = json_decode($issuesJson, true); - foreach ($issues as $issue) { - print_r('Issue #' . $issue['number'] . ' ' . $issue['title'] . "\n"); + $repositories_by_language = []; + foreach ($repositories as $repository) { + $repositories_by_language[$repository->language][] = $repository; + } - $str = '

    ' . $issue['title'] . '

    '; - $str .= '

    ' . $issue['html_url'] . '

    '; + $renderer = new Renderer($this->root_path); + $renderer->buildIndexHTML($repositories); - file_put_contents($langFile, $str, FILE_APPEND); - } - } - } + // TODO by lang } } diff --git a/src/Renderer.php b/src/Renderer.php new file mode 100644 index 0000000..ed43a52 --- /dev/null +++ b/src/Renderer.php @@ -0,0 +1,160 @@ + $repositories + * + * @return void + */ + public function buildIndexHTML(array $repositories): void + { + $cards_html = ''; + foreach ($repositories as $repository) { + $list_items_html = ''; + foreach ($repository->getIssues() as $issue) { + $list_items_html .= $this->renderCardListItemHTML($issue); + } + + $cards_html .= $this->renderCardHTML($repository, $list_items_html); + } + + // TODO To prif + $mainHTML = file_get_contents($this->rootPath . '/Templates/main.html'); + $replace_pairs = [ + '%CARDS%' => $cards_html, + ]; + + $html = strtr($mainHTML, $replace_pairs); + + file_put_contents('index.html', $html); + } + + private function renderCardHTML(Repository $repository, string $list_items_html): string + { + $main_card_template = file_get_contents($this->rootPath . '/Templates/main_card.html'); + + $replace_pairs = [ + '%REPO_URL%' => $repository->html_url, + '%REPO_NAME%' => $repository->full_name, + '%REPO_DESCRIPTION%' => $repository->description, + '%ISSUES_LIST_HTML%' => $list_items_html, + ]; + + return strtr($main_card_template, $replace_pairs); + } + + private function renderCardListItemHTML(Issue $issue): string + { + $mainCardLiTemplate = file_get_contents($this->rootPath . '/Templates/main_card_li.html'); + + $replace_pairs = [ + '_ISSUE_HREF_' => $issue->html_url, + '_ISSUE_TITLE_' => $issue->title, + '_ISSUE_UPDATED_AT_' => 'TODO', + ]; + + return strtr($mainCardLiTemplate, $replace_pairs); + } + + // public function buildLangs(array $repositories): void + // { + // + // $repositoriesByLanguage = []; + // + // // Проходимся по всем репозиториям + // foreach ($json_data as $line) { + // echo 'Line : https://api.github.com/repos/' . $line . "\n"; + // // Записываем информацию о репозитории в файл data/repositories.json + // // Это необходимо для главной страницы + // + // $opts = [ + // 'http' => [ + // 'method' => 'GET', + // 'header' => [ + // 'User-Agent: PHP', + // ], + // ], + // ]; + // + // $context = stream_context_create($opts); + // $repositoryJson = file_get_contents('https://api.github.com/repos/' . $line, false, $context); + // $repository = json_decode($repositoryJson, true); + // + // $repositoryData = [ + // 'html_url' => $repository['html_url'], // Ex: "https://github.com/octocat/Hello-World" + // 'full_name' => $repository['full_name'], // Ex: "octocat/Hello-World" + // 'description' => $repository['description'], // Ex: "This your first repo!" + // 'language' => $repository['language'], // Ex: null, + // 'stargazers_count' => $repository['stargazers_count'], // Ex: 80, + // 'open_issues_count' => $repository['open_issues_count'], // Ex: 0, + // 'open_issues' => $repository['open_issues'], // Ex: 0, + // 'updated_at' => $repository['updated_at'], // Ex: "2011-01-26T19:14:43Z", + // ]; + // + // print_r($repositoryData); + // + // // Конетент для главной страницы + // $indexContent .= '

    ' . $repositoryData['full_name'] . '

    '; + // $indexContent .= '

    ' . $repositoryData['description'] . '

    '; + // + // $repositoriesByLanguage[$repositoryData['language']][] = $repositoryData['full_name']; + // + // // Записываем ищуйки в общий файл + // } + // + // + // file_put_contents($langFile, $str, FILE_APPEND); + // + // file_put_contents('index.html', $indexContent); + // + // + // foreach ($repositoriesByLanguage as $lang => $repositories) { + // if (strlen($lang) < 1) { + // $lang = 'other'; + // } + // + // print_r('Language: ' . $lang); + // + // $langFile = 'lang/' . $lang . '.html'; + // if (file_exists($langFile)) { + // $status = unlink($langFile) ? 'The file ' . $langFile . ' has been deleted' . "\n" : 'Error deleting ' . $langFile . "\n"; + // echo $status; + // } + // + // + // // TODO Пишем шапку файла + // file_put_contents($langFile, '

    Lang: ' . $lang . '

    ' . "\n"); + // + // foreach ($repositories as $repository) { + // print_r('Repository: ' . $repository . "\n"); + // + // $issuesJson = file_get_contents('https://api.github.com/repos/' . $repository . '/issues?state=open&sort=updated&labels=good%20first%20issue', false, $context); + // $issues = json_decode($issuesJson, true); + // + // foreach ($issues as $issue) { + // print_r('Issue #' . $issue['number'] . ' ' . $issue['title'] . "\n"); + // + // $str = '

    ' . $issue['title'] . '

    '; + // $str .= '

    ' . $issue['html_url'] . '

    '; + // + // file_put_contents($langFile, $str, FILE_APPEND); + // } + // } + // } + // } +} diff --git a/src/Templates/Examples/issues.html b/src/Templates/Examples/issues.html new file mode 100644 index 0000000..fe5359e --- /dev/null +++ b/src/Templates/Examples/issues.html @@ -0,0 +1,211 @@ + + + + + + good first issue + + + + + + +
    +
    +
    + +
    +
    + + Package tgalopin/html-sanitizer is abandoned, you should avoid using it. Use + symfony/html-sanitizer instead. + +
    + + lang: PHP + +   + repo: gomzyakov/good-first-issue +   + + upd: 10 months ago + +
    +
    +
    + +
    +
    + + Set `level: 2` in phpstan.neon.dist + +
    + + lang: PHP + +   + repo: gomzyakov/good-first-issue +   + + upd: 10 months ago + +
    +
    +
    + +
    +
    + + Fix column styles on md and sm-resolutions + +
    + + lang: PHP + +   + repo: gomzyakov/good-first-issue +   + + upd: 3 feb 2023 + +
    +
    +
    + + +
    +
    + + gomzyakov/good-first-issue + +

    + + This is a wider card with supporting text below as a natural lead-in to + additional content. This content is a little bit longer. + +

    +
    + + lang: C# + +   + stars: 4.17K +   + + last activity: 10 months ago + +
    +
    + +
    + +
    +
    + + gomzyakov/good-first-issue + +

    + + This is a wider card with supporting text below as a natural lead-in to + additional content. This content is a little bit longer. + +

    +
    + + lang: C# + +   + stars: 4.17K +   + + last activity: 10 months ago + +
    +
    + +
    + +
    +
    +
    About
    + +

    Good First Issue curates easy pickings from popular open-source projects, and helps you make your first + contribution to open-source.

    + + + +

    + Made with ♥ by + + Alexander Gomzyakov + +

    +
    +
    +
    + + + + \ No newline at end of file diff --git a/src/Templates/Examples/main.html b/src/Templates/Examples/main.html index d211986..b6a10fe 100644 --- a/src/Templates/Examples/main.html +++ b/src/Templates/Examples/main.html @@ -195,4 +195,4 @@
    About
    integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"> - \ No newline at end of file + diff --git a/src/Templates/main_card.html b/src/Templates/main_card.html index 0a5476e..c4dc175 100644 --- a/src/Templates/main_card.html +++ b/src/Templates/main_card.html @@ -2,12 +2,11 @@
    - gomzyakov/good-first-issue + %REPO_NAME%

    - This is a wider card with supporting text below as a natural lead-in to - additional content. This content is a little bit longer. + %REPO_DESCRIPTION%

    @@ -25,7 +24,7 @@ diff --git a/src/Templates/main_card_li.html b/src/Templates/main_card_li.html index 7bbd7d2..b4bc7c2 100644 --- a/src/Templates/main_card_li.html +++ b/src/Templates/main_card_li.html @@ -1,6 +1,6 @@
  • - - Set `level: 2` in phpstan.neon.dist + + _ISSUE_TITLE_ - upd: 17 feb 2023 + upd: _ISSUE_UPDATED_AT_
  • From a4ad191d8127dae605fd9fb7e1a53d2dc38c82fb Mon Sep 17 00:00:00 2001 From: gomzyakov Date: Wed, 18 Oct 2023 08:46:50 +0500 Subject: [PATCH 4/8] Fix Generator.php --- src/DTO/Issue.php | 2 +- src/Generator.php | 4 +++- src/Renderer.php | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/DTO/Issue.php b/src/DTO/Issue.php index 326de43..befba3c 100644 --- a/src/DTO/Issue.php +++ b/src/DTO/Issue.php @@ -9,7 +9,7 @@ public function __construct( public string $html_url, public string $title, - public string $number + public int $number ) { } } diff --git a/src/Generator.php b/src/Generator.php index 47a20a3..f614e89 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -34,11 +34,13 @@ public function requestRepositoryInfo(string $repository_name): Repository $repositoryJson = file_get_contents('https://api.github.com/repos/' . $repository_name, false, $context); $repository = json_decode($repositoryJson, true); + $lang = (strlen((string) $repository['language']) < 1) ? 'other' : $repository['language']; + return new Repository( $repository['html_url'], $repository['full_name'], $repository['description'], - $repository['language'], + $lang, $repository['stargazers_count'], $repository['open_issues_count'], $repository['open_issues'], diff --git a/src/Renderer.php b/src/Renderer.php index ed43a52..082d30f 100644 --- a/src/Renderer.php +++ b/src/Renderer.php @@ -34,7 +34,7 @@ public function buildIndexHTML(array $repositories): void } // TODO To prif - $mainHTML = file_get_contents($this->rootPath . '/Templates/main.html'); + $mainHTML = file_get_contents($this->rootPath . '/src/Templates/main.html'); $replace_pairs = [ '%CARDS%' => $cards_html, ]; @@ -46,7 +46,7 @@ public function buildIndexHTML(array $repositories): void private function renderCardHTML(Repository $repository, string $list_items_html): string { - $main_card_template = file_get_contents($this->rootPath . '/Templates/main_card.html'); + $main_card_template = file_get_contents($this->rootPath . '/src/Templates/main_card.html'); $replace_pairs = [ '%REPO_URL%' => $repository->html_url, @@ -60,7 +60,7 @@ private function renderCardHTML(Repository $repository, string $list_items_html) private function renderCardListItemHTML(Issue $issue): string { - $mainCardLiTemplate = file_get_contents($this->rootPath . '/Templates/main_card_li.html'); + $mainCardLiTemplate = file_get_contents($this->rootPath . '/src/Templates/main_card_li.html'); $replace_pairs = [ '_ISSUE_HREF_' => $issue->html_url, From 0e8df2d2672ed5fdd1210cbf4c9c2a62dfacf86d Mon Sep 17 00:00:00 2001 From: gomzyakov Date: Fri, 20 Oct 2023 17:20:21 +0500 Subject: [PATCH 5/8] Extract GitHubAPIClient --- generate.php | 3 +- src/Generator.php | 111 ++++++---------------------------------- src/GitHubAPIClient.php | 86 +++++++++++++++++++++++++++++++ src/Renderer.php | 51 +++++++++++++----- 4 files changed, 141 insertions(+), 110 deletions(-) create mode 100644 src/GitHubAPIClient.php diff --git a/generate.php b/generate.php index 88c2ff9..13c4b4d 100644 --- a/generate.php +++ b/generate.php @@ -5,6 +5,7 @@ require_once('./vendor/autoload.php'); use GoodFirstIssue\Generator; +use GoodFirstIssue\GitHubAPIClient; -$generator = new Generator(__DIR__); +$generator = new Generator(__DIR__, new GitHubAPIClient); $generator->run(); diff --git a/src/Generator.php b/src/Generator.php index f614e89..be82810 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -4,102 +4,17 @@ namespace GoodFirstIssue; -use GoodFirstIssue\DTO\Issue; use GoodFirstIssue\DTO\Repository; +use LogicException; readonly class Generator { public function __construct( - private string $root_path + private string $root_path, + private GitHubAPIClient $github_api_client ) { } - /** - * TODO To Client. - * - * @param string $repository_name - * - * @return Repository - */ - public function requestRepositoryInfo(string $repository_name): Repository - { - $opts = [ - 'http' => [ - 'method' => 'GET', - 'header' => ['User-Agent: PHP'], - ], - ]; - - $context = stream_context_create($opts); - $repositoryJson = file_get_contents('https://api.github.com/repos/' . $repository_name, false, $context); - $repository = json_decode($repositoryJson, true); - - $lang = (strlen((string) $repository['language']) < 1) ? 'other' : $repository['language']; - - return new Repository( - $repository['html_url'], - $repository['full_name'], - $repository['description'], - $lang, - $repository['stargazers_count'], - $repository['open_issues_count'], - $repository['open_issues'], - $repository['updated_at'], - ); - } - - /** - * TODO To Client. - * - * @param string $repository_name - * - * @return array - */ - public function requestIssues(string $repository_name): array - { - $opts = [ - 'http' => [ - 'method' => 'GET', - 'header' => ['User-Agent: PHP'], - ], - ]; - - $context = stream_context_create($opts); - - $issuesJson = file_get_contents('https://api.github.com/repos/' . $repository_name . '/issues?state=open&sort=updated&labels=good%20first%20issue', false, $context); - $issues_data = json_decode($issuesJson, true); - - foreach ($issues_data as $data) { - print_r('Issue #' . $data['number'] . ' ' . $data['title'] . "\n"); - - $issues[] = new Issue( - $data['html_url'], - $data['title'], - $data['number'] - ); - } - - return $issues ?? []; - } - - /** - * Get information about all repositories - * TODO To Client. - * - * @param array $repository_names - * - * @return array - */ - public function requestRepositoriesData(array $repository_names): array - { - foreach ($repository_names as $line) { - $repository = $this->requestRepositoryInfo($line); - $repositories[] = $repository; - } - - return $repositories ?? []; - } - public function run(): void { if (! is_dir($this->root_path . '/lang')) { @@ -107,31 +22,37 @@ public function run(): void } // Get repository names from `repositories.json` file - $json = file_get_contents('repositories.json'); + $json = file_get_contents('repositories.json'); + if (! is_string($json)) { + throw new LogicException('Cannot read file: repositories.json'); + } + $repository_names = json_decode($json, true); + if (! is_array($repository_names)) { + throw new LogicException('Cannot read decode repository names'); + } print_r($repository_names); - $repositories = $this->requestRepositoriesData($repository_names); + $repositories = $this->github_api_client->requestRepositoriesData($repository_names); // TODO сортируем репозитории по updated at foreach ($repositories as $repository) { print_r('Get issues for repository: ' . $repository->full_name . "\n"); - $issues = $this->requestIssues($repository->full_name); + $issues = $this->github_api_client->requestIssues($repository->full_name); $repository->setIssues($issues); } + $renderer = new Renderer($this->root_path); + $renderer->renderIndexPage($repositories); $repositories_by_language = []; foreach ($repositories as $repository) { $repositories_by_language[$repository->language][] = $repository; } - $renderer = new Renderer($this->root_path); - $renderer->buildIndexHTML($repositories); - - // TODO by lang + // TODO $renderer->renderPerLanguagePage($repositories); } } diff --git a/src/GitHubAPIClient.php b/src/GitHubAPIClient.php new file mode 100644 index 0000000..4934b93 --- /dev/null +++ b/src/GitHubAPIClient.php @@ -0,0 +1,86 @@ + [ + 'method' => 'GET', + 'header' => ['User-Agent: PHP'], + ], + ]; + + /** + * Get information about all repositories. + * + * @param array $repository_names + * + * @return array + */ + public function requestRepositoriesData(array $repository_names): array + { + foreach ($repository_names as $repository_name) { + $repository = $this->requestRepositoryData($repository_name); + $repositories[] = $repository; + } + + return $repositories ?? []; + } + + /** + * @param string $repository_name + * + * @return Repository + */ + public function requestRepositoryData(string $repository_name): Repository + { + $context = stream_context_create(self::OPTS); + + $repository_json = file_get_contents('https://api.github.com/repos/' . $repository_name, false, $context); + $repository = json_decode($repository_json, true); + + $lang = (strlen((string) $repository['language']) < 1) ? 'other' : $repository['language']; + + return new Repository( + $repository['html_url'], + $repository['full_name'], + $repository['description'], + $lang, + $repository['stargazers_count'], + $repository['open_issues_count'], + $repository['open_issues'], + $repository['updated_at'], + ); + } + + /** + * @param string $repository_name + * + * @return array + */ + public function requestIssues(string $repository_name): array + { + $context = stream_context_create(self::OPTS); + + $issues_json = file_get_contents('https://api.github.com/repos/' . $repository_name . '/issues?state=open&sort=updated&labels=good%20first%20issue', false, $context); + $issues_data = json_decode($issues_json, true); + + foreach ($issues_data as $data) { + print_r('Issue #' . $data['number'] . ' ' . $data['title'] . "\n"); + + $issues[] = new Issue( + $data['html_url'], + $data['title'], + $data['number'] + ); + } + + return $issues ?? []; + } +} diff --git a/src/Renderer.php b/src/Renderer.php index 082d30f..318c525 100644 --- a/src/Renderer.php +++ b/src/Renderer.php @@ -6,11 +6,12 @@ use GoodFirstIssue\DTO\Issue; use GoodFirstIssue\DTO\Repository; +use LogicException; readonly class Renderer { public function __construct( - private string $rootPath + private string $root_path ) { } @@ -21,7 +22,29 @@ public function __construct( * * @return void */ - public function buildIndexHTML(array $repositories): void + public function renderIndexPage(array $repositories): void + { + $main_html = file_get_contents($template_path = $this->root_path . '/src/Templates/main.html'); + + if (! is_string($main_html)) { + throw new LogicException('Cannot read file: ' . $template_path); + } + + $replace_pairs = [ + '%CARDS%' => $this->renderCardsListHTML($repositories), + ]; + + $html = strtr($main_html, $replace_pairs); + + file_put_contents('index.html', $html); + } + + /** + * @param array $repositories + * + * @return string + */ + private function renderCardsListHTML(array $repositories): string { $cards_html = ''; foreach ($repositories as $repository) { @@ -33,20 +56,16 @@ public function buildIndexHTML(array $repositories): void $cards_html .= $this->renderCardHTML($repository, $list_items_html); } - // TODO To prif - $mainHTML = file_get_contents($this->rootPath . '/src/Templates/main.html'); - $replace_pairs = [ - '%CARDS%' => $cards_html, - ]; - - $html = strtr($mainHTML, $replace_pairs); - - file_put_contents('index.html', $html); + return $cards_html; } private function renderCardHTML(Repository $repository, string $list_items_html): string { - $main_card_template = file_get_contents($this->rootPath . '/src/Templates/main_card.html'); + $main_card_template = file_get_contents($template_path = $this->root_path . '/src/Templates/main_card.html'); + + if (! is_string($main_card_template)) { + throw new LogicException('Cannot read file: ' . $template_path); + } $replace_pairs = [ '%REPO_URL%' => $repository->html_url, @@ -60,7 +79,11 @@ private function renderCardHTML(Repository $repository, string $list_items_html) private function renderCardListItemHTML(Issue $issue): string { - $mainCardLiTemplate = file_get_contents($this->rootPath . '/src/Templates/main_card_li.html'); + $list_item_template = file_get_contents($template_path = $this->root_path . '/src/Templates/main_card_li.html'); + + if (! is_string($list_item_template)) { + throw new LogicException('Cannot read file: ' . $template_path); + } $replace_pairs = [ '_ISSUE_HREF_' => $issue->html_url, @@ -68,7 +91,7 @@ private function renderCardListItemHTML(Issue $issue): string '_ISSUE_UPDATED_AT_' => 'TODO', ]; - return strtr($mainCardLiTemplate, $replace_pairs); + return strtr($list_item_template, $replace_pairs); } // public function buildLangs(array $repositories): void From 2610a502a0ec704511744042b617b42022537a29 Mon Sep 17 00:00:00 2001 From: gomzyakov Date: Sat, 21 Oct 2023 13:28:02 +0500 Subject: [PATCH 6/8] Fix tests --- .gitignore | 6 ++++++ src/Generator.php | 2 +- src/GitHubAPIClient.php | 19 +++++++++++++++++-- tests/GeneratorTest.php | 7 +++++-- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 035edb9..1049f24 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,12 @@ # Composer /vendor/ +# PHPUnit +.phpunit.cache + # Generated files index.html lang + +# Mac OS +.DS_Store diff --git a/src/Generator.php b/src/Generator.php index be82810..c3e62ca 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -29,7 +29,7 @@ public function run(): void $repository_names = json_decode($json, true); if (! is_array($repository_names)) { - throw new LogicException('Cannot read decode repository names'); + throw new LogicException('Cannot decode repository names'); } print_r($repository_names); diff --git a/src/GitHubAPIClient.php b/src/GitHubAPIClient.php index 4934b93..faee22b 100644 --- a/src/GitHubAPIClient.php +++ b/src/GitHubAPIClient.php @@ -6,6 +6,7 @@ use GoodFirstIssue\DTO\Issue; use GoodFirstIssue\DTO\Repository; +use LogicException; readonly class GitHubAPIClient { @@ -42,8 +43,15 @@ public function requestRepositoryData(string $repository_name): Repository { $context = stream_context_create(self::OPTS); - $repository_json = file_get_contents('https://api.github.com/repos/' . $repository_name, false, $context); + $repository_json = file_get_contents($api_route = 'https://api.github.com/repos/' . $repository_name, false, $context); + if (! is_string($repository_json)) { + throw new LogicException('Cannot read GitHub API response from ' . $api_route); + } + $repository = json_decode($repository_json, true); + if (! is_array($repository)) { + throw new LogicException('Cannot decode repository data'); + } $lang = (strlen((string) $repository['language']) < 1) ? 'other' : $repository['language']; @@ -68,8 +76,15 @@ public function requestIssues(string $repository_name): array { $context = stream_context_create(self::OPTS); - $issues_json = file_get_contents('https://api.github.com/repos/' . $repository_name . '/issues?state=open&sort=updated&labels=good%20first%20issue', false, $context); + $issues_json = file_get_contents($api_route = 'https://api.github.com/repos/' . $repository_name . '/issues?state=open&sort=updated&labels=good%20first%20issue', false, $context); + if (! is_string($issues_json)) { + throw new LogicException('Cannot read GitHub API response from ' . $api_route); + } + $issues_data = json_decode($issues_json, true); + if (! is_array($issues_data)) { + throw new LogicException('Cannot decode issues data'); + } foreach ($issues_data as $data) { print_r('Issue #' . $data['number'] . ' ' . $data['title'] . "\n"); diff --git a/tests/GeneratorTest.php b/tests/GeneratorTest.php index 6de746f..6e4f701 100644 --- a/tests/GeneratorTest.php +++ b/tests/GeneratorTest.php @@ -2,17 +2,20 @@ declare(strict_types = 1); +namespace Tests; + use GoodFirstIssue\Generator; +use GoodFirstIssue\GitHubAPIClient; use PHPUnit\Framework\TestCase; /** - * @coversNothing + * @covers \GoodFirstIssue\Generator */ final class GeneratorTest extends TestCase { public function test_instance_of_generator(): void { - $generator = new Generator(); + $generator = new Generator(__DIR__, new GitHubAPIClient()); $this->assertInstanceOf(Generator::class, $generator); } From 013d4c3868db316da63f28f624201d4435c6142c Mon Sep 17 00:00:00 2001 From: gomzyakov Date: Sat, 21 Oct 2023 13:31:05 +0500 Subject: [PATCH 7/8] Install Composer dependencies --- .github/workflows/generate.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml index fed59d8..3a485a5 100644 --- a/.github/workflows/generate.yml +++ b/.github/workflows/generate.yml @@ -47,6 +47,9 @@ jobs: - name: Show PHP version run: php -v + - name: Install Composer dependencies + run: composer install + - name: Generate HTML files run: php generate.php From f12094a2508715770c7aa08c50992540ee17593a Mon Sep 17 00:00:00 2001 From: gomzyakov Date: Sat, 21 Oct 2023 13:37:32 +0500 Subject: [PATCH 8/8] Update `composer install` command --- .github/workflows/generate.yml | 2 +- .github/workflows/phpstan.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml index 3a485a5..444ab54 100644 --- a/.github/workflows/generate.yml +++ b/.github/workflows/generate.yml @@ -48,7 +48,7 @@ jobs: run: php -v - name: Install Composer dependencies - run: composer install + run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist - name: Generate HTML files run: php generate.php diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index ec68bab..fa6e588 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -21,7 +21,7 @@ jobs: php-version: 8.2 - name: Install Composer dependencies - run: composer update --no-interaction --prefer-dist + run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist - name: Run tests via PHPStan run: composer phpstan