From b5877e2385fdac5f4011638017d095e6eb9683bb Mon Sep 17 00:00:00 2001 From: are0h Date: Fri, 21 Oct 2022 13:35:12 -0700 Subject: [PATCH] Updating files and added php formatting First commit to the new repo so I'm doing a bit of housecleaning, which is cleaning up some old files, adding some changes that had been made elsewhere and adding a php format config file to normalize formatting across multiple projects. --- .php-cs-fixer.php | 74 +++++++++ data/posts.json | 82 ++++++++-- data/settings.json | 31 +++- engine/StartKit.php | 2 +- engine/ThemeEngine.php | 150 +++++++++--------- index.php | 4 +- .../fipamo-default/archive.twig | 80 +++++----- .../fipamo-default/page.twig | 111 ++++++------- 8 files changed, 340 insertions(+), 194 deletions(-) create mode 100644 .php-cs-fixer.php diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php new file mode 100644 index 0000000..cd8588c --- /dev/null +++ b/.php-cs-fixer.php @@ -0,0 +1,74 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12' => true, + 'array_indentation' => true, + 'array_syntax' => [ + 'syntax' => 'short', + ], + 'combine_consecutive_unsets' => true, + 'method_chaining_indentation' => true, + 'class_attributes_separation' => [ + 'elements' => [ + 'const' => 'none', + 'method' => 'one', + 'property' => 'none', + 'trait_import' => 'none', + ], + ], + 'multiline_whitespace_before_semicolons' => [ + 'strategy' => 'no_multi_line', + ], + 'single_quote' => false, + + 'binary_operator_spaces' => [ + 'default' => 'single_space', + 'operators' => [ + '=' => 'align_single_space_minimal', + '=>' => 'align_single_space_minimal', + ], + ], + 'braces' => [ + 'allow_single_line_closure' => true, + ], + 'concat_space' => [ + 'spacing' => 'one', + ], + 'declare_equal_normalize' => true, + 'function_typehint_space' => true, + 'single_line_comment_style' => [ + 'comment_types' => [ + 'hash', + ], + ], + 'include' => true, + 'lowercase_cast' => true, + 'no_extra_blank_lines' => [ + 'tokens' => [ + 'curly_brace_block', + 'extra', + 'parenthesis_brace_block', + 'throw', + ] + ], + 'no_multiline_whitespace_around_double_arrow' => true, + 'no_spaces_around_offset' => true, + 'no_unused_imports' => true, + 'no_whitespace_before_comma_in_array' => true, + 'no_whitespace_in_blank_line' => true, + 'object_operator_without_whitespace' => true, + 'single_blank_line_before_namespace' => true, + 'ternary_operator_spaces' => true, + 'trim_array_spaces' => true, + 'unary_operator_spaces' => true, + 'whitespace_after_comma_in_array' => true, + 'single_line_after_imports' => true, + 'ordered_imports' => [ + 'sort_algorithm' => 'none', + ], + //Other rules here... + ]) + ->setLineEnding("\n"); diff --git a/data/posts.json b/data/posts.json index dce7c73..c8d1a32 100644 --- a/data/posts.json +++ b/data/posts.json @@ -1,64 +1,82 @@ { + "index-content-no-image": "The path of the righteous man is beset on ALL sides by the iniquities of the selfish and the tyranny of evil men. Blessed is he who, in the name of charity and good will, shepherds the weak through the valley of darkness, for he is truly his brother's keeper and the finder of lost children. And I will strike down upon thee with great vengeance and furious anger those who would attempt to poison and destroy My brothers. And you will know My name is the Lord when I lay My vengeance upon thee.", "index-content": "
The path of the righteous man is beset on ALL sides by the iniquities of the selfish and the tyranny of evil men. Blessed is he who, in the name of charity and good will, shepherds the weak through the valley of darkness, for he is truly his brother's keeper and the finder of lost children. And I will strike down upon thee with great vengeance and furious anger those who would attempt to poison and destroy My brothers. And you will know My name is the Lord when I lay My vengeance upon thee.", - "content": "
The path of the righteous man is beset on ALL sides by the iniquities of the selfish and the tyranny of evil men. Blessed is he who, in the name of charity and good will, shepherds the weak through the valley of darkness, for he is truly his brother's keeper and the finder of lost children. And I will strike down upon thee with great vengeance and furious anger those who would attempt to poison and destroy My brothers. And you will know My name is the Lord when I lay My vengeance upon thee.
server\n { listen 80; server_name yourcoolassdomain.com; \n\tlocation / \n\t\t{ \n\t\t\tproxy_pass https://127.0.0.1:2314; \n\t\t\tproxy_http_version 1.1; \n\t\t\tproxy_set_header \n\t\t\tUpgrade $http_upgrade; \n\t\t\tproxy_set_header Connection 'upgrade'; \n\t\t\tproxy_set_header Host $host; \n\t\t\tproxy_cache_bypass $http_upgrade; \n\t\t} \n\t} 
", + "content": "this is mini text
The path of the righteous man is beset on ALL sides by the iniquities of the selfish and the tyranny of evil men. Blessed is he who, in the name of charity and good will, shepherds the weak through the valley of darkness, for he is truly his brother's keeper and the finder of lost children. And I will strike down upon thee with great vengeance and furious anger those who would attempt to poison and destroy My brothers. And you will know My name is the Lord when I lay My vengeance upon thee.
server\n { listen 80; server_name yourcoolassdomain.com; \n\tlocation / \n\t\t{ \n\t\t\tproxy_pass https://127.0.0.1:2314; \n\t\t\tproxy_http_version 1.1; \n\t\t\tproxy_set_header \n\t\t\tUpgrade $http_upgrade; \n\t\t\tproxy_set_header Connection 'upgrade'; \n\t\t\tproxy_set_header Host $host; \n\t\t\tproxy_cache_bypass $http_upgrade; \n\t\t} \n\t} 

", "feature": "/assets/video/blog/2022/01/one.mp4, /assets/images/blog/2022/03/01.jpg, /assets/images/blog/2022/03/02.jpg, /assets/images/blog/2022/03/03.jpg", "files": "/assets/docs/blog/2022/01/one.pdf, /assets/docs/blog/2022/01/two.txt, /assets/docs/blog/2022/01/three.rtf, /assets/sound/blog/2022/01/one.mp3, /assets/sound/blog/2022/01/two.mp3,", "meta": { "who": "Are0h", - "when": "6 hours ago", + "when": "2022 Jan Sun 30", "tags": [ - { "label": "star trek discovery", "slug": "star-trek-discovery" }, - { "label": " sonequa martin-green", "slug": "sonequa-martin-green" }, - { "label": " faves", "slug": "faves" }, - { "label": " damn", "slug": "damn" }, - { "label": " sci-fi", "slug": "sci-fi" }, - { "label": " tv", "slug": "tv" } + { + "label": "star trek discovery", + "slug": "star-trek-discovery" + }, + { + "label": " sonequa martin-green", + "slug": "sonequa-martin-green" + }, + { + "label": " faves", + "slug": "faves" + }, + { + "label": " damn", + "slug": "damn" + }, + { + "label": " sci-fi", + "slug": "sci-fi" + }, + { + "label": " tv", + "slug": "tv" + } ] }, "recent_posts": [ { "title": "Teyonah Parris", "slug": "teyonah-parris", - "feature": "content/blog-images/2018/11/chi-raq.jpg", + "feature": "/assets/video/blog/2022/01/one.mp4, /assets/images/blog/2022/03/01.jpg, /assets/images/blog/2022/03/02.jpg, /assets/images/blog/2022/03/03.jpg", "path": "2020/06", "created": "2018 Nov 11th" }, { "title": "Aja Naomi-King", "slug": "aja-naomi-king", - "feature": "content/blog-images/2018/11/chi-raq.jpg", + "feature": "/assets/images/blog/2022/03/01.jpg, /assets/images/blog/2022/03/02.jpg, /assets/images/blog/2022/03/03.jpg", "path": "2020/06", "created": "2018 Nov 11th" }, { "title": "Sonequa Martin-Green", "slug": "sonequa-martin-green", - "feature": "content/blog-images/2018/11/chi-raq.jpg", + "feature": "/assets/images/blog/2022/03/02.jpg, /assets/images/blog/2022/03/03.jpg", "path": "2020/06", "created": "2018 Nov 11th" }, { "title": "Jameela Jamil", "slug": "jameela-jamil", - "feature": "content/blog-images/2018/11/chi-raq.jpg", + "feature": "/assets/images/blog/2022/03/03.jpg", "path": "2020/06", "created": "2018 Nov 11th" }, { "title": "I/'m so behind", "slug": "im-so-behind", - "feature": "content/blog-images/2018/11/chi-raq.jpg", + "feature": "/assets/images/blog/2022/03/03.jpg", "path": "2020/06", "created": "2018 Nov 11th" } ], - "featured_posts": [ { "id": 6, "uuid": "e322f21e-3317-4795-a7e8-a280c4611406", "title": "Roberta MF Draper", - "feature": "/assets/images/blog/2020/06/bobbie-draper-the-expanse-1170x585.jpg", + "feature": "/src/themes/theme-rikc03/rikc03/assets/images/test/2018/05/nxt_08202016dg_2622.jpg", "path": "2020/06", "layout": "page", "tags": "faves, damn, the expanse, sci-fi, frankie adams", @@ -74,7 +92,39 @@ "id": 4, "uuid": "9051e9df-c723-42c6-bf77-59f5bb8caeac", "title": "Michael MF Burnam", - "feature": "/assets/images/blog/2020/06/Sonequa-Martin-Green-as-Michael-Burnham-in-Star-Trek-Discovery-season-3.jpg", + "feature": "/src/themes/theme-rikc03/rikc03/assets/images/test/2018/05/13603447_1213326028679516_1212043235232357993_o.jpg", + "path": "2020/06", + "layout": "page", + "tags": "star trek discovery, sonequa martin-green, faves, damn, sci-fi, tv", + "author": "Are0h", + "deleted": false, + "menu": true, + "featured": true, + "published": true, + "slug": "michael-mf-burnam", + "content": "Yall acting like Michael won/'t give you those hands." + }, + { + "id": 6, + "uuid": "e322f21e-3317-4795-a7e8-a280c4611406", + "title": "Roberta MF Draper", + "feature": "/assets/video/blog/2022/01/one.mp4", + "path": "2020/06", + "layout": "page", + "tags": "faves, damn, the expanse, sci-fi, frankie adams", + "author": "Are0h", + "deleted": false, + "menu": false, + "featured": true, + "published": true, + "slug": "roberta-mf-draper", + "content": "Even Amos doesn/'t want any smoke with Mars/' Finest. She will fuck your whole shit up. Bad. \r\n\r\nRENDER ON SAVE WORKS!\r\n" + }, + { + "id": 4, + "uuid": "9051e9df-c723-42c6-bf77-59f5bb8caeac", + "title": "Michael MF Burnam", + "feature": "/src/themes/theme-rikc03/rikc03/assets/images/test/2018/05/13603447_1213326028679516_1212043235232357993_o.jpg", "path": "2020/06", "layout": "page", "tags": "star trek discovery, sonequa martin-green, faves, damn, sci-fi, tv", diff --git a/data/settings.json b/data/settings.json index b1d8a2e..5fe9f21 100644 --- a/data/settings.json +++ b/data/settings.json @@ -9,25 +9,48 @@ "default_avi": "/assets/images/global/default-avi.png", "bucket": [{ "item": "one" }, { "item": "two" }, { "item": "three" }], "tag_list": [ - { "title": "Swaggy Disgust", "slug": "swaggy-disgust", "path": "2020/06" } + { + "title": "Swaggy Disgust", + "slug": "swaggy-disgust", + "path": "2020/06", + "feature": "/src/themes/theme-rikc03/rikc03/assets/images/test/2018/05/PREACHER_S2_207_SP_03.jpg" + }, + { + "title": "Swaggy Disgust 2", + "slug": "swaggy-disgust-2", + "path": "2020/06", + "feature": "/src/themes/theme-rikc03/rikc03/assets/images/test/2018/05/nxt_08202016dg_2622.jpg" + }, + { + "title": "Swaggy Disgust", + "slug": "swaggy-disgust", + "path": "2020/06", + "feature": "/src/themes/theme-rikc03/rikc03/assets/images/test/2018/05/PREACHER_S2_207_SP_03.jpg" + }, + { + "title": "Swaggy Disgust 2", + "slug": "swaggy-disgust-2", + "path": "2020/06", + "feature": "/src/themes/theme-rikc03/rikc03/assets/images/test/2018/05/nxt_08202016dg_2622.jpg" + } ], "menu": [ { - "title": "This is the first page", + "title": "First", "id": "3", "slug": "page-one", "uuid": "b60d6843-e957-43ac-8425-65145142236c", "path": "2021/04" }, { - "title": "This is the second page", + "title": "Second", "id": "4", "slug": "page-two", "uuid": "b60d6843-e957-43ac-8425-65145142236d", "path": "2021/04" }, { - "title": "This is the second page", + "title": "Third", "id": "5", "slug": "page-three", "uuid": "b60d6843-e957-43ac-8425-65145142236d", diff --git a/engine/StartKit.php b/engine/StartKit.php index dc1a6d7..82f4b5d 100644 --- a/engine/StartKit.php +++ b/engine/StartKit.php @@ -7,7 +7,7 @@ class StartKit public function __construct() { $settings = json_decode(file_get_contents("./package.json"), true); - $theme = $settings["config"]["current_theme"]; + $theme = $settings["config"]["current_theme"]; new ThemeEngine( "src/themes/theme-" . $theme, "/src/themes/theme-" . $theme . "/" . $theme diff --git a/engine/ThemeEngine.php b/engine/ThemeEngine.php index 93809f1..c4c57c0 100644 --- a/engine/ThemeEngine.php +++ b/engine/ThemeEngine.php @@ -10,22 +10,22 @@ class ThemeEngine public function __construct(string $themePath, string $themeAssetPath) { - $var = []; - $this->themePath = $themePath; + $var = []; + $this->themePath = $themePath; $this->themeAssetPath = $themeAssetPath; - $path = explode('/', $themeAssetPath); - $this->themeFolder = $path[4]; - $this->settings = json_decode( + $path = explode('/', $themeAssetPath); + $this->themeFolder = $path[4]; + $this->settings = json_decode( file_get_contents('./data/settings.json'), true ); - $this->posts = json_decode(file_get_contents('./data/posts.json'), true); + $this->posts = json_decode(file_get_contents('./data/posts.json'), true); $this->archives = json_decode( file_get_contents('./data/archives.json'), true ); $this->loader = new \Twig\Loader\FilesystemLoader( - $themePath.'/'.$path[4] + $themePath . '/' . $path[4] ); $this->twig = new \Twig\Environment($this->loader, []); $this->router($_SERVER['REQUEST_URI']); @@ -34,19 +34,19 @@ class ThemeEngine public function router(string $request) { $pageInfo = [ - 'keywords' => $this->settings['keywords'], - 'description' => $this->settings['description'], - 'image' => $this->themeAssetPath.'/assets/images/global/default-bg.jpg', + 'keywords' => $this->settings['keywords'], + 'description' => $this->settings['description'], + 'image' => $this->themeAssetPath . '/assets/images/global/default-bg.jpg', ]; $featureList = explode(',', $this->posts['feature']); - $fileList = explode(',', $this->posts['files']); + $fileList = explode(',', $this->posts['files']); $images = []; - $files = []; + $files = []; foreach ($featureList as $file) { $item = trim($file); - $ext = pathinfo($item, PATHINFO_EXTENSION); + $ext = pathinfo($item, PATHINFO_EXTENSION); if ($item != null || $item != '') { array_push($images, ['file' => $item, 'type' => trim($ext)]); } @@ -54,7 +54,7 @@ class ThemeEngine foreach ($fileList as $file) { $item = trim($file); - $ext = pathinfo($item, PATHINFO_EXTENSION); + $ext = pathinfo($item, PATHINFO_EXTENSION); if ($item != null || $item != '') { array_push($files, ['file' => $item, 'type' => trim($ext)]); } @@ -63,74 +63,70 @@ class ThemeEngine $menu = $this->settings['menu']; switch ($request) { case '/': - $recent = $this->posts['recent_posts']; + $recent = $this->posts['recent_posts']; $featured = $this->posts['featured_posts']; $template = 'index.twig'; - $content = $this->posts['index-content']; + $content = $this->posts['index-content-no-image']; $pageOptions = [ - 'debug' => true, // for theme kit - 'theme' => $this->themeFolder, // for theme kit - 'title' => 'This is Fipamo', - 'dynamicRender' => $this->settings['dynamicRender'], - 'background' => $this->themeAssetPath.'/assets/images/global/default-bg.jpg', - 'recent' => $recent, - 'featured' => $featured, - 'info' => $pageInfo, - 'menu' => $menu, - 'content' => $content, - 'media' => $images, - 'files' => $files, + 'debug' => true, // for theme kit + 'theme' => $this->themeFolder, // for theme kit + 'title' => 'This is Fipamo', + 'dynamicRender' => $this->settings['dynamicRender'], + 'background' => $this->themeAssetPath . '/assets/images/global/default-bg.jpg', + 'recent' => $recent, + 'featured' => $featured, + 'info' => $pageInfo, + 'menu' => $menu, + 'content' => $content, + 'media' => $images, + 'files' => $files, ]; break; case '/page': - $content = $this->posts['content']; - $meta = $this->posts['meta']; - $template = $request.'.twig'; + $content = $this->posts['content']; + $meta = $this->posts['meta']; + $template = $request . '.twig'; $pageOptions = [ - 'debug' => true, // for theme kit - 'theme' => $this->themeFolder, // for theme kit - 'title' => 'Page Title', - 'dynamicRender' => $this->settings['dynamicRender'], - 'background' => $this->themeAssetPath.'/assets/images/global/default-bg.jpg', - 'content' => $content, - 'meta' => $meta, - 'info' => $pageInfo, - 'menu' => $menu, - 'media' => $images, - 'files' => $files, + 'debug' => true, // for theme kit + 'theme' => $this->themeFolder, // for theme kit + 'title' => 'Page Title', + 'dynamicRender' => $this->settings['dynamicRender'], + 'background' => $this->themeAssetPath . '/assets/images/global/default-bg.jpg', + 'content' => $content, + 'meta' => $meta, + 'info' => $pageInfo, + 'menu' => $menu, + 'media' => $images, + 'files' => $files, ]; break; case '/tags': - $tags = $this->settings['tag_list']; - $template = 'tags.twig'; + $tags = $this->settings['tag_list']; + $template = 'tags.twig'; $pageOptions = [ - 'debug' => true, // for theme kit - 'theme' => $this->themeFolder, // for theme kit - 'title' => 'Pages Tagged as Tag', - 'dynamicRender' => $this->settings['dynamicRender'], - 'background' => $this->themeAssetPath.'/assets/images/global/default-bg.jpg', - 'tag_list' => $tags, - 'info' => $pageInfo, - 'menu' => $menu, - 'media' => $images, - 'files' => $files, + 'debug' => true, // for theme kit + 'theme' => $this->themeFolder, // for theme kit + 'title' => 'Pages Tagged as Tag', + 'dynamicRender' => $this->settings['dynamicRender'], + 'background' => $this->themeAssetPath . '/assets/images/global/default-bg.jpg', + 'tag_list' => $tags, + 'info' => $pageInfo, + 'menu' => $menu, ]; break; case '/archive': - $archive = $this->archives; - $template = 'archive.twig'; + $archive = $this->archives; + $template = 'archive.twig'; $pageOptions = [ - 'debug' => true, // for theme kit - 'theme' => $this->themeFolder, // for theme kit - 'title' => 'Archive', - 'dynamicRender' => $this->settings['dynamicRender'], - 'background' => $this->themeAssetPath.'/assets/images/global/default-bg.jpg', - 'archives' => $archive['archives'], - 'info' => $pageInfo, - 'menu' => $menu, - 'media' => $images, - 'files' => $files, + 'debug' => true, // for theme kit + 'theme' => $this->themeFolder, // for theme kit + 'title' => 'Archive', + 'dynamicRender' => $this->settings['dynamicRender'], + 'background' => $this->themeAssetPath . '/assets/images/global/default-bg.jpg', + 'archives' => $archive['archives'], + 'info' => $pageInfo, + 'menu' => $menu, ]; break; default: @@ -138,18 +134,18 @@ class ThemeEngine // throw new \ErrorException($errstr, $errno, 0, $errfile, $errline); $error = $errstr; }); - $template = 'error.twig'; + $template = 'error.twig'; $pageOptions = [ - 'debug' => true, // for theme kit - 'theme' => $this->themeFolder, // for theme kit - 'title' => 'Uh oh', - 'dynamicRender' => $this->settings['dynamicRender'], - 'background' => $this->themeAssetPath.'/assets/images/global/default-bg.jpg', - 'info' => $pageInfo, - 'content' => "Uh Oh, so there's a problem.", - 'menu' => $menu, - 'media' => $images, - 'files' => $files, + 'debug' => true, // for theme kit + 'theme' => $this->themeFolder, // for theme kit + 'title' => 'Uh oh', + 'dynamicRender' => $this->settings['dynamicRender'], + 'background' => $this->themeAssetPath . '/assets/images/global/default-bg.jpg', + 'info' => $pageInfo, + 'content' => "Uh Oh, so there's a problem.", + 'menu' => $menu, + 'media' => $images, + 'files' => $files, ]; break; diff --git a/index.php b/index.php index 579ddc3..c72995d 100644 --- a/index.php +++ b/index.php @@ -1,7 +1,7 @@ -
- {{title}} -
- -
-
- {% for item in archives %} -
- - {{item.year}} - - {% for data in item.year_data %} -
- - {{data.full_month}} - - {% for page in data.pages %} - {% if dynamicRender is defined %} - {% if dynamicRender == 'true' %} - {{page.title}}
- {% else %} - {{page.title}}
- {% endif %} - - {% else %} - {{page.title}}
- {% endif %} - {% endfor %} -
- - {% endfor %} -
- {% endfor %} - -
-
- {% endblock %} \ No newline at end of file +{% block mainContent %} +
+
+ {{ title }} +
+
+
+
+ {% for item in archives %} +
+ + {{ item.year }} + + {% for data in item.year_data %} +
+ + {{ data.full_month }} + + {% for page in data.pages %} + {% if dynamicRender is defined %} + {% if dynamicRender == 'true' %} + {{ page.title }}
+ {% else %} + {{ page.title }}
+ {% endif %} + + {% else %} + {{ page.title }}
+ {% endif %} + {% endfor %} +
+ + {% endfor %} +
+ {% endfor %} + +
+
+{% endblock %} \ No newline at end of file diff --git a/src/themes/theme-fipamo-default/fipamo-default/page.twig b/src/themes/theme-fipamo-default/fipamo-default/page.twig index e3f3d53..fecc238 100644 --- a/src/themes/theme-fipamo-default/fipamo-default/page.twig +++ b/src/themes/theme-fipamo-default/fipamo-default/page.twig @@ -1,59 +1,62 @@ {% extends "frame.twig" %} {% block title %} - {{ title }} + {{ title }} {% endblock %} - {% block mainContent %} -
-
- {{title}} -
-
-
-
-

{{content | raw}}

-
-
-
- Files
- {% for doc in files %} - {% if doc.type != "mp3" %} - {% set path = doc.file|split('/') %} - {{path[6]}} - - {% endif %} - {% endfor %} -
-
- Sounds
- {% for doc in files %} - {% if doc.type == "mp3" %} - - {% endif %} - {% endfor %} -
- -
-
- {{meta['who']}} dropped this {{ meta['when'] }}
- tags: - {% for tag in meta['tags'] %} - {% if dynamicRender is defined %} - {% if dynamicRender == 'true' %} - {{ tag.label }} - {% else %} - {{ tag.label }} - {% endif %} - {% else %} - {{ tag.label }} - {% endif %} - {% endfor %} - -
-
-
- {% endblock %} \ No newline at end of file +{% block mainContent %} +
+
+ {{ title }} +
+
+
+
+

{{ content | raw }}

+
+
+
+ Files
+ {% for doc in files %} + {% if doc.type != "mp3" %} + {% set path = doc.file|split('/') %} + {{ path[6] }} + + {% endif %} + {% endfor %} +
+
+ Sounds
+ {% for doc in files %} + {% if doc.type == "mp3" %} + + {% endif %} + {% endfor %} +
+ +
+
+ {{ meta['who'] }} + dropped this + {{ meta['when'] }}
+ tags: + + {% for tag in meta['tags'] %} + {% if dynamicRender is defined %} + {% if dynamicRender == 'true' %} + {{ tag.label }} + {% else %} + {{ tag.label }} + {% endif %} + {% else %} + {{ tag.label }} + {% endif %} + {% endfor %} + +
+
+
+ {% endblock %} \ No newline at end of file