diff --git a/README.md b/README.md index a923467..65230c4 100644 --- a/README.md +++ b/README.md @@ -5,226 +5,14 @@ Just like an actual proton is only one part of an atom, Proton is just one piece This project is meant to replace the popular [panini](https://github.com/foundation/panini) site generator. ``` -This project is in very early stages. Any feedback would be appreciated. +This project is in beta. Any feedback would be appreciated. -PLEASE DO NOT USE THIS FOR PRODUCTION PROJECTS. BREAKING CHANGES MAY BE MADE AT ANY TIME FOR NOW +PLEASE DO NOT USE THIS FOR PRODUCTION PROJECTS. ``` -## Proton Features +## Documentation -Here are a list of the main features of Proton and how it can help you. +Check out the docs at -### Reduce NPM Dependencies - -Managing NPM dependencies has become a difficult task. Both of our development tools and front end libraries are managed by the same package manager. Too often we have to deal with dependency conflicts between libraries and tools that we want to use. Proton is built with PHP. This means that it's dependencies lay 100% outside of your project's NPM dependency chain. - -### Twig Templates - -Proton leverages [Twig](https://twig.symfony.com) to bring you a powerful, flexible and fast templating system. Check out the [Twig Templates for Designers](https://twig.symfony.com/doc/3.x/templates.html) docs. If you like Handlebars, you will be blown away with Twig. - -Twig templates can be written in multiple formats. Markdown and Pug will be compiled down to HTML. All other languages, like HTML and PHP, will pass through into the compiled webpages. - -You can add front matter to your templates in order to customize options and provide page specific data. - -### Template Inheritance - -There are 4 levels of templates that you can use: layouts, pages, partials and macros. - -Pages are the core template type. You will create pages for each public page that you want on your site (batching aside, see below). Each page can inherit from one layout. Partials and macros allow you to create reusable components that you can use across all of your pages. - -### Data Storage - -Data can be stored in either YAML or JSON files. You can have multiple data files named differently. All data files are stored in one giant data structure during page compilation. This means that every page has full access to all pages. - -Data stored in the YAML frontmatter on each page will override the global data. - -### Batch Generate Pages - -You can create a single page that can be used to generate multiple pages for each item within a data set. For example, if you can define an array of products inside of your data. You can then create a page for each product by applying these data fro each to the one page template that you created. - -## Installation - -Proton requires that you have [Composer](https://getcomposer.org) installed. It may be easiest to then install proton globally on your computer. You can do this with the following command. - -```sh -composer global require foundation/proton -``` - -Make sure that you add the composer global installation folder to your shell PATH. By default it should be in the following location: `~/.composer/vendor/bin` - -## Getting Started - -You can see an example of how to setup proton in the [sample folder](https://github.com/foundation/proton/tree/master/sample). You can also create a [proton.yml](https://github.com/foundation/proton/blob/master/proton.yml) configuration file. - -You can use the following command to create the default structure needed for proton. You can optionally add the `--config` option to generate a config file as well. - -```sh -$ proton init --config -``` - -Then you can run the following command to build your site. - -```sh -$ proton build -``` - -## Template Overview - -Layouts are the highest level of template. These traditionally contain the base HTML for your webpage. This could include the page `` and the basic layouts for your webpages. You can have multiple layouts that can be used across your pages. A page can only have one layout. - -Layouts may also contain content blocks. These blocks of content can be overwritten in pages. For example, a layout may have a content block for the header, main content, sidebar and footer. These are on top of the data variables that you can also inject into your templates for further customization. - -Partials allow you to create reusable pieces of content that can be used across multiple pages or possible multiple times on that same page. Partials are great for navigation, CTAs, subscription forms and more. - -Lastly, macros are basically functions that allow you to pass parameters in order to generate content in your pages. You can use these macros as many times as you want. - -**Make sure that you thoroughly review the [Twig for Template Designers](https://twig.symfony.com/doc/3.x/templates.html) documentation.** - -### Default Content Block - -If no content blocks are defined in your page template, a default `content` block will be added so that you can leverage the content inside of your layouts. - -### Markdown - -In order to process content as markdown inside of a template, you simple need to make sure that the file extension of the template is `md`. Example: `template.md` - -You can also use markdown in parts of your template with a markdown filter. - -``` -{% markdown %} -### Header - -This is my content -{% endmarkdown %} -``` - -### Pug - -You can process a template using Pug simply by giving the file that `pug` extension. Example: `template.pug` - -### Page Destinations - -All templates will be named the exact same name in the exact same folder structure inside of the configured `dist` folder. There are the following exceptions: - -* All templates with `pug`, `twig` and `md` extensions will become `html` files by default. You can change this with the `defaultExt` configuration value. -* You can customize the path and filename that a page gets output to via the `output` parameter set inside of a page's frontmatter. - -### Page Formatting - -You can use the `pretty` and `minify` configuration values to determine if the output of a page's HTML will be minified or indented to look "pretty". - -## Data Overview - -Storing data inside Proton is very flexible to work with Manu different workflows. By default, data is stored in YAML and JSON files inside of the `data` folder. However, this folder is configurable. - -### Default data.yml/json - -Data stored in the in the `data.yml` or `data.json` files are special in that they are stored at the top level of that global data structure. For example, let's look at this YAML data. - -```yaml -project: Proton - CLI Tool for compiling web pages -version: 1.0.0 -``` - -You will be able to insert this data into your page content via standard mustache syntax: `{{ project }}` and `{{ version }}` - -### Data Hierarchy - -There are many ways to create hierarchy within your data. - -Inside of your default data file (see above), you can add your own hierarchy inside of the yaml/json. - -```yaml -level1: - level2A: - propA: lorem ipsum - propB: lorem ipsum - level2B: - propA: lorem ipsum - propB: lorem ipsum -``` - -The above data would be the same as if you were to create a file named `level1.yml` with the following content. - -```yaml -level2A: - propA: lorem ipsum - propB: lorem ipsum -level2B: - propA: lorem ipsum - propB: lorem ipsum -``` - -The last way would be to add a folder structure into the mix. If you create a folder inside your data folder named `level1`, then crate yaml files for each level2 object: `level1/level2A.yml` and `level1/level2B.yml`. Each of these files would contain their data. - -```yaml -propA: lorem ipsum -propB: lorem ipsum -``` - -For all of the example above you can access the data just like you would traditionally with mustache templates: `{{ level1.level2A.propB }}` - -Twig provides many logic based functions like `for` loops that allow you iterate through data to create content dynamically. - -### Front Matter Data - -You can define data via YAML as frontmatter on any page. Data defined inside of the frontmatter is specific to just that page. Any values defined will overwrite any global data stored. - -There are a few special variables that can be defined inside of your frontmatter. - -* `layout`: defines the layout to use for the page. If no layout is defined the default layout defined in the configuration will be used. You can set this to `none` in order to have no default layout set. -* `output`: This sets the destination name of the page when it gets compiled into the configured `dist` folder. -* `batch`: Batch create pages based on an array of items in your data. This could allow you to create multiple items (such as products) based on the same page but with different data defined within your data. - -Example: - -``` ---- -layout: default.html -title: My awesome webpage ---- - -My Page Content... -``` - -### Debugging Data - -You can use the `data` command with proton to analyze the data that proton will use to build your pages. This can help you visualize how proton builds the data strucute. You can also pass an optional `--page` parameter in order to take into account a page's front matter so you can see the exact data strcutre used to build a single page. - -```sh -$ proton data -$ proton data --page=subfolder/index.html -``` - -## Configuration - -At the root of your project you can create a config file called either `proton.yml` or `.proton.yml`. - -```yaml -autoindex: true -debug: false -defaultExt: html -minify: false -pretty: true -paths: - batch: sample/batch - data: sample/data - dist: sample/dist - layouts: sample/layouts - macros: sample/macros - pages: sample/pages - partials: sample/partials -layouts: - default: default.html - rules: - subfolder: subfolder.html - blog: blog.html -``` - -## Updating Proton - -Updating proton could not be easier. You simply need to run the `self-update` command to update to the most recent version. - -```sh -$ proton self-update -``` +* [Installation](https://docs.page/foundation/proton/install) +* [Getting Started](https://docs.page/foundation/proton/getting-started) diff --git a/app/Commands/Build.php b/app/Commands/Build.php index 78d6d4a..e4bdfc8 100644 --- a/app/Commands/Build.php +++ b/app/Commands/Build.php @@ -36,13 +36,13 @@ public function handle() $this->error('Not all required paths exist to build site. You can run `proton init` to ensure everything is setup.'); return; } - $this->info('Cleaning previous builds.'); + $this->info('Cleaning previous builds'); $fsManager->cleanupDist(); //---------------------------------- // Load in Data //---------------------------------- - $this->info('Loading data.'); + $this->info('Loading data'); $data = new \App\Proton\Data($config); if ($config->settings->debug) { @@ -53,11 +53,26 @@ public function handle() //---------------------------------- // Process all pages //---------------------------------- - $this->info('Compiling Pages.'); + $this->info('Compiling Pages'); $pageManger = new \App\Proton\PageManager($config, $data); $pageManger->compilePages(); + //---------------------------------- + // Create Sitemap + //---------------------------------- + if ($config->settings->sitemap) { + $this->info('Building Sitemap'); + $sitemap = new \App\Proton\Sitemap($config); + $sitemap->write(); + } + + //---------------------------------- + // Copy Assets + //---------------------------------- + $this->info('Copying Assets'); + $assetManger = new \App\Proton\AssetManager($config); + $assetManger->copyAssets(); - $this->info('Build Complete.'); + $this->info('Build Complete'); } } diff --git a/app/Proton/AssetManager.php b/app/Proton/AssetManager.php new file mode 100644 index 0000000..fca08aa --- /dev/null +++ b/app/Proton/AssetManager.php @@ -0,0 +1,35 @@ +config = $config; + $this->paths = $config->settings->paths; + } + + public function copyAssets(): void + { + $fsManager = new FilesystemManager($this->config); + $assets = $fsManager->getAllFiles($this->paths->assets); + foreach ($assets as $asset) { + $from = $this->paths->assets .DIRECTORY_SEPARATOR. $asset; + $to = $this->paths->dist .DIRECTORY_SEPARATOR. $asset; + $dir = dirname($to); + if (!file_exists($dir)) { + mkdir($dir, 0777, true); + } + copy($from, $to); + } + } +} diff --git a/app/Proton/Config.php b/app/Proton/Config.php index f868014..12a94a7 100644 --- a/app/Proton/Config.php +++ b/app/Proton/Config.php @@ -15,10 +15,12 @@ class Config ]; const DEFAULTS = [ "defaultExt" => "html", + "domain" => "https://www.example.com", "autoindex" => true, "debug" => false, "pretty" => true, "minify" => false, + "sitemap" => true, "layouts" => [ "default" => "default.html", "rules" => [ @@ -27,6 +29,7 @@ class Config ], "paths" => [ "dist" => "dist", + "assets" => "src/assets", "data" => "src/data", "layouts" => "src/layouts", "macros" => "src/macros", diff --git a/app/Proton/FilesystemManager.php b/app/Proton/FilesystemManager.php index 9526af9..6967400 100644 --- a/app/Proton/FilesystemManager.php +++ b/app/Proton/FilesystemManager.php @@ -23,6 +23,21 @@ public function printPaths(): void } } + public function getAllFiles(string $path): array + { + $directory = new \RecursiveDirectoryIterator($path); + $directory->setFlags(\RecursiveDirectoryIterator::SKIP_DOTS); + $iterator = new \RecursiveIteratorIterator($directory); + $files = []; + // The length of the pages folder name + / + $dirLength = strlen($path)+1; + foreach ($iterator as $info) { + // Remove the pages fodler name from the file name + $files[] = substr_replace($info->getPathname(), '', 0, $dirLength); + } + return $files; + } + public function pathsExist(): bool { // Check if all paths exist diff --git a/app/Proton/PageBatchWriter.php b/app/Proton/PageBatchWriter.php index b490014..eaf61a0 100644 --- a/app/Proton/PageBatchWriter.php +++ b/app/Proton/PageBatchWriter.php @@ -20,7 +20,11 @@ public function processBatch(): void { $batchkey = $this->page->data[Page::BATCHKEY]; $batchData = $this->page->data[$batchkey]; - foreach ($batchData as $key => $data) { + foreach ($batchData as $key => $props) { + // merge batch data into the global data batch key + $data = $this->page->data; + $data['batch'] = $props; + $this->output = $this->render($data); $this->formatOutput(); diff --git a/app/Proton/PageManager.php b/app/Proton/PageManager.php index 1cd37cd..c5896c6 100644 --- a/app/Proton/PageManager.php +++ b/app/Proton/PageManager.php @@ -30,7 +30,9 @@ public function __construct(Config $config, Data $data) public function compilePages(): void { - foreach ($this->getAllPages() as $pageName) { + $fsManager = new FilesystemManager($this->config); + $pages = $fsManager->getAllFiles($this->paths->pages); + foreach ($pages as $pageName) { $page = new Page($pageName, $this->config, $this->data); $loader = $this->createPageLoader($pageName, $page->content); if ($page->isBatch()) { @@ -67,19 +69,4 @@ private function initTemplateLoader(): FilesystemLoader $loader->addPath($this->paths->layouts, "layouts"); return $loader; } - - private function getAllPages(): array - { - $directory = new \RecursiveDirectoryIterator($this->paths->pages); - $directory->setFlags(\RecursiveDirectoryIterator::SKIP_DOTS); - $iterator = new \RecursiveIteratorIterator($directory); - $pages = []; - // The length of the pages folder name + / - $dirLength = strlen($this->paths->pages)+1; - foreach ($iterator as $info) { - // Remove the pages fodler name from the file name - $pages[] = substr_replace($info->getPathname(), '', 0, $dirLength); - } - return $pages; - } } diff --git a/app/Proton/Sitemap.php b/app/Proton/Sitemap.php new file mode 100644 index 0000000..6b78b99 --- /dev/null +++ b/app/Proton/Sitemap.php @@ -0,0 +1,32 @@ +config = $config; + } + + public function write(): void + { + $dir = $this->config->settings->paths->dist; + $fsManager = new FilesystemManager($this->config); + $assets = $fsManager->getAllFiles($dir); + $domain = $this->config->settings->domain; + $sitemap = new \samdark\sitemap\Sitemap($dir .DIRECTORY_SEPARATOR. self::SITEMAP); + foreach ($assets as $asset) { + $url = $domain .'/'. $asset; + $sitemap->addItem($url); + } + $sitemap->write(); + } +} diff --git a/builds/proton b/builds/proton index 9924214..1f568d2 100755 Binary files a/builds/proton and b/builds/proton differ diff --git a/composer.json b/composer.json index 0b82f81..bc0ae32 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,7 @@ "laravel-zero/phar-updater": "^1.0.6", "michelf/php-markdown": "^1.9", "pug/twig": "^1.2", + "samdark/sitemap": "^2.4", "twig/twig": "^3.3", "webuni/front-matter": "^1.3", "wyrihaximus/html-compress": "^4.1" diff --git a/composer.lock b/composer.lock index 22bbf8e..50728f6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "99c6be4b20a6e3591ec0af9ccd4dc384", + "content-hash": "72cda96fd06ce41acc5617753e3648ad", "packages": [ { "name": "aptoma/twig-markdown", @@ -2245,6 +2245,65 @@ ], "time": "2021-05-02T17:13:06+00:00" }, + { + "name": "samdark/sitemap", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/samdark/sitemap.git", + "reference": "a7ba091766afeb5fad1ea3056b30f26c3a620962" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/samdark/sitemap/zipball/a7ba091766afeb5fad1ea3056b30f26c3a620962", + "reference": "a7ba091766afeb5fad1ea3056b30f26c3a620962", + "shasum": "" + }, + "require": { + "ext-xmlwriter": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "samdark\\sitemap\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Alexander Makarov", + "email": "sam@rmcreative.ru", + "homepage": "http://rmcreative.ru/" + } + ], + "description": "Sitemap and sitemap index builder", + "homepage": "https://github.com/samdark/sitemap", + "keywords": [ + "Sitemap" + ], + "support": { + "issues": "https://github.com/samdark/sitemap/issues", + "source": "https://github.com/samdark/sitemap" + }, + "funding": [ + { + "url": "https://github.com/samdark", + "type": "github" + }, + { + "url": "https://www.patreon.com/samdark", + "type": "patreon" + } + ], + "time": "2021-10-02T04:04:28+00:00" + }, { "name": "sebastian/cli-parser", "version": "1.0.1", diff --git a/docs.json b/docs.json new file mode 100644 index 0000000..ce1f402 --- /dev/null +++ b/docs.json @@ -0,0 +1,17 @@ +{ + "name": "Proton", + "theme": "#36B9B9", + "twitter": "foundationcss", + "sidebar": [ + ["Overview", "/"], + ["Installation", "/install"], + ["Getting Started", "/getting-started"], + ["Configuration", "/configuration"], + ["Page & Templates", "/templates"], + ["Site Assets", "/assets"], + ["Adding Data", "/data"], + ["Generate Sitemap", "/sitemap"], + ["Updating Proton", "/update"], + ["About Foundation", "https://get.foundation/"] + ] +} diff --git a/docs/assets.mdx b/docs/assets.mdx new file mode 100644 index 0000000..fa4afbb --- /dev/null +++ b/docs/assets.mdx @@ -0,0 +1,13 @@ +# Assets Overview + +Any file placed into the configured `assets` folder will be directly copied into the `dist` folder. The files at the top level will be copied into the top level of the dist folder. + +The assets folder is a perfect place to store all of your site assets. + +* images +* JavaScript +* CSS +* webmanifest +* .htaccess +* favicons +* So much more... diff --git a/docs/configuration.mdx b/docs/configuration.mdx index 185bc96..6bd74bb 100644 --- a/docs/configuration.mdx +++ b/docs/configuration.mdx @@ -6,16 +6,18 @@ At the root of your project you can create a config file called either `proton.y autoindex: true debug: false defaultExt: html +domain: https://www.example.com minify: false pretty: true +sitemap: true paths: - batch: sample/batch - data: sample/data - dist: sample/dist - layouts: sample/layouts - macros: sample/macros - pages: sample/pages - partials: sample/partials + dist: dist + assets: src/assets + data: src/data + layouts: src/layouts + macros: src/macros + pages: src/pages + partials: src/partials layouts: default: default.html rules: diff --git a/docs/data.mdx b/docs/data.mdx new file mode 100644 index 0000000..e50f867 --- /dev/null +++ b/docs/data.mdx @@ -0,0 +1,82 @@ +# Data Overview + +Storing data inside Proton is very flexible to work with Manu different workflows. By default, data is stored in YAML and JSON files inside of the `data` folder. However, this folder is configurable. + +### Default data.yml/json + +Data stored in the in the `data.yml` or `data.json` files are special in that they are stored at the top level of that global data structure. For example, let's look at this YAML data. + +```yaml +project: Proton - CLI Tool for compiling web pages +version: 1.0.0 +``` + +You will be able to insert this data into your page content via standard mustache syntax: `{{ project }}` and `{{ version }}` + +### Data Hierarchy + +There are many ways to create hierarchy within your data. + +Inside of your default data file (see above), you can add your own hierarchy inside of the yaml/json. + +```yaml +level1: + level2A: + propA: lorem ipsum + propB: lorem ipsum + level2B: + propA: lorem ipsum + propB: lorem ipsum +``` + +The above data would be the same as if you were to create a file named `level1.yml` with the following content. + +```yaml +level2A: + propA: lorem ipsum + propB: lorem ipsum +level2B: + propA: lorem ipsum + propB: lorem ipsum +``` + +The last way would be to add a folder structure into the mix. If you create a folder inside your data folder named `level1`, then crate yaml files for each level2 object: `level1/level2A.yml` and `level1/level2B.yml`. Each of these files would contain their data. + +```yaml +propA: lorem ipsum +propB: lorem ipsum +``` + +For all of the example above you can access the data just like you would traditionally with mustache templates: `{{ level1.level2A.propB }}` + +Twig provides many logic based functions like `for` loops that allow you iterate through data to create content dynamically. + +### Front Matter Data + +You can define data via YAML as frontmatter on any page. Data defined inside of the frontmatter is specific to just that page. Any values defined will overwrite any global data stored. + +There are a few special variables that can be defined inside of your frontmatter. + +* `layout`: defines the layout to use for the page. If no layout is defined the default layout defined in the configuration will be used. You can set this to `none` in order to have no default layout set. +* `output`: This sets the destination name of the page when it gets compiled into the configured `dist` folder. +* `batch`: Batch create pages based on an array of items in your data. This could allow you to create multiple items (such as products) based on the same page but with different data defined within your data. + +Example: + +``` +--- +layout: default.html +title: My awesome webpage +--- + +My Page Content... +``` + +### Debugging Data + +You can use the `data` command with proton to analyze the data that proton will use to build your pages. This can help you visualize how proton builds the data strucute. You can also pass an optional `--page` parameter in order to take into account a page's front matter so you can see the exact data strcutre used to build a single page. + +```sh +$ proton data +$ proton data --page=subfolder/index.html +``` diff --git a/docs/index.mdx b/docs/index.mdx index fbbdf5a..10cbe76 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -1,14 +1,9 @@ -# Proton - CLI Tool for compiling web pages and email +# Proton -Just like an actual proton is only one part of an atom, Proton is just one piece of the core for your project. It is not intended to be a full fledged CMS. It does zero management of your JavaScript, CSS, Assets or any kind of API that you website may require. Proton can compile your webpage assets into HTML, PHP or whatever language you may require. - -This project is meant to replace the popular [panini](https://github.com/foundation/panini) site generator. +## CLI Tool for compiling web pages and email -``` -This project is in very early stages. Any feedback would be appreciated. +Just like an actual proton is only one part of an atom, Proton is just one piece of the core for your project. It is not intended to be a full fledged CMS. It does zero management of your JavaScript, CSS, Assets or any kind of API that you website may require. Proton can compile your webpage assets into HTML, PHP or whatever language you may require. -PLEASE DO NOT USE THIS FOR PRODUCTION PROJECTS. BREAKING CHANGES MAY BE MADE AT ANY TIME FOR NOW -``` ## Proton Features diff --git a/docs/install.mdx b/docs/install.mdx new file mode 100644 index 0000000..75d0fbe --- /dev/null +++ b/docs/install.mdx @@ -0,0 +1,9 @@ +# Installation + +Proton requires that you have [Composer](https://getcomposer.org) installed. It may be easiest to then install proton globally on your computer. You can do this with the following command. + +```sh +composer global require foundation/proton +``` + +Make sure that you add the composer global installation folder to your shell PATH. By default it should be in the following location: `~/.composer/vendor/bin` \ No newline at end of file diff --git a/docs/sitemap.mdx b/docs/sitemap.mdx new file mode 100644 index 0000000..82984af --- /dev/null +++ b/docs/sitemap.mdx @@ -0,0 +1,5 @@ +# Generating a Sitemap + +By default, Proton will generate a `sitemap.xml` file for SEO. You can turn off this feature by setting the `sitemap` option in the config file to `false`. + +One important setting that you will need to make sure that you set is the `domain` setting. This setting should contain the root URL of your website. For example: `https://www.example.com`. diff --git a/docs/update.mdx b/docs/update.mdx new file mode 100644 index 0000000..7a82fe4 --- /dev/null +++ b/docs/update.mdx @@ -0,0 +1,7 @@ +# Updating Proton + +Updating proton could not be easier. You simply need to run the `self-update` command to update to the most recent version. + +```sh +$ proton self-update +``` diff --git a/proton.yml b/proton.yml index bc2679d..6c6e8fa 100644 --- a/proton.yml +++ b/proton.yml @@ -1,6 +1,7 @@ paths: - data: sample/data dist: sample/dist + assets: sample/assets + data: sample/data layouts: sample/layouts macros: sample/macros pages: sample/pages diff --git a/sample/assets/.htaccess b/sample/assets/.htaccess new file mode 100644 index 0000000..dee1157 --- /dev/null +++ b/sample/assets/.htaccess @@ -0,0 +1 @@ +# testing assets \ No newline at end of file